Shooting multiple raycasts to the exact same location each time

Hi there,

I’m trying to add a shotgun in my game by adding multiple raycasts at the same time but I’m having trouble offsetting them. I have found several solutions for shooting raycasts in random locations but that’s not what I want, I want the spread to be exactly the same each time. I.e. the first bullet in the exact centre of the screen, 2nd bullet just above, 3rd just below, 4th to the left, and 5th to the right.

This is the code for one ray cast:

public float fireRate = 1.0f;
    private float nextFire;
    public int shotgunForce = 50;
    public int shotgunDamage = 10;
  
    public Transform gunEnd;
    public Camera fpsCam;
    public ParticleSystem muzzleFlash;
    public GameObject impactEffect;

    void Update()
    {
        // Check if the player has pressed the fire button and if enough time has elapsed since they last fired
        if (Input.GetKey(KeyCode.Mouse0) && Time.time > nextFire)
        {
            // Update the time when our player can fire next
            nextFire = Time.time + fireRate;
            PlayerFireWeapon();
        }
        if (Input.GetKey(KeyCode.Mouse1))
            PlayerFireAlternative();
    }

    void PlayerFireWeapon()
    {
            FindObjectOfType<AudioManager>().Play("ShotgunFire");
            muzzleFlash.Play();
            // Declare a raycast hit to store information about what our raycast has hit
            RaycastHit hit;
            RaycastHit hit1;

            Vector3 rayOrigin = fpsCam.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0.0f));

            /* ------------------FIRST SHOT--------------------- */
            // Check if our raycast has hit anything
            if (Physics.Raycast(rayOrigin, fpsCam.transform.forward, out hit, Mathf.Infinity))
            {
                // if the object hit has as target script attached, run the take damage function
                Target target = hit.transform.GetComponent<Target>();
                if (target != null)
                {
                    target.takeDamage(shotgunDamage);
                }
                GameObject impactEffectInstance = Instantiate(impactEffect, hit.point, Quaternion.LookRotation(hit.normal));
                Destroy(impactEffectInstance, 1f);
            }

            // Check if the object we hit has a rigidbody attached
            if (hit.rigidbody != null)
            {
                // Add force to the rigidbody we hit, in the direction from which it was hit
                hit.rigidbody.AddForce(-hit.normal * shotgunForce);
            }

Then I have added another raycast by delcaring another Raycasthit varialbe ‘hit1’ then copy / pasting the same shooting code and editing the Physics.Raycast function to offset it, but I can’t work it out. This is what I’m trying now for the 2nd raycast:

if (Physics.Raycast(fpsCam.transform.up * 2.5f, fpsCam.transform.forward, out hit1, Mathf.Infinity))

And this seems to kinda work but it’s not always in the same place? I think I’m in the right direction but need some help please!

Thanks in advance!

Why you dont do more Raycast with a different offset?
In this case if the shot hit in part, you can have less damage.

1 Like

You probably want to shoot using Camera.ViewportPointToRay. What you have looks like it would probably work for the first raycast, but then your second one doesn’t use the same method for calculating the ray origin.

Since the center of the viewport is (0.5f, 0.5f), your second raycast, if you wanted it just above the last, would be like (0.5f, 0.55f) or some small offset vertically.

2 Likes

I just realised though looking at your post you wanted something a way more specific than this but the tutorial should help you regardless, @LiterallyJeff seems to have a handle on it anyway.

1 Like

I’ve just tried making more raycast origins for each like so:

Vector3 rayOrigin1 = fpsCam.ViewportToWorldPoint(new Vector3(0.5f, 0.6f, 0.0f));
            Vector3 rayOrigin2 = fpsCam.ViewportToWorldPoint(new Vector3(0.5f, 0.4f, 0.0f));
            Vector3 rayOrigin3 = fpsCam.ViewportToWorldPoint(new Vector3(0.6f, 0.5f, 0.0f));
            Vector3 rayOrigin4 = fpsCam.ViewportToWorldPoint(new Vector3(0.4f, 0.5f, 0.0f));

but they all seem to go to the exact same point still… which makes no sense to me!

