Day 17: Shooting With A Gun In Unity

Josh Unity VR Development Leave a Comment

Welcome back to day 17 of our Unity development. Previously, we switched out our cube gun with a real gun model.

Today, we’re going to look at how to make a gun shoot in Unity.

Originally our code sends a raycast directly from our camera, however I’ve learned from Scouting Ninja that when we shoot a projectile it comes from the gun muzzle. We did something similar in the Survival Shooter back in the Unity tutorial.

Technically since we still don’t have any projectiles coming out, shooting straight from the middle is fine, but let’s say we want to shoot something out, what would we do?

Today, I’m going to make adjustments to fire a raycast from the muzzle of our gun and then go towards where we’re facing.

To do that, I found this great Unity video tutorial to follow: Let’s Try: Shooting with Raycasts.

A lot of the things that were talked about were mentioned in the Survival Shooter tutorial and I’ve also worked on a lot of similar parts my own… wow why didn’t I just follow this tutorial….

Anyways, I’m going to cover the things I learned and added. Let’s get to it!

Debug Raycast

Have you ever wondered what your raycast would look like if you visualized it? Great! Neither have I! Glad I’m not alone on this.

The first addition I want to make is a raycast debugger that visualizes your raycast in the scene tab when you play the game.

Like so:

See the green line on the right?

To attach this, all we need to do is add a line of code:

Debug.DrawRay()

Adding that into PlayerShootingController inside Update(), we’ll have something like this:

    void Update ()
    {
        _timer += Time.deltaTime;

        // Create a vector at the center of our camera's viewport
        Vector3 lineOrigin = _camera.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0.0f));

        // Draw a line in the Scene View  from the point lineOrigin in the direction of fpsCam.transform.forward * weaponRange, using the color green
        Debug.DrawRay(lineOrigin, _camera.transform.forward * Range, Color.green);

        // rest of code…
    }

The important part is that we’re using our camera to create a raycast with Debug.DrawRay()

We pass in very similar parameters as you might expect with a raycast except we don’t detect if we hit anything, instead we just create a green line.

Shooting from our Gun

If we want to shoot from the end of our gun, we need a location to start shooting from. We can’t use the gun itself, because we can’t exactly pinpoint the muzzle of the gun for something to be shot out.

We need to create something that indicates we shot something.

We’ll be using the example provided by the tutorial to make a Line Renderer to create a line from our gun muzzle to the target.

The first thing we need to do is some setup work.

Adding our Game Objects

The first thing we need to do is create the end of our gun:

I created a cube called gunEnd as a child MachingGun_00 to represent the end of the gun.

I used a cube so that it’s easier showing you the end of the gun, but I’ll be removing the Mesh Filter and Mesh Renderer so that it’ll just be an empty object.

The position/scale might differ for you than the ones I set above, but just mess around with it in Unity until you get something satisfactory.

In MachingGun_00 we’re going to create a Line Renderer component to represent a laser that we will shoot from our gun to our mouse click.

Make sure that the Line Renderer has a width of 0.1

With all of this in place, we’re ready to use these components for our script.

Writing the code to Fire Bullets from our Gun

We’re going to make some new additions to our PlayerShootingController to use our new pieces and shoot a laser from our gun endpoint.

Here’s the code:

using UnityEngine;
using System.Collections;

public class PlayerShootingController : MonoBehaviour
{
    public float Range = 100;
    public float ShootingDelay = 0.1f;
    public AudioClip ShotSfxClips;
    public Transform GunEndPoint;

    private Camera _camera;
    private ParticleSystem _particle;
    private LayerMask _shootableMask;
    private float _timer;
    private AudioSource _audioSource;
    private Animator _animator;
    private bool _isShooting;
    private bool _isReloading;
    private LineRenderer _lineRenderer;


    void Start () {
        _camera = Camera.main;
        _particle = GetComponentInChildren<ParticleSystem>();
        Cursor.lockState = CursorLockMode.Locked;
        _shootableMask = LayerMask.GetMask("Shootable");
        _timer = 0;
        SetupSound();
        _animator = GetComponent<Animator>();
        _isShooting = false;
        _isReloading = false;
        _lineRenderer = GetComponent<LineRenderer>();
    }
    
