Search Unity

Help Wanted Bullet Spread not acting as expected.

Discussion in 'Scripting' started by HolBol, Nov 30, 2020.

  1. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,888
    Hey there.

    I'm attempting to implement basic bullet spread (in this case, a shotgun's pellet spread) via raycasts, and I've noticed a slightly unexpected bug. When firing along the world z axis, the spread is as expected, seen here:

    1.png

    However, when firing along the world x axis, this compression along one axis of the spread seems to occur.

    2.png

    I'm unsure quite what the issue is here. Maybe I need to be taking the z axis into account somewhere in this code? It's been a while since I've done anything like this.

    Code (CSharp):
    1. void FireScatter(){
    2.        
    3.         //one round should give multi pellets
    4.         roundsInMagazine--;
    5.         nextFire = Time.time + rateOfFire;
    6.        
    7.         float rad = 0.0f;
    8.         float spreadX = 0.0f;
    9.         float spreadY = 0.0f;
    10.         //fire a ray for every pellet, with each one getting random spread
    11.         for (int i = 0; i<numShotPellets; i++) {
    12.            
    13.             rad = Random.Range(0.0f, 360.0f) * Mathf.Rad2Deg;
    14.             spreadX = Random.Range(0.0f, spreadFactorX / 2.0f)* Mathf.Cos(rad);
    15.             spreadY = Random.Range(0.0f, spreadFactorY / 2.0f)* Mathf.Sin(rad);
    16.             Vector3 deviation = new Vector3(spreadX, spreadY, 0.0f);
    17.            
    18.             Vector3 direction = cam.transform.forward;
    19.             Ray ray = new Ray(cam.transform.position, direction + deviation );
    20.            
    21.             RaycastHit hit;
    22.             if (Physics.Raycast(ray, out hit, range, mask)){
    23.                
    24.                 Debug.DrawRay(cam.transform.position, hit.point - ray.origin, Color.red, 100f);
    25.                 if (hitEffect)
    26.                 {
    27.                     Instantiate(hitEffect, hit.point, Quaternion.FromToRotation(Vector3.up, hit.normal));
    28.                 }
    29.                 else
    30.                 {
    31.                     Debug.Log("No hit effect assigned");
    32.  
    33.                 }
    34.             }
    35.         }
    36.     }
    Any help would be greatly appreciated! Thanks in advance.
     
  2. AlTheSlacker

    AlTheSlacker

    Joined:
    Jun 12, 2017
    Posts:
    270
    I can see two suspicious lines:

    rad = Random.Range(0.0f, 360.0f) * Mathf.Rad2Deg;
    Are you sure you want to generate a random number between 0 and 360 and then convert it to degrees? This will give you a number between 0 and 360*57.3 (it's working because your trig functions, but it doesn't make much sense), you may as well just random range 0 to 2Pi and save yourself some CPU.

    And I believe your real problem is:
    Vector3 deviation = new Vector3(spreadX, spreadY, 0.0f);

    You then ray cast with no Z deviation, you can probably be lazy and just add a spreadZ instead of 0.0f.
     
  3. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,888
    My main concern being, would that not cause other weirdness?


    This is how it looks now. I'm seeing a similar pattern, but skewed along the diagonal for each shot. On the other side of the block that I'm shooting at, the pattern skews to the left, rather than the right as shown here.
    3.png


    I've updated the code as such

    Code (CSharp):
    1.    
    2. float spreadX = 0.0f;
    3.         float spreadZ = 0.0f;
    4.         float spreadY = 0.0f;
    5.         float spreadFactorZ = spreadFactorX;
    6.         //fire a ray for every pellet, with each one getting random spread
    7.         for (int i = 0; i<numShotPellets; i++) {
    8.          
    9.             rad = Random.Range(0.0f, 360.0f) * Mathf.Rad2Deg;
    10.             spreadX = Random.Range(0.0f, spreadFactorX / 2.0f)* Mathf.Cos(rad);
    11.             spreadY = Random.Range(0.0f, spreadFactorY / 2.0f)* Mathf.Sin(rad);
    12.             spreadZ = Random.Range(0.0f, spreadFactorZ / 2.0f)* Mathf.Sin(rad);
    13.             Vector3 deviation = new Vector3(spreadX, spreadY, spreadZ);
    14.          
    15.             Vector3 direction = cam.transform.forward;
    You mentioned some weirdness in in that math- which I must say I lifted from another source. The original direction I had gone in was more like this.

    Code (CSharp):
    1. float rad = 0.0f;
    2.         float spreadX = 0.0f;
    3.         float spreadY = 0.0f;
    4.         //fire a ray for every pellet, with each one getting random spread
    5.         for (int i = 0; i<numShotPellets; i++) {
    6.  
    7.             Vector3 direction = cam.transform.forward;
    8.             direction.x += Random.Range(-spreadFactorX, spreadFactorX);
    9.             direction.y += Random.Range(-spreadFactorY, spreadFactorY);
    10.          
    11.             Ray ray = new Ray(cam.transform.position, direction);
    But this exhibits identical results to the first code, with the compression along the axis. Is there a simpler way to add deviation from the forward line that I'm missing? My main concern that introducing a cheap spreadFactorZ in the same manner here would give the same results as the above picture.
     
  4. AlTheSlacker

    AlTheSlacker

    Joined:
    Jun 12, 2017
    Posts:
    270
    OK, I was too lazy... Hopefully the following works for you, if I have somehow managed to screw up typing that snippet, check the attached demo for a working scene (version 2020.1.14)

    Code (CSharp):
    1. spreadX = Random.Range(-spreadFactorX, spreadFactorX);
    2. spreadY = Random.Range(-spreadFactorY, spreadFactorY);
    3. Vector3 direction = cam.transform.forward + cam.transform.right * spreadX + cam.transform.up * spreadY;
    4. Ray ray = new Ray(cam.transform.position, direction);
    Just set the spreadFactors for the base of a square cone pointing forward from the camera 1m away. If a square footprint is not cool (it shouldn't be obvious unless you use a lot of pellets) then just bring the trig back in, but you can see how you can use the right and up to give you a plane with a normal to the camera.
     

    Attached Files:

    HolBol likes this.
  5. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,888
    Ah, that does seem to help, thank you! The square footprint is of no real bother, so long as it behaves how the player would expect a typical game shotgun to behave. I can confirm it's working as expected.

    Could you possibly explain what I was messing up here? I know there was some sort of fault in my approach.
     
  6. SparrowGS

    SparrowGS

    Joined:
    Apr 6, 2017
    Posts:
    2,518
    what you were doing wrong is not taking into account the player rotating, you need to calculate the XY offset relative to the player, lacking that it will behave as if the player is with "no rotation" (IE looking in the global .forward(positive Z) direction, Euler(0,0,0) )
    then if you shoot at global.forward it will scatter your shots on global XY behaving as expected, but if you shoot say on global.left (negative X, i think) it will still try to scatter on the global XY, nullifing the X offset, same as if you would shoot up, you'll only scatter on the X and the Y will be nullified.
     
    HolBol likes this.
  7. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    1,184
    Many tiny things ^^.

    First your usage of cos and sin doesn't make much sense because you roll two seperate random values for the x and y spread. This would only make sense when you roll a single "distance" value which you use for both values. So you pick a direction and then just scale that direction by a random radius / distance. Though doing this generally produces a higher density at the center which may be desired or not. There are ways to fix this by having the radius fall off with the square root.

    The next issue is your spread offset direction is defined in worldspace. So when you look 90 to the left or right the x spread will now point in the same direction you're looking. That's why the spread becomes a line since from the local perspective right and left is now the world z axis. That's why AlTheSlacker used cam.transform.right and cam.transform.up for the two spread directions as they are the right / up direction vectors relative to your view.

    If you're actually looking for a uniform spherical distribution I've posted a solution about 9 years ago on UA ^^. Ignore the first part and just look at the first edit. "GetPointOnUnitSphereCap" takes in a rotation (just pass in cam.transform.rotation) and a max spread angle in degree. That angle is half of the cone angle. So an angle of 90 would mean you get a 180° spread (== +-90°).
     
unityunity