unity-game-enginegame-development

Hipfire calculations when shooting, calculating a random raycast direction inside the crosshair


Update: I finally solved the issue, note that I didn't work at this 100%, that's why I found a solution after so much time.

I am facing this issue from a few days, I want to shoot a Raycast inside the UI reticle but I can't get it working as it should, changing the screen aspect ratio or the reticle size is enough to make the bullet go outside the reticle, for example setting the resolution to 4K is going to make the bullet hit almost the center of the reticle.

Here is the code responsible for handling the raycast:

//If you scroll below this html page, you can see the implementation of the UIHandler methods with comments
Vector3 origin = UIHandler.SINGLETON.gunCrosshair.GetCrosshairPosition();
float pixelDelta = UIHandler.SINGLETON.gunCrosshair.GetCrosshairPixelDelta();
float spread = pixelDelta / Screen.width * Camera.main.fieldOfView;
/* hipfireSpreadMultiplier is a value needed because the spread itself is a very small value.
   I am adding to the origin just the right direction for testing */
origin += spread* Vector3.right * hipfireSpreadMultiplier;
Ray ray = Camera.main.ScreenPointToRay(origin);
if (Physics.Raycast(ray, out RaycastHit raycastHit, 900, enemyLayerMask))
   Debug.Log("I did hit " + raycastHit.transform.name);

And here is the code responsible for handling the UI reticle:

public void UpdateCrosshairSize(float size)
{
    //This method makes the crosshair larger when moving
    crosshair.sizeDelta = Vector2.one * size * crosshairSizeMultiplier;
}
public float GetCrosshairInnerEdge()
{
    //right is a RectTransform, it is the right white line of the crosshair
    return right.position.x - right.sizeDelta.x / 2;
}
public Vector3 GetCrosshairPosition()
{
    //crosshair is a RectTransform, parent of all the lines of the reticle
    return crosshair.position;
}
public float GetCrosshairPixelDelta()
{
    //This calculates the difference between the position of the crosshair and the start of the right reticle line
    return GetCrosshairInnerEdge() - GetCrosshairPosition().x;
}

Even if the code seems right the bullet doesn't always match the crosshair. Below you can see the images showing the issue, the size is less than 20kb each so it's mobile data friendly. All the screenshots are made by keeping the camera stright forward.

Note that the gun moves up due to recoil but only the crosshair delta is used in the spread calculation.

In particular when firing the first shot the bullet matches the crosshair: first bullet hit point

When firing the second shot the bullet touches the right line of the crosshair: second bullet hit point

When firing the third shot the bullet goes outside the crosshair: third bullet hit point

I have tried to:


Solution

  • As always it turns out that the easiest solution is the one working.

    I did add to the right line of the reticle an empty gameobject which is anchored to the inner edge of the right line as you can see (33Kb image):

    Reticle Hierarchy child-parent inspection

    I edited the raycast calculation code as it follows:

    float maxSpread = UIHandler.SINGLETON.gunCrosshair.GetCrosshairInnerEdgePosition().x - UIHandler.SINGLETON.gunCrosshair.GetCrosshairPosition().x;
    Vector3 randomSpread = Vector3.up * Random.Range(-maxSpread, maxSpread) + Vector3.right * Random.Range(-maxSpread, maxSpread);
    Ray ray = Camera.main.ScreenPointToRay(UIHandler.SINGLETON.gunCrosshair.GetCrosshairPosition() + randomSpread);
    
    if (Physics.Raycast(ray, out RaycastHit raycastHit, 900, weaponData.enemyLayerMask))
       Debug.Log("I did hit " + raycastHit.transform.name);
    

    And I refactored the UI code:

    public Vector3 GetCrosshairInnerEdgePosition()
    {
        //You shouldn't use Find at runtime! Use a reference or you are going to waste performance.
        return right.Find("InnerEdge").position;
    }
    public Vector3 GetCrosshairPosition()
    {
        return crosshair.position;
    }
    

    And that's enough to make all work, I feel so dumb, the solution was extremely easy and probably more efficient than calculating the pixel delta based on the Screen.width at runtime.