Search Unity

Mouse aim offset correction in a top-perspective 3D game with 2-dimensional (XZ) aim

Discussion in 'Scripting' started by ApocFI, Jan 25, 2019.

  1. ApocFI

    ApocFI

    Joined:
    May 26, 2017
    Posts:
    33
    I've scoured through this issue for ages and haven't found a good solution. I'm working on a top-perspective shooter game, where the camera is in an angle above the player, but not exactly above. The player is rotated towards the mouse cursor at all times, which is done with ScreenPointToRay:

    Code (CSharp):
    1.         Ray camRay = Camera.main.ScreenPointToRay (Input.mousePosition);
    2.  
    3.         RaycastHit floorHit;
    4.  
    5.         if (Physics.Raycast(camRay, out floorHit, camRayLength, floorMask))
    6.         {
    7.  
    8.             // Vector3 from the player to mouse cursor position
    9.             Vector3 playerToMouse = floorHit.point - transform.position;
    10.             playerToMouse.y = 0f;
    11.  
    12.             // Vector3 from aiming camera to mouse position
    13.             Vector3 camToMouse = aimCam.transform.position - floorHit.point;
    14.  
    15.        
    16.             Quaternion newRotation = Quaternion.LookRotation(playerToMouse);
    17.             playerRigidBody.MoveRotation(newRotation);
    18. }
    The aiming is locked to XZ axes, so you can't shoot up or down. This brings up a problem with how you'd expect the game to work. You point the mouse cursor at a point, and the shot should go there right?
    Here, the player is pointing the mouse cursor at the target and shooting. However, the shot is offset "above" or left of the target.
    B.JPG

    A.jpg
    (Pictures taken in two different situations but you get the idea)

    I expected this to be because of the floor mask used to detect the mouse position was at ground level and not at the actual height of where the shot would be, so I tried creating another layer which is located exactly at the gun barrel height, but it didn't change the offset at all.

    So next I created another camera (only for aiming purposes), which is always exactly above the player character and angled 90 degrees down. This corrected the aiming for the top part of the screen, but middle- and lower areas of the screen would still have the offset.

    After fiddling around for a while I ended up giving a fixed correction to the aim with a Vector3 offset defined in the inspector:
    Code (CSharp):
    1. //offset the rotation by a fixed offset to correct the perceived aiming offset    
    2. Quaternion newRotation = Quaternion.LookRotation(playerToMouse + offset);
    3.             playerRigidBody.MoveRotation(newRotation);
    with a Vector3 of ~0f,0f, -0.2f. This corrects the aim quite well in the lower part, but offsets it in the upper part.

    So, how should this really be fixed instead of the jury-rig approach that I have? It kind of works, but at times it feels odd, because if you'd like to aim spot-on you'd still have to aim in a slightly different way depending on which part of the screen you are aiming. I also tried some trigonometry to calculate the offset at runtime, but I suck at maths and I feel I have reached a dead end with my own solutions.
     
  2. exiguous

    exiguous

    Joined:
    Nov 21, 2010
    Posts:
    1,749
    You shoot the ray through the mouse cursor down to the terrain? I prefer to raycast against a "virtual" plane.
    Code (csharp):
    1.  
    2. public static class ExtensionMethods_Camera
    3. {
    4.    private static Plane xzPlane = new Plane(Vector3.up, Vector3.zero);
    5.  
    6.    public static Vector3 MouseOnPlane(this Camera camera)
    7.    {// calculates the intersection of a ray through the mouse pointer with a static x/z plane for example for movement etc, source: http://unifycommunity.com/wiki/index.php?title=Click_To_Move
    8.        Ray mouseray = camera.ScreenPointToRay(Input.mousePosition);
    9.        float hitdist = 0.0f;
    10.        if (xzPlane.Raycast(mouseray, out hitdist))
    11.        {// check for the intersection point between ray and plane
    12.            return mouseray.GetPoint(hitdist);
    13.        }
    14.        if( hitdist < -1.0f )
    15.        {// when point is "behind" plane (hitdist != zero, fe for far away orthographic camera) simply switch sign https://docs.unity3d.com/ScriptReference/Plane.Raycast.html
    16.            return mouseray.GetPoint( -hitdist );
    17.        }
    18.        Debug.Log ( "ExtensionMethods_Camera.MouseOnPlane: plane is behind camera or ray is parallel to plane! " + hitdist);       // both are parallel or plane is behind camera so write a log and return zero vector
    19.        return Vector3.zero;
    20.    }
    21. }
    22.  
    You could set the plane each frame to the "height" of the player/weapon when the height changes (stairs, hills). Maybe this gives a better direction vector for the aiming than different high objects and the ground.

    Then I would shoot a ray from the weapon to test what it hits.

    There is a Unity Tutorial with exactly this behavior. Don't know the name and it's a little older so it may throw some errors when importing into a new Unity version. It also has a nice laser pointer effect. Maybe this could give you some insight on how you should do it.
     
  3. ApocFI

    ApocFI

    Joined:
    May 26, 2017
    Posts:
    33
    Thanks for the answer. Unfortunately it didn't work, and it seems to be fundamentally the same thing that I do. Do you have any idea which tutorial is the one you're referring to?

    The floor plane that is used for the raycasting is just a invisible plane object in the game, positioned at 0,0,0 (I tried barrel height as well). The shooting is done by raycasting forwards from the weapon, which _should_ be pointed towards the mouse position.
     
  4. exiguous

    exiguous

    Joined:
    Nov 21, 2010
    Posts:
    1,749
    What IS the mouse position? The mouse is just a point on your screen. I guess there is not a single position in the world, but a line of possible intersections.

    I think Angry Bots was the one I mean and there is a more recent Survival Shooter tutorial.
     
  5. ApocFI

    ApocFI

    Joined:
    May 26, 2017
    Posts:
    33
    Yes, I'm aware that the mouse position is virtual. This system would work fine (at least I think so) if it wasn't locked in 2D aiming. If the mouse would point at the toe of an enemy, the shot would go there. but now it doesn't go to the toe, it goes to the fixed height directly above that point. And that is perceived as the aim offset.

    The game I'm doing derives from the survival shooter tutorial, unfortunately this problem is included in it :(
     
  6. exiguous

    exiguous

    Joined:
    Nov 21, 2010
    Posts:
    1,749
    Must the player manually aim this precise? You could just detect which enemy the player points at and orient the weapon directly towards that enemy not the mouse position. A simple "aim help".
     
  7. ApocFI

    ApocFI

    Joined:
    May 26, 2017
    Posts:
    33
    Yeah, that is one approach I have considered. I feel that the manual offset method is nearly precise enough, but could be better. Of course it doesn't have to be 100% accurate, but once you know it's not accurate you'll start noticing it.
    Hopefully someone has ideas about how to calculate the offset accurately. Thanks for your feedback anyway!
     
  8. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    Not totally sure this is what you're going for, but this definitely makes the angled-viewport aiming seem more intuitive:

    What it does is make a raycast for anything, and if that hit location is above the gun height, it just drops the height to gun level. If it's lower than the gun height, it will do the virtual plane raycast and aim at that point instead of just raising the height. That way from the camera perspective, the shots will go where the mouse is aiming.

    Code (CSharp):
    1. public Transform player;
    2. public float gunHeight = 1f;
    3.  
    4. private void Update()
    5. {
    6.     Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    7.  
    8.     if (Physics.Raycast(ray, out RaycastHit hitInfo))
    9.     {
    10.         Vector3 aimPoint = GetGunHeightAimPoint(ray, hitInfo);
    11.         player.LookAt(aimPoint);
    12.     }
    13. }
    14.  
    15. private Vector3 GetGunHeightAimPoint(Ray mouseAim, RaycastHit hitInfo)
    16. {
    17.     // if the raycast hit something and the Y is above the gun height
    18.     if(hitInfo.collider != null && hitInfo.point.y > gunHeight)
    19.     {
    20.         Vector3 heightAdjusted = hitInfo.point;
    21.         heightAdjusted.y = gunHeight;
    22.         return heightAdjusted;
    23.     }
    24.  
    25.     Plane aimPlane = new Plane(Vector3.up, Vector3.up * gunHeight);
    26.     if (aimPlane.Raycast(mouseAim, out float distance))
    27.     {
    28.         return mouseAim.GetPoint(distance);
    29.     }
    30.     else
    31.     {
    32.         return Vector3.zero; // you missed the ground
    33.     }
    34. }
    So there's probably some math that could make this fuction only require a single raycast operation by manipulating the vectors, but i dont think one extra virtual plane raycast is anything to worry about.
     
    Last edited: Jan 25, 2019
  9. ApocFI

    ApocFI

    Joined:
    May 26, 2017
    Posts:
    33
    Thanks for the input. I tried this method, but unfortunately it doesn't seem to work. The aim offset is still the same, plus now the player will do XZ rotation which is breaking out the 2D aiming. The character will not be rotated towards points below the gun barrel height, but might be rotated upwards in that direction.

    Maybe I will have to make a good illustration on this to get the problem visualized properly.
     
  10. ApocFI

    ApocFI

    Joined:
    May 26, 2017
    Posts:
    33
    Guys, I managed to make it work now! I tried to set the raycasting plane at barrel height again like I did previously before making this thread, and it worked now. I'm not sure what I did differently, but it works now. I was really baffled why it didn't work, because all my debugging ray etc. suggested that the problem is solely because the hit detection mask is at different Y height than the weapon.

    So in principle the methods suggested in this thread should be viable. Thank you guys, you made me push into this deep enough to get it solved.
     
    LiterallyJeff likes this.