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. 

If you want to learn a simple version of this script, check out this video tutorial: 
https://youtu.be/wrwHAgRez3g

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;
}
}


This tutorial is not sponsored by or affiliated with Unity Technologies or its affiliates. “Unity” is a trademark or registered trademark of Unity Technologies or its affiliates in the U.S. and elsewhere.
App Store badge US-UK Get it on Google Play
V. 1.9