Search Unity

Question Issue with Zooming Towards Mouse Cursor for RTS-Like Camera

Discussion in 'Scripting' started by TimmyChips, May 25, 2022.

  1. TimmyChips

    TimmyChips

    Joined:
    Aug 13, 2021
    Posts:
    4
    I've been making a camera in Unity that moves similar to an RTS for a 3D colony management type game. I have panning, orbiting, as well as zooming down, except with one caveat. To explain the problem the best that I can, my camera is set up with an empty as the Camera Pivot and then a Camera as it's root/child. Moving and orbiting the camera affects the pivot. Zooming in changes the distance of the camera to it's pivot, as well as moving the pivot to where the mouse is pointing. These all work, except moving the camera's pivot has an issue.

    To make the zoom work, I have a raycast from the camera to the mouse's position, and as I scroll in and out, the camera will zoom and move towards the mouse. This is all in a Coroutine to ensure that the zooming and moving of the camera and it's pivot happens simultaneously. The variable _parentObject refers to the camera's pivot.
    Code (CSharp):
    1. // Zooms in and out based on Scroll Wheel
    2. IEnumerator cameraZoom()
    3. {
    4.     // Scrolls based on Scroll Wheel direction and sensitivity
    5.     float scrollAmount = Input.GetAxis("Mouse ScrollWheel") * scrollSensitivity;
    6.  
    7.     float parentMoveAmount = scrollAmount;
    8.  
    9.     // Makes camera zoom faster the further away it is from the target
    10.     scrollAmount *= (this._cameraDistance * 0.3f);
    11.  
    12.     // Assigns parent's distance between mouse cursor
    13.     // Makes parent move faster further away it is from mouse cursor
    14.     parentMoveAmount *= (Vector3.Distance(_mousePosition, _parentObject.position) * 0.3f);
    15.  
    16.     // Assigns camera's distance and flips camera axis
    17.     this._cameraDistance += scrollAmount * -1f;
    18.  
    19.     // This makes camera go no closer than 1.5m from target, and no further than 100m from target
    20.     _cameraDistance = Mathf.Clamp(_cameraDistance, 1.5f, 100f);
    21.  
    22.     // Assigns position of camera
    23.     _camObject.localPosition = new Vector3(0f, 0f, Mathf.Lerp(_camObject.localPosition.z, _cameraDistance * -1f, scrollDampening));
    24.  
    25.     // Assigns position of parent
    26.     if (_cameraDistance < 100f)
    27.     {
    28.         _parentObject.position = Vector3.MoveTowards(_parentObject.position, _mousePosition, parentMoveAmount);
    29.     }
    30.  
    31.     return null;
    32. }
    33.  

    This moves the camera pivot on the x, y, and z axis, and the problem that I have with the zoom is the y-axis. I need the y value to change to ensure the camera will move and zoom seamlessly to the mouse cursor.
    However, the side effect is that if I zoom to something tall like a big tree, and then zoom away with my mouse cursor on the ground, the pivot gets sent way up into the sky.
    When you orbit, it won't orbit nicely on the ground but will instead orbit the sky:



    I've attempted numerous things like modifying the y-value of the camera pivot in the script, or using a Cinemachine/Virtual Camera, but I was unable to get them to work. In a lot of cases, clamping the y-value of the parent fixes the problem of it moving too far high up, but then zooming isn't seamless and the mouse cursor will change as you zoom in/out, which shouldn't happen. I know that I am missing a step, as a game called Stonehearth implements a similar thing, but the pivot is constrained while still being seamless. I am unsure of how to do both for mine.

    I've asked a similar question a while back and I was recommended to use Cinemachine, except I had no idea how to get it work for my case. This was the best way I could get it to work, but as lined from this forum post, maybe adding a virtual camera might help? https://forum.unity.com/threads/rts-camera.473931/#post-3456029 I am not sure, as I also couldn't figure out how to get it to work.

    Summary: RTS camera zooms in on mouse cursor almost perfectly, except the camera pivot can move either high off the ground or below. Pivot should be near ground to orbit nicely, however clamping the pivot's y-value makes zooming not seamless. I need a way to limit the height of the pivot yet ensure that the mouse cursor's position will not change.

    Any help is appreciated!
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,726
    Just to clarify terminology, if you move the camera, even in and out along its visual axis, that is NOT a zoom.

    Moving the camera is always a dolly, even in and out. A true zoom is only a field of view change.

    For what you are doing, I think you want to reorganize your thinking:

    - do not think of the camera as floating somewhere above the playfield

    - instead think of the camera as "lifted back from" a particular spot on the ground, the "focus" spot.

    When you scroll around, you move this spot, then compute the camera's position by "backing up" the viewplane of the camera (by the distance, at the angle, at the heading).

    Then what you are calling a "zoom" (mouse wheel in out) which is actually a dolly, simply adjusts that "back up" value.

    Similarly, viewing from different angles changes the compass direction (heading) around which you back up.

    And viewing shallow-steep changes the angle.

    Think of a very flat wide ice cream cone with its point touching the "focus" on the board.

    The camera lives on the upper edge of the cone rim,

    Fun fact, the famed Alfred Hitchcock effect happens by changing the field of view AND simultaneously dolly in the opposite direction:

    https://en.wikipedia.org/wiki/Dolly_zoom
     
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,726
    Pulled this out of one of my games and whipped it into shape like described above.

    To demo, drop it on a Camera, put a cube at (0,0,0) and wiggle the controls in the inspector.

    Code (csharp):
    1. using UnityEngine;
    2.  
    3. // @kurtdekker
    4. // put me on the camera
    5. // a simple RTS look-at-ground script
    6.  
    7. public class LookAtGround : MonoBehaviour
    8. {
    9.     [Header( "Feed this with the world focus point.")]
    10.     public Vector3 GroundSpot;
    11.  
    12.     [Header( "How far back from the point.")]
    13.     [Range( 5, 40)]
    14.     public float Distance = 10;
    15.  
    16.     [Header( "Heading around the compass")]
    17.     public float Heading;
    18.  
    19.     [Header( "Regard angle: high is overhead.")]
    20.     [Range( 10, 80)]
    21.     public float Angle = 45;
    22.  
    23.     [Header( "This can be handy: to lift a bit.")]
    24.     public float Raise = 2.0f;
    25.  
    26.     void Update ()
    27.     {
    28.         // first around the heading
    29.         Quaternion rot = Quaternion.Euler( 0, Heading, 0);
    30.  
    31.         // next our regard angle
    32.         rot = rot * Quaternion.Euler( -Angle, 0, 0);
    33.  
    34.         // and back up vector
    35.         Vector3 backup = rot * Vector3.forward;
    36.  
    37.         // stare point could be up a bit from the ground
    38.         Vector3 StarePoint = GroundSpot + Vector3.up * Raise;
    39.  
    40.         // bring it all together
    41.         Vector3 ViewpointPosition = StarePoint + backup * Distance;
    42.  
    43.         // make it so
    44.         transform.position = ViewpointPosition;
    45.         transform.LookAt( StarePoint);
    46.     }
    47. }