BUT I have also just tried using ‘ViewportPointToRay’ as you suggested and it seems to work using these:

Ray rayOrigin1 = fpsCam.ViewportPointToRay(new Vector3(0.5f, 0.55f, 0.0f));
            Ray rayOrigin2 = fpsCam.ViewportPointToRay(new Vector3(0.5f, 0.45f, 0.0f));
            Ray rayOrigin3 = fpsCam.ViewportPointToRay(new Vector3(0.55f, 0.5f, 0.0f));
            Ray rayOrigin4 = fpsCam.ViewportPointToRay(new Vector3(0.45f, 0.5f, 0.0f));

Will do some further testing and report back any issues :slight_smile: Thanks for that!

Also I have one more question, the code is a bit messy as it requires a lot of duplicated code for the rays, is there anyway I can improve on this?

Yeah that video was useful for a random spread shotgun, but not quite what I was after, thanks though :slight_smile:

If you have duplicate code, make a function and pass in the things that change. Feel free to post your script and I’ll see if I can suggest more ways to clean it up.

I’m playing around with it, trying to use an array of Rays and then a foreach loop but can’t quite work it out:

var shots = new Ray[5];
        shots[0] = fpsCam.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0.0f));
        shots[1] = fpsCam.ViewportPointToRay(new Vector3(0.5f, 0.55f, 0.0f));
        shots[2] = fpsCam.ViewportPointToRay(new Vector3(0.5f, 0.45f, 0.0f));
        shots[3] = fpsCam.ViewportPointToRay(new Vector3(0.53f, 0.5f, 0.0f));
        shots[4] = fpsCam.ViewportPointToRay(new Vector3(0.47f, 0.5f, 0.0f));

        Ray currentShot = shots[0];
        Ray rayOrigin = fpsCam.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0.0f));
        Ray rayOrigin1 = fpsCam.ViewportPointToRay(new Vector3(0.5f, 0.55f, 0.0f));
        Ray rayOrigin2 = fpsCam.ViewportPointToRay(new Vector3(0.5f, 0.45f, 0.0f));
        Ray rayOrigin3 = fpsCam.ViewportPointToRay(new Vector3(0.53f, 0.5f, 0.0f));
        Ray rayOrigin4 = fpsCam.ViewportPointToRay(new Vector3(0.47f, 0.5f, 0.0f));
     
        foreach (var shot in shots)
        {
            if (Physics.Raycast(shots[0], out hit, Mathf.Infinity))
            {
                // if the object hit has as target script attached, run the take damage function
                Target target = hit.transform.GetComponent<Target>();
                if (target != null)
                {
                    target.takeDamage(shotgunDamage);
                }

                GameObject impactEffectInstance = Instantiate(impactEffect, hit.point, Quaternion.LookRotation(hit.normal));
                Destroy(impactEffectInstance, 1f);
            }

I don’t think it’ll work like that. Also trying with a function as you suggested like this:

void shotgunShot(Ray rayOrigin, RaycastHit hit)
    {
     
    }

But again I can’t quite work out how to implement it, i.e. how to pass the Ray and RaycastHit into the function

Sorry I’m quite new to this! (as you can probably tell)

Here’s my full script minus the stuff I’m testing, maybe you can let me know what you think :slight_smile:

using System.Collections.Generic;
using UnityEngine;

public class Shotgun : MonoBehaviour
{
    public float fireRate = 1.0f;
    private float nextFire;
    public int shotgunForce = 50;
    public int shotgunDamage = 10;
 
    public Transform gunEnd;
    public Camera fpsCam;
    public ParticleSystem muzzleFlash;
    public GameObject impactEffect;

    void Update()
    {
        // Check if the player has pressed the fire button and if enough time has elapsed since they last fired
        if (Input.GetKey(KeyCode.Mouse0) && Time.time > nextFire)
        {
            // Update the time when our player can fire next
            nextFire = Time.time + fireRate;
            PlayerFireWeapon();
        }
        if (Input.GetKey(KeyCode.Mouse1))
            PlayerFireAlternative();
    }

    void PlayerFireWeapon()
    {
        FindObjectOfType<AudioManager>().Play("ShotgunFire");
        muzzleFlash.Play();
        // Declare a raycast hit to store information about what our raycast has hit
        RaycastHit hit;
        RaycastHit hit1;
        RaycastHit hit2;
        RaycastHit hit3;
        RaycastHit hit4;

        Ray rayOrigin = fpsCam.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0.0f));
        Ray rayOrigin1 = fpsCam.ViewportPointToRay(new Vector3(0.5f, 0.55f, 0.0f));
        Ray rayOrigin2 = fpsCam.ViewportPointToRay(new Vector3(0.5f, 0.45f, 0.0f));
        Ray rayOrigin3 = fpsCam.ViewportPointToRay(new Vector3(0.53f, 0.5f, 0.0f));
        Ray rayOrigin4 = fpsCam.ViewportPointToRay(new Vector3(0.47f, 0.5f, 0.0f));

        /* ------------------FIRST SHOT--------------------- */
        // Check if our raycast has hit anything
        if (Physics.Raycast(rayOrigin, out hit, Mathf.Infinity))
            {
                // if the object hit has as target script attached, run the take damage function
                Target target = hit.transform.GetComponent<Target>();
                if (target != null)
                {
                    target.takeDamage(shotgunDamage);
                }
             
                GameObject impactEffectInstance = Instantiate(impactEffect, hit.point, Quaternion.LookRotation(hit.normal));
                Destroy(impactEffectInstance, 1f);
            }

        // Check if the object we hit has a rigidbody attached
        if (hit.rigidbody != null)
        {
            // Add force to the rigidbody we hit, in the direction from which it was hit
            hit.rigidbody.AddForce(-hit.normal * shotgunForce);
        }

        /* ------------------SECOND SHOT--------------------- */
        // Check if our raycast has hit anything
        if (Physics.Raycast(rayOrigin1, out hit1, Mathf.Infinity))
        {
            // if the object hit has as target script attached, run the take damage function
            Target target = hit1.transform.GetComponent<Target>();
            if (target != null)
            {
                target.takeDamage(shotgunDamage);
            }
            GameObject impactEffectInstance = Instantiate(impactEffect, hit1.point, Quaternion.LookRotation(hit1.normal));
            Destroy(impactEffectInstance, 1f);
        }
     
        // Check if the object we hit has a rigidbody attached
        if (hit1.rigidbody != null)
        {
            // Add force to the rigidbody we hit, in the direction from which it was hit
            hit1.rigidbody.AddForce(-hit1.normal * shotgunForce);
        }
         
        /* ------------------THIRD SHOT---------------------*/
        // Check if our raycast has hit anything
        if (Physics.Raycast(rayOrigin2, out hit2, Mathf.Infinity))
        {
            // if the object hit has as target script attached, run the take damage function
            Target target = hit2.transform.GetComponent<Target>();
            if (target != null)
            {
                target.takeDamage(shotgunDamage);
            }
            GameObject impactEffectInstance = Instantiate(impactEffect, hit2.point, Quaternion.LookRotation(hit2.normal));
            Destroy(impactEffectInstance, 1f);
        }
     
        // Check if the object we hit has a rigidbody attached
        if (hit2.rigidbody != null)
        {
            // Add force to the rigidbody we hit, in the direction from which it was hit
            hit2.rigidbody.AddForce(-hit2.normal * shotgunForce);
        }

        /* ------------------FOURTH SHOT---------------------*/
        // Check if our raycast has hit anything
        if (Physics.Raycast(rayOrigin3, out hit3, Mathf.Infinity))
        {
            // if the object hit has as target script attached, run the take damage function
            Target target = hit3.transform.GetComponent<Target>();
            if (target != null)
            {
                target.takeDamage(shotgunDamage);
            }
            GameObject impactEffectInstance = Instantiate(impactEffect, hit3.point, Quaternion.LookRotation(hit3.normal));
            Destroy(impactEffectInstance, 1f);
        }
     
        // Check if the object we hit has a rigidbody attached
        if (hit3.rigidbody != null)
        {
        // Add force to the rigidbody we hit, in the direction from which it was hit
            hit3.rigidbody.AddForce(-hit3.normal * shotgunForce);
        }

        /* ------------------FIFTH SHOT---------------------*/
        // Check if our raycast has hit anything
        if (Physics.Raycast(rayOrigin4, out hit4, Mathf.Infinity))
        {
            // if the object hit has as target script attached, run the take damage function
            Target target = hit4.transform.GetComponent<Target>();
            if (target != null)
            {
                target.takeDamage(shotgunDamage);
            }
            GameObject impactEffectInstance = Instantiate(impactEffect, hit4.point, Quaternion.LookRotation(hit4.normal));
            Destroy(impactEffectInstance, 1f);
        }
     
        // Check if the object we hit has a rigidbody attached
        if (hit4.rigidbody != null)
        {
        // Add force to the rigidbody we hit, in the direction from which it was hit
            hit4.rigidbody.AddForce(-hit4.normal * shotgunForce);
        }
    }

    void PlayerFireAlternative()
    {
        Debug.Log("You fired your weapon's alternate fire");
    }
}

