Unity VR First Person Shooter
Creating Terrain
- Open Unity and create a new project called
YourName-Unity-VR-FPS
- Go into the Assets store and search and download Mega Fantasy Props Pack. After the download is complete, click import.
- Create a new Terrain by right-clicking in the Hierarchy window then select
3D Object > Terrain
- Change the Terrain Width and Terrain Length of the terrain to 200:
- Under the Terrain component, we select the paintbrush from the tool bar to paint the texture.
- Add the
grass_1_fixed_d
texture. You can do this by searching forgrass_1_fixed_d
in the Assets folder located in the Project window. Set the x and y size of the terrain to 1 - Add another texture called
Dirt_1_fixed_d
- Select the dirt texture and the biggest brush to add a dirt patch. This is where we will put our buildings.
Adding Buildings
- Click on the
Mega Fantasy Props Pack
folder. Add buildings or props from thePrefab
folder. Be creative!
Setting Up Our Character
NOTE: Because this is a VR game, the MAIN CAMERA will be our player
- Go into the Hierarchy window and create an empty game object make sure nothing is highlighted in the Hierarchy window before you add the empty object!. Right click and select
Create Empty
. Rename itPlayer
then we’ll make our main camera a child of player. - From the Hierarchy window, drag the
Main Camera
into thePlayer
so that Main Camera becomes the child of Player. Your Hierarchy should look like: - Set the
Main Camera
position to (0, 1, 0) and thePlayer
position to be (5, 1, 5) - Add a
Capsule Collider
component to thePlayer
game object and set the height to 2 - Attach a
RigidBody
component toPlayer
- Make sure the
Player
game object is highlighted. Next, create a new script calledPlayerController
. Delete everything in the script and add the following:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour {
public float Speed = 3f;
private Vector3 _movement;
private Rigidbody _playerRigidBody;
private void Awake()
{
_playerRigidBody = GetComponent<Rigidbody>();
}
private void FixedUpdate()
{
float horizontal = Input.GetAxisRaw("Horizontal");
float vertical = Input.GetAxisRaw("Vertical");
Move(horizontal, vertical);
}
private void Move(float horizontal, float vertical)
{
_movement = (vertical * transform.forward) + (horizontal * transform.right);
_movement = _movement.normalized * Speed * Time.deltaTime;
_playerRigidBody.MovePosition(transform.position + _movement);
}
}
Rotating Camera
- In the Hierarchy window, select
Main Camera
and add a new script component calledMouseCameraController
delete everything and add the following:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MouseCameraController : MonoBehaviour {
public float Sensitivity = 5.0f;
public float Smoothing = 2.0f;
private Vector2 _mouseLook;
private Vector2 _smoothV;
private GameObject _player;
void Awake () {
_player = transform.parent.gameObject;
}
// Update is called once per frame
void Update () {
Vector2 mouseDirection = new Vector2 (Input.GetAxisRaw ("Mouse X"), Input.GetAxisRaw ("Mouse Y"));
mouseDirection.x *= Sensitivity * Smoothing;
mouseDirection.y *= Sensitivity * Smoothing;
_smoothV.x = Mathf.Lerp (_smoothV.x, mouseDirection.x, 1f / Smoothing);
_smoothV.y = Mathf.Lerp (_smoothV.y, mouseDirection.y, 1f / Smoothing);
_mouseLook += _smoothV;
_mouseLook.y = Mathf.Clamp (_mouseLook.y, -90, 90);
transform.localRotation = Quaternion.AngleAxis (-_mouseLook.y, Vector3.right);
_player.transform.rotation = Quaternion.AngleAxis (_mouseLook.x, _player.transform.up);
}
}
Setting Up A Weapon
- In the Hierarchy window, select
Main Camera
. Next, create aCube
3D Object and rename it toGun
- Change the
Position
to (0.25, -0.5, 1) and theScale
to (0.25, 0.25, .5) - In the
Inspector
window remove theBox Collider
component from theGun
game object. Your window should look like the following: - Select
Gun
next, right click and selectEffects > Particle System
- Change the
Particle System
configurations to:
- Duration: 1
- Looping: unchecked
- Start Lifetime: 0.05
- Start Speed: 5
- Start Size: 1
- Start Color: Yellow
- Play Awake: unchecked
- In the
Hierarchy
window selectMain Camera
and create a new script calledPlayerShootingController
delete everything and replace with:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerShootingController : MonoBehaviour {
public float Range = 100;
private Camera _camera;
private ParticleSystem _particle;
void Start () {
_camera = Camera.main;
_particle = GetComponentInChildren<ParticleSystem>();
Cursor.lockState = CursorLockMode.Locked;
}
void Update () {
if (Input.GetMouseButton (0)) {
Ray ray = _camera.ScreenPointToRay (Input.mousePosition);
RaycastHit hit = new RaycastHit ();
if (Physics.Raycast (ray, out hit, Range)) {
print ("hit " + hit.collider.gameObject);
_particle.Play ();
}
}
}
}
- In the
Hierarchy
window, make sure everything is deselected. Next, right-click and selectUI > Image
. Rename the image toCrosshair
- In the
Inspector
window change the Width and Height to 10. You should now have something that looks like: - Select
Canvas
and add aCanvas Group
from theInspector
window. DeselectInteractable
andBlocks Raycasts
Creating An Enemy
- In the
Hierarchy
window, make sure everything is deselected. Next, create a newCube
3D Object. Rename it toEnemy
. - With
Enemy
selected in theHierarchy
window, create a new script calledEnemyHealth
. Delete everything and replace with:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyHealth : MonoBehaviour {
public float Health = 10;
public void TakeDamage (float damage) {
Health -= damage;
if (Health == 0) {
Destroy (gameObject);
}
}
}
- With the
Enemy
object selected in theHierarchy
window. Go to theInspector
window and find theLayer
drop down and add a new layer calledShootable
. Your window should now look like the following: - Go back to the
PlayerShootingController
script and replace the code with the following:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerShootingController : MonoBehaviour {
public float Range = 100;
private Camera _camera;
private ParticleSystem _particle;
private LayerMask _shootableMask;
void Start () {
_camera = Camera.main;
_particle = GetComponentInChildren<ParticleSystem> ();
Cursor.lockState = CursorLockMode.Locked;
_shootableMask = LayerMask.GetMask ("Shootable");
}
void Update () {
if (Input.GetMouseButton (0)) {
Ray ray = _camera.ScreenPointToRay (Input.mousePosition);
RaycastHit hit = new RaycastHit ();
if (Physics.Raycast (ray, out hit, Range, _shootableMask)) {
print ("hit " + hit.collider.gameObject);
_particle.Play ();
EnemyHealth health = hit.collider.GetComponent<EnemyHealth> ();
if (health != null) {
health.TakeDamage (1);
}
}
}
}
}
- WHEN YOU GET TO THIS STEP CALL OVER AN INSTRUCTOR TO HELP YOU DOWNLOAD THE
Mecanim Warrior Anim
FOLDER FROM THIS REPOSITORY - From the
Assets Store
search forWarrior Pack Bundle 1
andStrong Knight
download and import. - In the
Project
window, deselect everything, then create a new folder calledAnimation
- Right click the
Animation
folder and selectCreate > Animation Controller
. Rename the Animation Controller toKnight Controller
- Create 3 parameters: bool:
IsPlayerNear
, trigger:Death
, trigger:Idle
- Drag the following animations into
Animation Controller
:
- death from Mecanim Warrior
- Attack1 from Brute Warrior Mecanim
- Idle from Brute Warrior Mecanim
- Run from Brute Warrior Mecanim
- WHEN YOU GET TO THIS STEP CALL OVER AN INSTRUCTOR TO HELP YOU WITH THE ANIMATIONS Your animation flow should look like. Right click on the button and select
Make Transition
. To create conditions click the arrow and a screen will appear in theInspector
window to create conditions:- Run => Attack (IsPlayerNear = true)
- Attack => Run (IsPlayerNear = false)
- Any State => Idle (Idle)
- Any State => Death (Death)
- Drag the
Knight
from the prefabs folder into the Scene. Next, drag theKnight Controller
to theKnight
object in theHierarchy
window. - Add a
Nav Mesh Agent
component to theKnight
- Open up the Navigation window by going to the top of the screen where File, Edit, Selection, Etc is located. Select
Window > Navigation
. The Navigation window will pop up in the same area asInspector
- In the
Bake
hit theBake
Button - With the
Knight
selected in the Hierarchy window, create a new script calledEnemyMovement
. Delete and replace the script with the following:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class EnemyMovement : MonoBehaviour {
private NavMeshAgent _nav;
private Transform _player;
void Start () {
_nav = GetComponent<NavMeshAgent> ();
_player = GameObject.FindGameObjectWithTag ("Player").transform;
}
void Update () {
_nav.SetDestination (_player.position);
}
}
- Click on
Player
in theHierarchy
window and change the Tag in theInspector
window toPlayer
- Add a
Capsule Collider
component to theKnight
game object and change these settings:
- Is Trigger is checked
- Y Center is 1
- Y Radius is 1.5
- Y Height is 1
Your screen should look like:
- Select the
Knight
in the Hierarchy window. Next, create a new script calledEnemyAttack
. Delete and replace with the following script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyAttack : MonoBehaviour {
Animator _animator;
GameObject _player;
void Awake () {
_player = GameObject.FindGameObjectWithTag ("Player");
_animator = GetComponent<Animator> ();
}
void OnTriggerEnter (Collider other) {
if (other.gameObject == _player) {
_animator.SetBool ("IsNearPlayer", true);
}
}
void OnTriggerExit (Collider other) {
if (other.gameObject == _player) {
_animator.SetBool ("IsNearPlayer", false);
}
}
}
Note: If the Knight stops attacking after the first time, check the attack animation clip and make sure Loop Time is checked.
- Add a
Mesh Collider
component to theKnight
game object. - Attach the
body
mesh that our Knight uses to theMesh Collider
- Click on
Attack1
in theAnimator
and the select theAnimation
tab to open it - Duplicate the
Attack1
by typingcmd+d
and rename it toKnight Attack
. Next, dragKnight Attack
into theAnimation
folder. - Back in the
Animator
tab for the Knight Animator Controller, switch theAttack1
state to use the newKnight Attack
animation clip instead of the previous one. - In the
EnemyAttack
script replace with the following:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyAttack : MonoBehaviour {
private Animator _animator;
private GameObject _player;
private bool _collidedWithPlayer;
void Awake () {
_player = GameObject.FindGameObjectWithTag ("Player");
_animator = GetComponent<Animator> ();
}
void OnTriggerEnter (Collider other) {
if (other.gameObject == _player) {
_animator.SetBool ("IsNearPlayer", true);
}
print ("enter trigger with _player");
}
void OnCollisionEnter (Collision other) {
if (other.gameObject == _player) {
_collidedWithPlayer = true;
}
print ("enter collided with _player");
}
void OnCollisionExit (Collision other) {
if (other.gameObject == _player) {
_collidedWithPlayer = false;
}
print ("exit collided with _player");
}
void OnTriggerExit (Collider other) {
if (other.gameObject == _player) {
_animator.SetBool ("IsNearPlayer", false);
}
print ("exit trigger with _player");
}
void Attack () {
if (_collidedWithPlayer) {
print ("player has been hit");
}
}
}
Attacking Enemies & Health System
- Select
Player
from theHierarchy
window and change changeDrag
in theInspector
window to 5. - Drag the
EnemyHealth
script toKnight
in theHierarchy
window, Next, open the script and replace with the following:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyHealth : MonoBehaviour {
public float Health = 10;
private Animator _animator;
void Start () {
_animator = GetComponent<Animator> ();
}
public void TakeDamage (float damage) {
if (Health <= 0) {
return;
}
Health -= damage;
if (Health <= 0) {
Death ();
}
}
private void Death () {
_animator.SetTrigger ("Death");
}
}
- In the
EnemyMovement
script replace with the following:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class EnemyMovement : MonoBehaviour
{
private NavMeshAgent _nav;
private Transform _player;
private EnemyHealth _enemyHealth;
void Start ()
{
_nav = GetComponent<NavMeshAgent>();
_player = GameObject.FindGameObjectWithTag("Player").transform;
_enemyHealth = GetComponent<EnemyHealth>();
}
void Update ()
{
if (_enemyHealth.Health > 0)
{
_nav.SetDestination(_player.position);
}
else
{
_nav.enabled = false;
}
}
}