blog post cover
First person shooting in Unity using raycast - 23.08.2019
Have you ever wanted to build your own first-person shooter game?
You got the character and movement setup in Unity but need some kind of shooting functionality?
This can easily be accomplished using raycasts.

Step 1:
- Create a new script called AttackController.cs
- Add the script to your Unity character. (TIP: if you don't have a script for movement, 
                                                                        download Unity's standard assets plugin at the store
                                                                        and drag the "RigidBodyFPSController.prefab" into the scene.

Step 2:
- Create another script called Weapon.cs
- Add a game object to the scene which is going to represent a weapon.
- Add Weapon.cs as a component to the game object.
- Add a 3D cube inside the weapon's game object and scale it to the size of the gun barrel.
- Move the 3D cube to the point where the bullet is supposed to be fired from.
- Remove the Mesh- and Mesh filter component from the cube.
- Rename the cube to "GunBarrel"

Step 3:
- Open Weapon.cs
- Add 5 public variables at the top of the class:
public class Weapon : MonoBehaviour
{
public float damage;
// This variable determines
// how fast the weapon can shoot
public float fireRate;
// This variable determines
// how far the bullet fly when fired
public float fireLength;
// This variable determines
// how fast the weapon reload
public float reloadTime;
// A reference to the
// weapon's gun barrel game object
public Transform gunBarrel;
}
- Click on the weapon's game object inside Unity and set up the properties of your weapon.
- Fx damage=10, fireRate=0.5 and fireLength=10
- Drag the gun barrel into the 'gunBarrel' field.

Step 4:
- Open AttackController.cs
- Add the following variables:
public class AttackController : MonoBehaviour
{
// The current weapon
// our player is using
public Weapon currentWeapon;
// Used to check if
// the player wants to shoot
private bool shoot;
// Used to check if the player
// should wait before shooting another bullet
private bool isShooting;
// Used to check if the player is reloading
private bool isReloading;
}
- Add the following inside an 'Update()' function and add a new function called 'Shoot()':
public class AttackController : MonoBehaviour
{
// ...

void Update () {

// If the player click the fire button
// (mouse left click) we set 'fire=true'
// and if the player release the fire
// button we set 'shoot=false'
// and last if shoot is true we run
// our 'Shoot()' function
// By doing so can we make the player shoot
// as long the fire button haven't been
// released.
if (Input.GetButtonDown("Fire1")) {
shoot = true;
}

if (Input.GetButtonUp("Fire1")) {
shoot = false;
}

if (shoot) {
Shoot();
}
}

void Shoot() {
}
}
- Move down to 'Shoot()' and add the following:
public class AttackController : MonoBehaviour
{
// ...

void Shoot() {
// Return until the fire cooldown
// is gone and if we don't have a weapon
if (isShooting || currentWeapon == null) {
return;
}

// If a weapon doesn't have a fire
// rate we assume it's a non-automatic weapon
// and we set 'shoot=false' to implement
// a 'tap-to-shoot' functionality
if (currentWeapon.fireRate == 0) {
shoot = false;
}

// We set 'isShooting=true' and run
// FireCooldown as an IEnumerator to
// use the WaitForSeconds class
isShooting = true;
StartCoroutine(FireCooldown());
}

IEnumerator FireCooldown() {
// The weapon will be ready to fire
// another bullet when 'isShooting=false'
yield return new WaitForSeconds(currentWeapon.fireRate);
isShooting = false;
}
}
- Move back to the top of the class and add a private RaycastHit called hit:
public class AttackController : MonoBehaviour
{
// The current weapon our
// player is using
public Weapon currentWeapon;
// Used to check if the
// player wants to shoot
private bool shoot;
// Used to check if the player
// should wait before shooting
// another bullet
private bool isShooting;
// Used to check if the player
// is reloading
private bool isReloading;
// This variable will store
// information about raycast hits.
private RaycastHit hit;

// ...
}
- Move back to 'Shoot()' and add the following:
public class AttackController : MonoBehaviour
{
// ...
void Shoot() {
// Return until the fire cooldown
// is gone and if we don't have a weapon
if (isShooting || currentWeapon == null) {
return;
}

// If a weapon doesn't have a fire rate we
// assume it's a non-automatic weapon
// and we set 'shoot=false' to implement
// a 'tap-to-shoot' functionality
if (currentWeapon.fireRate == 0) {
shoot = false;
}

// We set up a Ray which moves towards
// the center of the main camera
Ray ray = Camera.main.ViewportPointToRay(new Vector2(.5f, .5f));
// We set the origin of the ray to gun
// barrel position so it doesn't start
// at the camera position
ray.origin = currentWeapon.gunBarrel.position;
// We fire the raycast in an 'if' statement
// and pass the 'ray', our RaycastHit variable
// and we limit the distance of the raycast
// to match the current weapon's fire length
if (Physics.Raycast(ray, out hit, currentWeapon.fireLength))
{
// if the raycast it's a game object we
// check if it has the player tag
if (hit.collider.CompareTag("Player")) {
// and finally if it is a player, we want
// to add some damage to him/her
// I won't explain how to build a health
// controller class in this tutorial,
// but you would be able to access one
// like this if the player had one:
// var healthCtrl = hit.collider.GetComponent<HealthController>();
// healthCtrl.ApplyDamage(currentWeapon.damage);
}
}

// We set 'isShooting=true' and run FireCooldown
// as an IEnumerator to use the WaitForSeconds class
isShooting = true;
StartCoroutine(FireCooldown());
}

// ...
}
- That's it! You now have a very simple "weapon attack system" which is able to shoot and detect if it hits another player.

Step 5:
- We also want to add a reload functionality to our weapons, so that we have to reload some times between shooting.
- To simplify this in the tutorial, we will create a system where all weapons use the same type of bullets.
- Add the following above our private variables and add a 'Start()' function above 'Update()' where you set 'currentExtraBullets=maxExtraBullets':
public class AttackController : MonoBehaviour
{
// The current weapon our
// player is using
public Weapon currentWeapon;
// The number of bullets the
// player has available for reloading
public int currentExtraBullets;
// The number of extra bullets
// the player CAN have
public int maxExtraBullets = 100;
// Used to check if the
// player wants to shoot
private bool shoot;
// Used to check if the player should
// wait before shooting another bullet
private bool isShooting;
// Used to check if the
// player is reloading
private bool isReloading;
// This variable will store
// information about raycast hits.
private RaycastHit hit;

void Start() {
currentExtraBullets = maxExtraBullets;
}

// ...
}
- Open Weapon.cs and add the following:
public class Weapon : MonoBehaviour
{
public float damage;
// This variable determines
// how fast the weapon can shoot
public float fireRate;
// This variable determines
// how far the bullet fly when fired
public float fireLength;
// This variable determines
// how fast the weapon reload
public float reloadTime;
// A reference to the weapon's
// gun barrel game object
public Transform gunBarrel;
// The number of bullets
// this weapon has
public int currentBullets;
// The number of bullets
// this weapon CAN have
public int maxBullets = 30;

void Start() {
// We set the weapon to
// start with a full magazin
currentBullets = maxBullets;
}
}
- Open AttackController.cs and move down to 'Shoot()' and add the following in the if statement at the top of the function:
public class AttackController : MonoBehaviour
{
// ...
void Shoot() {
// Return until the fire cooldown
// is gone and if we don't have a weapon
// OR
// if the player is reloading the weapon
// or the weapon's magazin is empty
if (isShooting || currentWeapon == null ||
isReloading || currentWeapon != null &&
currentWeapon.currentBullets <= 0) {
return;
}
// remove a bullet from
// the weapon's magazin
currentWeapon.currentBullets -= 1;

// ...
}

// ...
}
- At this point do we use 1 bullet every time we shoot, so we only need to implement a new function to start reloading
- Move to the 'Update()' function and add these new lines below our 'if(shoot)' statement:
public class AttackController : MonoBehaviour
{
// ...

void Update () {

// ...
// if we click the 'R' button on the
// keyboard we run our reload function
if (Input.GetKeyDown(KeyCode.R)) {
Reload();
}
}
// ...

}
- Move down below 'Shoot()' and 'FireCooldown()' and add two new functions called 'Reload()' and 'ReloadCooldown()'
- And add the following lines:
public class AttackController : MonoBehaviour
{
// ...
void Reload() {
// return the function if
// the player already is reloading
// OR the player don't have a weapon
// OR the player have a weapon
// that already is full
// OR the player don't have any
// extra bullets to insert
// into the weapon's magazin
if (isReloading || currentWeapon == null ||
currentWeapon != null &&
currentWeapon.currentBullets >=
currentWeapon.maxBullets ||
currentExtraBullets <= 0) {
return;
}

// The first thing we need to know is
// how many bullets we can
// insert into the player's weapon
var diff = currentWeapon.maxBullets -
currentWeapon.currentBullets;

// Then we check if the number of bullets
// missing is less or equal to
// the number our player has
// we know if this is true, the
// player must have more or the
// exact number of extra bullets as
// the current weapon is missing
// and we can therefore just
// set the number of bullets on the weapon
// to the max number of bullets
// the weapon's magazin can store
if (diff <= currentExtraBullets) {
currentExtraBullets -= diff;
currentWeapon.currentBullets = currentWeapon.maxBullets;
} else {
// if the player doesn't have more
// or the exact number of bullets
// it's either 0 or greater
// and we can safely add the
// current number of extra
// bullets to our weapon and set
// the player's number
// of extra bullets to 0
currentWeapon.currentBullets += currentExtraBullets;
currentExtraBullets = 0;
}

// And then we do the exact same
// thing here as in the Shoot() function
// We could just have added a
// function to handle this,
// but it might be easier
// to implement other functionality
// to the reload or shoot function
// if you keep the seperated
isReloading = true;
StartCoroutine(ReloadCooldown());
}

IEnumerator ReloadCooldown() {
yield return new WaitForSeconds(currentWeapon.reloadTime);
isReloading = false;
}
}
- And that's everything for the reloading functionality.
- The last thing we need is to move the weapon's game object inside the character's at a position where you want him to hold it
and drag the reference of the weapon into the AttackController component on the player's game object.

Find the finished scripts below:
Weapon.cs:
public class Weapon : MonoBehaviour
{
public float damage;
// This variable determines
// how fast the weapon can shoot
public float fireRate;
// This variable determines
// how far the bullet fly when fired
public float fireLength;
// This variable determines
// how fast the weapon reload
public float reloadTime;
// A reference to the weapon's
// gun barrel game object
public Transform gunBarrel;
// The number of bullets
// this weapon has
public int currentBullets;
// The number of bullets
// this weapon CAN have
public int maxBullets = 30;

void Start() {
// We set the weapon to
// start with a full magazin
currentBullets = maxBullets;
}
}

- AttackController.cs:
public class AttackController : MonoBehaviour
{
// The current weapon our
// player is using
public Weapon currentWeapon;
// The number of bullets the
// player has available for reloading
public int currentExtraBullets;
// The number of extra
// bullets the player CAN have
public int maxExtraBullets = 100;
// Used to check if
// the player wants to shoot
private bool shoot;
// Used to check if the
// player should wait
// before shooting another bullet
private bool isShooting;
// Used to check if
// the player is reloading
private bool isReloading;
// This variable will store
// information about raycast hits.
private RaycastHit hit;

void Start() {
currentExtraBullets = maxExtraBullets;
}

void Update () {

// If the player click the fire
// button (mouse left click)
// we set 'fire=true'
// and if the player release
// the fire button we set 'shoot=false'
// and last if shoot is true
// we run our 'Shoot()' function
// By doing so can we make the
// player shoot as long the
// fire button haven't been released.
if (Input.GetButtonDown("Fire1")) {
shoot = true;
}

if (Input.GetButtonUp("Fire1")) {
shoot = false;
}

if (shoot) {
Shoot();
}

// if we click the 'R' button
// on the keyboard we run our reload function
if (Input.GetKeyDown(KeyCode.R)) {
Reload();
}
}

void Shoot() {
// Return until the fire cooldown
// is gone and if we don't have a weapon
// OR
// if the player is reloading
// the weapon or the weapon's magazin is empty
if (isShooting || currentWeapon == null ||
isReloading || currentWeapon != null &&
currentWeapon.currentBullets <= 0) {
return;
}
// remove a bullet from the weapon's magazin
currentWeapon.currentBullets -= 1;

// If a weapon doesn't have a
// fire rate we assume it's
// a non-automatic weapon
// and we set 'shoot=false'
// to implement a 'tap-to-shoot' functionality
if (currentWeapon.fireRate == 0) {
shoot = false;
}

// We set up a Ray which moves
// towards the center of the main camera
Ray ray = Camera.main.ViewportPointToRay(new Vector2(.5f, .5f));
// We set the origin of the ray
// to gun barrel position so it
// doesn't start at the camera position
ray.origin = currentWeapon.gunBarrel.position;
// We fire the raycast in an 'if'
// statement and pass the 'ray',
// our RaycastHit variable
// and we limit the distance of
// the raycast to match the
// current weapon's fire length
if (Physics.Raycast(ray, out hit, currentWeapon.fireLength))
{
// if the raycast it's a game
// object we check if it has the player tag
if (hit.collider.CompareTag("Player")) {
// and finally if it is a player,
// we want to add some damage to him/her
// I won't explain how to build a
// health controller class in this tutorial,
// but you would be able to access
// one like this if the player had one:
// var healthCtrl = hit.collider.GetComponent<HealthController>();
// healthCtrl.ApplyDamage(currentWeapon.damage);
}
}

// We set 'isShooting=true' and run FireCooldown
// as an IEnumerator to use the WaitForSeconds class
isShooting = true;
StartCoroutine(FireCooldown());
}

IEnumerator FireCooldown() {
// The weapon will be ready to fire
// another bullet when 'isShooting=false'
yield return new WaitForSeconds(currentWeapon.fireRate);
isShooting = false;
}

void Reload() {
// return the function if the
// player already is reloading
// OR the player don't have a weapon
// OR the player have a
// weapon that already is full
// OR the player don't have
// any extra bullets to
// insert into the weapon's magazin
if (isReloading || currentWeapon == null ||
currentWeapon != null && currentWeapon.currentBullets
>= currentWeapon.maxBullets ||
currentExtraBullets <= 0) {
return;
}

// The first thing we need to know
// is how many bullets we can
// insert into the player's weapon
var diff = currentWeapon.maxBullets -
currentWeapon.currentBullets;

// Then we check if the number of
// bullets missing is less or
// equal to the number our player has
// we know if this is true, the player
// must have more or the exact
// number of extra bullets as
// the current weapon is missing and
// we can therefore just set the
// number of bullets on the weapon
// to the max number of bullets
// the weapon's magazin can store
if (diff <= currentExtraBullets) {
currentExtraBullets -= diff;
currentWeapon.currentBullets = currentWeapon.maxBullets;
} else {
// if the player doesn't have more
// or the exact number of
// bullets it's either 0 or greater
// and we can safely add the
// current number of extra
// bullets to our weapon and set
// the player's number
// of extra bullets to 0
currentWeapon.currentBullets += currentExtraBullets;
currentExtraBullets = 0;
}

// And then we do the exact same
// thing here as in the Shoot() function
// We could just have added a function
// to handle this, but it might be easier
// to implement other functionality to
// the reload or shoot function
// if you keep the seperated
isReloading = true;
StartCoroutine(ReloadCooldown());
}

IEnumerator ReloadCooldown() {
yield return new WaitForSeconds(currentWeapon.reloadTime);
isReloading = false;
}
}

Author: 
Nicolai B. Andersen
App Store badge US-UK Get it on Google Play
V. 1.8