Take a look through this:

using System.Collections.Generic;
using UnityEngine;

public class Shotgun : MonoBehaviour
{
    public float fireRate = 1.0f;
    private float nextFire;
    public int shotgunForce = 50;
    public int shotgunDamage = 10;

    public Transform gunEnd;
    public Camera fpsCam;
    public ParticleSystem muzzleFlash;
    public GameObject impactEffect;

    private List<Vector3> bulletOffsets;

    private void Awake()
    {
        bulletOffsets = new List<Vector3>()
        {
            new Vector3(0.5f, 0.5f, 0.0f),
            new Vector3(0.5f, 0.55f, 0.0f),
            new Vector3(0.5f, 0.45f, 0.0f),
            new Vector3(0.53f, 0.5f, 0.0f),
            new Vector3(0.47f, 0.5f, 0.0f),
        };
    }


    private void Update()
    {
        // Check if the player has pressed the fire button and if enough time has elapsed since they last fired
        if(Input.GetKey(KeyCode.Mouse0) && Time.time > nextFire)
        {
            // Update the time when our player can fire next
            nextFire = Time.time + fireRate;
            PlayerFireWeapon();
        }

        if(Input.GetKey(KeyCode.Mouse1))
        {
            PlayerFireAlternative();
        }
    }

    void PlayerFireWeapon()
    {
        FindObjectOfType<AudioManager>().Play("ShotgunFire");
        muzzleFlash.Play();

        // make a new list of rays
        List<Ray> rays = new List<Ray>();

        // create the rays based on the configured offsets
        foreach(Vector3 bulletOffset in bulletOffsets)
        {
            rays.Add(fpsCam.ViewportPointToRay(bulletOffset));
        }

        // fire the rays
        foreach(Ray ray in rays)
        {
            Fire(ray);
        }
    }

    // assuming all the bullets will do the same thing
    private void Fire(Ray ray)
    {
        RaycastHit hit;
        // Check if our raycast has hit anything
        if(Physics.Raycast(ray, out hit, Mathf.Infinity))
        {
            // if the object hit has as target script attached, run the take damage function
            Target target = hit.transform.GetComponent<Target>();
            if(target != null)
            {
                target.takeDamage(shotgunDamage);
            }

            // Check if the object we hit has a rigidbody attached
            if(hit.rigidbody != null)
            {
                // Add force to the rigidbody we hit, in the direction from which it was hit
                hit.rigidbody.AddForce(-hit.normal * shotgunForce);
            }

            GameObject impactEffectInstance = Instantiate(impactEffect, hit.point, Quaternion.LookRotation(hit.normal));
            Destroy(impactEffectInstance, 1f);
        }
    }

    void PlayerFireAlternative()
    {
        Debug.Log("You fired your weapon's alternate fire");
    }
}

A good rule of thumb is that if you have duplicate code or find yourself numbering your variables, you should probably be using a list or array, loops, and functions.

1 Like

That’s awesome thank you. Looks a lot cleaner now!

1 Like