Create fixed bullet spread for FPS game

I am making a skill based first person shooter, and for this reason I do not want my weapons to have deviation based on random number generation. Instead, I would like for the shotguns to spread the pellets in a specific pattern each time I fire the gun. Eg, I want pellet number one to be 7 degrees to the right, pellet 2 7 degrees to the left, etc etc. No random number generation making the shots inconsistent.

The issue I currently have, however, is that I cannot make a consistent spread. I can add values to a raycast’s direction, but they end up doing this weird effect where the spread rotates.

As you can see, the shot facing forwards is a horizontal line of three pellets, but as you look up or down, the player’s spread pattern itself rotates entirely, being more diagonal instead of horizontal. How can I prevent this and make the spread pattern actually consistent? That ceiling shot shouldn’t be diagonal.

Below is the code I currently have. Above this section is a script that rotates the camera based on mouse input, and then the direction of the raycast has a few values added to it. Should I be using a quaternion, a vector3, a transform, or some other variable in order to properly get a fixed spread? Team Fortress 2’s competitive mode fixed spread achieves this but I cannot replicate the effect of the fixed shotgun spread.

if (Input.GetKey(KeyCode.Q))
 {
     for (int i = 0; i < 3; i++)
     {
         Vector3 direction = transform.forward;
         direction.x += combatAngles[i].z;
         direction.y += combatAngles[i].y;
         direction.z += combatAngles[i].z; 
         if (Physics.Raycast(transform.position, direction, out RaycastHit testHit))
         {
             GameObject testObj = GameObject.CreatePrimitive(PrimitiveType.Cube);
             testObj.transform.position = testHit.point;
             testObj.transform.localScale *= 0.1f;
         }
     }
 }

Also one thing to note is that the line spacing format might be messed up in the above code snippet.

You need to transform your combatAngles from local space to world space to account for the rotation: Unity - Scripting API: Transform.TransformDirection

I would just multiply the origin forward vetctor with a set of predefined angles. Like I do with our shotgun code, but without randomness. Though I think it shuld be random. Part of the skill with shotun is to know which range to use it, and time the shots.

public void CreateBallisticsJob(Vector3 pos, Vector3 direction, float deviation, int numberProjectiles, FirearmStats firearmStats)
{
    var batch = ArrayPool<BallisticsJob>.Get(numberProjectiles);
    for (int i = 0; i < batch.Length; i++)
    {
        batch[i] = new BallisticsJob
        {
            Position = pos,
            Direction = deviation > 0 ? Quaternion.Euler(Random.Range(-deviation, deviation), Random.Range(-deviation, deviation), Random.Range(-deviation, deviation)) * direction : direction,
            FirearmStats = FirearmStats
        };
    }
    Enqueue(batch);
}

In other words, deviation*directionVector;

I added Camera.main.transform.TransformDirection() to the Physics.Raycast() call, but it still does the exact same thing. Am I using it right? If not can you help explain to me what the difference between local space and world space is? I sort of understand the concept but not fully by any means.

You have to time and realize what range you can use a shotgun effectively at with fixed spread as well. Random chance is the literal opposite of skill. And plus, with random chance, there is no guarantee that any degree of skill will make you succeed.

The shotgun should have that uncertaininess to it. Thats how shotguns in games have been designed for ever, for a reason. But hey. Its not hard alter my code todo it the wrong way :wink:

I’m not sure since you didn’t paste your new code. You don’t need to add anything together like you’re currently doing, just call TransformDirection() on the transform you’re using as your firing point.

Here’s an example:

       public List<Vector3> spread; // has values: 1,0,0    0,1,0   0,0,1

       private void Update()
       {
           for(int i = 0; i < spread.Count; ++i)
           {
               Vector3 transformedDirection = transform.TransformDirection(spread[i]);

               Debug.DrawRay(transform.position, transformedDirection.normalized * 2.0f, Color.red);
           }
       }

And you can see that the rays maintain their direction in any orientation:
4456594--408937--rays.png

It’s important to note that your “combat angles” are actually now unit vectors, so your values may need to be adjusted. 1,0,0 would be directly to the object’s right, 0,1,0 would be directly up and 0,0,1 would be forward.

You’re not helpful. It hasn’t been that way forever, either actually. And yes, it is that difficult because your code creates a square spread instead of a conical one. I have random spread fine, but I asked for fixed spread.

Thank you. I ended up using something very similar, with transform.transformDirection(spread*), with my issue being that none of the vectors had 1 for the z value. Your posts cleared up a lot of things for me, and also, shoutout to the DUSK development team for helping too.*

Original Doom used a random spread…

That’s “forever” as far as shotguns in an FPS is concerned.

1 Like

If we’re to go into DOOM, it used 256 ticks, and the ticks were used mainly for damage. The spread only mattered horizontally and since that spread was so small it hardly mattered that there was a spread at all. If I wanted random spread I would have asked for it though.

My code does not create a square spread. The spread is a cone going from muzzle +/- 1.5 degrees outward

@xthunderduckx

I think it is easiest to think this like one was using a projector. And I would just use positions.

First create a pattern some distance away, in x,y and z-depth.

Then use transform.transformpoint / direction to move this pattern to object space, like @GroZZleR said.

This way you can easily create any pattern you want (like here a rectangular, circular and scatter pattern), facing any direction. You can convert the points to direction vectors if need be.

Spread only mattered horizontally because the game was essentially a 2D game viewed through a 3D perspective, and at range it made a lot of difference. Chance based odds are very skill intensive by the way. Being able to quickly calculate chance and use that to inform decision making is not a no skill activity. Otherwise you wouldn’t see the same set of people reliably appearing at the final table in poker tournaments.

As for how to do what you want, I’d just create a set of vectors that represent the shotgun blast, one for each of the individual projectiles. Then when you fire you use them either to raycast or as a multiplier for applying force to physics objects, depending on how you want to simulate the blast itself. Creating the set of vectors manually should be fairly trivial. No need to generate them from some algorithm.

1 Like

Exactly like I do in my first post :stuck_out_tongue: deviationRotation*direction

1 Like