Can't create fixed shotgun spread

I have been trying to make bullets spread out in a non-random way when shooting, similar to TF2’s competitive mode, where all the bullets go in 3 straight horizontal lines, and form a square. (here is an example of it) With my current script, they all go in straight lines when the player is looking at a 90 degree angle, but spread diagonally when looking at a bigger or smaller angle. (the bullets go in a vertical line at 180 degrees)

Here is an example of the problem : Imgur: The magic of the Internet The main holdup seems to be that my script doesn’t properly take account of the player’s y rotation when spreading out the bullets. No matter what I try, it never seems to…

The script :

 public GameObject bullethole;
    private float NextTimeToFire = 0f;
    List<Vector3> s = new List<Vector3>();
    public int currentAmmo = 4;
    public float firerate = 0.625f;
    public float reloadTime = 0.6f;
    public float timer;
    public float damage = 6f;
    public Vector3 direction;
    public Vector3 spread;
    public float range;
    public Camera cam;

    void Start()
    {
    //this is a list of predetermined spread positions
        s.Add(new Vector3(0, 0));
        s.Add(new Vector3(0, 0.05f));
        s.Add(new Vector3(0, -0.05f));
        s.Add(new Vector3(0.05f, 0));
        s.Add(new Vector3(0.05f, 0.05f));
        s.Add(new Vector3(0.05f, -0.05f));
        s.Add(new Vector3(-0.05f, 0, 0));
        s.Add(new Vector3(-0.05f, 0.05f));
        s.Add(new Vector3(-0.05f, -0.05f));
    }

    void Update()
    {
        if (currentAmmo == 0)
        {
            timer += Time.deltaTime;

            if (timer >= reloadTime)
            {
                timer = 0;
                currentAmmo = 4;
            }
        }

        if (Input.GetButtonDown("Fire1") && currentAmmo != 0 && Time.time >= NextTimeToFire)
        {
            NextTimeToFire = Time.time + firerate;
            for (int i = 0; i < s.Count; i++)
            {
          //for each predetermined spread position in the list, shoot
                RaycastHit hit;
                spread = transform.TransformDirection(s[i]);
                Vector3 direction = spread + cam.transform.forward;
          //convert the spread to world space and add it to the look direction

                if (Physics.Raycast(cam.transform.position, direction, out hit))
                {
                    GameObject hole = Instantiate(bullethole, hit.point + new Vector3(0, 0, 0.3f), Quaternion.LookRotation(-hit.normal));
                    Destroy(hole, 5f);
                    Health target = hit.transform.GetComponent<Health>();

                    if (target != null)
                    {
                        target.TakeDamage(damage);
                    }
                }

                if (i == 10)
                {
                    currentAmmo -= 1;
                }
            }
        }
    }

If someone can find a solution to this it would be a godsend…

                spread = transform.TransformDirection(s[i]);
                Vector3 direction = spread + cam.transform.forward;

If transform and cam.transform are not pointing the same direction…

It doesn’t seem to change if the line is spread = cam.transform.TransformDirection(s[i]); I already tried that =(

If you can share the project or a part of it, I can take a look. If you use the same transform to rotate the spread AND add its forward direction, that will definitely produce the right result - there must be something else going on that isn’t visible in that chunk of code.

Incidentally, instead of adding the camera transform forward, you could just put 1f as the third element in all the spread constructors. That will have the same effect.

Also:

                if (i == 10)
                {
                    currentAmmo -= 1;
                }

…what?

Here is the project : https://www.mediafire.com/file/354149kxvpl510y/My+project+(1).7z/file though I can basically guarantee that there is nothing else going that isn’t visible in that chunk of code. I tried your suggestion and as expected it sadly produced the same result than adding cam.transform.forward… As for this part :

 if (i == 10)
     {
         currentAmmo -= 1;
      }

I don’t get what’s weird about it, it just removes one ammo when the last bullet (the tenth one) is spawned. You can move that line here though to save an if statement.

 if (Input.GetButtonDown("Fire1") && currentAmmo != 0 && Time.time >= NextTimeToFire)
        {
            NextTimeToFire = Time.time + firerate;
            currentAmmo -= 1;

I thought that’s what it meant, but when you see something out of place like that it makes you doubt yourself: “If it’s just supposed to happen at the end of the loop, why not just put it after the loop? What am I missing”

Also, it won’t work because i will never be equal to 10 (or 9, for that matter)

Your project is working fine. I swapped out the bullethole quad for a sphere (easier than fixing the z-fighting to see what’s going on) and got this:
8130941--1054784--Capture1.PNG8130941--1054790--Capture2.PNG 8130941--1054793--Capture3.PNG

Don’t forget that it will only look like an even spread from the place you fired it from. If you move after firing, it will no longer look even:
8130941--1054796--upload_2022-5-16_10-3-18.png

EDIT: Oh, and you had two gun scripts in the scene.

Well I honestly don’t know what to do anymore, I can very obviously see that it does not look straight, even when not moving. You can see proof of this here :

Thanks for the help.

It’s an optical illiusion caused by the perspective and varying visual size of the bulletholes.See what happens if I put a green dot in the middle of each:
8133131--1055132--upload_2022-5-17_0-21-42.png

To convince yourself, replace the black circle quad with a 0.1 scale black sphere - ideally with a material that doesn’t z-read, so that the whole of the sphere is always visible.