    void Update ()
    {
        _timer += Time.deltaTime;

        // Create a vector at the center of our camera's viewport
        Vector3 lineOrigin = _camera.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0.0f));

        // Draw a line in the Scene View  from the point lineOrigin in the direction of fpsCam.transform.forward * weaponRange, using the color green
        Debug.DrawRay(lineOrigin, _camera.transform.forward * Range, Color.green);

        if (Input.GetMouseButton(0) && _timer >= ShootingDelay && !_isReloading)
        {
            Shoot();
            if (!_isShooting)
            {
                TriggerShootingAnimation();
            }
        }
        else if (!Input.GetMouseButton(0))
        {
            StopShooting();
            if (_isShooting)
            {
                TriggerShootingAnimation();
            }
        }

        if (Input.GetKeyDown(KeyCode.R))
        {
            StartReloading();
        }
    }

    private void StartReloading()
    {
        _animator.SetTrigger("DoReload");
        StopShooting();
        _isShooting = false;
        _isReloading = true;
    }

    private void TriggerShootingAnimation()
    {
        _isShooting = !_isShooting;
        _animator.SetTrigger("Shoot");
    }

    private void StopShooting()
    {
        _audioSource.Stop();
        _particle.Stop();
    }

    private void Shoot()
    {
        _timer = 0;
        Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit = new RaycastHit();
        _audioSource.Play();
        _particle.Play();

        _lineRenderer.SetPosition(0, GunEndPoint.position);
        StartCoroutine(FireLine());
        if (Physics.Raycast(ray, out hit, Range, _shootableMask))
        {
            print("hit " + hit.collider.gameObject);
            _lineRenderer.SetPosition(1, hit.point);
            EnemyHealth health = hit.collider.GetComponent<EnemyHealth>();
            EnemyMovement enemyMovement = hit.collider.GetComponent<EnemyMovement>();
            if (enemyMovement != null)
            {
                enemyMovement.KnockBack();
            }
            if (health != null)
            {
                health.TakeDamage(1);
            }
        }
        else
        {
            _lineRenderer.SetPosition(1, ray.GetPoint(Range));
        }
    }

    private IEnumerator FireLine()
    {
        _lineRenderer.enabled = true;

        yield return ShootingDelay - 0.05f;

        _lineRenderer.enabled = false;
    }

    // called from the animation finished
    public void ReloadFinish()
    {
        _isReloading = false;
    }

    private void SetupSound()
    {
        _audioSource = gameObject.AddComponent<AudioSource>();
        _audioSource.volume = 0.2f;
        _audioSource.clip = ShotSfxClips;
    }
}

Here’s the flow for our code:

  1. We have two new variables GunEndPoint and _lineRenderer that we instantiate in Start()
  2. The way LineRenderer works is that you set 2 points to form the line you want to make. The 1st point is at the tip of our gun. The second point depends on our raycast.
  3. If our raycast hits something, we want to set whatever we hit as our 2nd point in our line.
  4. If our raycast doesn’t hit anything, we just make the player’s shot go towards the center of our screen by the length of our bullet
  5. Finally, we create a Coroutine that calls FireLine() that enables our LineRenderer component so we can it, wait about 0.05 seconds and then disable it. Doing this gives us the illusion that we’re shooting something.

After we add in our code, make sure that we drag our gunEnd game object into the appropriate slot in our PlayerShootingController script and now we can shoot purple lasers!

Conclusion

So that’s it for Day 17.

Today we learned how to use raycast debug so that we can see where our raycast will end up and we used a line renderer to shoot lasers from the end of our gun to where the player is standing at.

It’s very exciting to say that I’ve come a long way since I first started doing the Survival Shooter tutorial, especially since I was just copying code.

Now I’m fluent enough in Unity that I can write some code myself!

With all that being said, it’s getting late so I’m going to sleep. I’ll see you all in day 18 where I’ll be setting up the UI for the game!

Day 16 | 100 Days of VR | Day 18

Home

Subscribe To Our Weekly Newsletter!
Like these coding articles? Join my mailing list for the latest updates and influence the code that I write!
We hate spam. Your email address will not be sold or shared with anyone else.

Leave a Reply

Your email address will not be published. Required fields are marked *