Creating a Scrolling Map for RTS and Strategy Games in Unity

Published

In Strategy and RTS games, the player can look around the map by putting the mouse cursor near the border of the game window: by doing this, the camera starts scrolling in the selected direction on the x axis and the z axis. This is a very important system for this kind of games and easy to implement.

To do this, there are a few things to keep in mind. First, this scrolling effect does not occur until the cursor reaches near the border of the window. So we’ll need to set some boundaries to start scrolling. For these boundaries, we’ll give them values between 0 and 0.5, since we’ll be considering them as % of the viewport (aka the screen size).

Then, we’ll need to get our Mouse Position, with Input.MousePosition. An important thing to remember is that Input.MousePosition gives us the mouse position in Screen Space and we need it in Viewport Space. This is where the camera comes in: cameras in Unity can handle conversion between different world spaces. In this case, we can convert from Screen Space to Viewport Space by using the Camera.ScreenToViewportPoint method.

Finally, we compare the cursor position in Viewport space to our selected boundaries, and scroll the map accordingly: our camera system is done!

The code is pretty straightforward with plenty of comments, feel free to use it:

public class MouseScreenScroller : MonoBehaviour
{
    public Camera mCamera;

    [Header("Settings")]
    public float mCameraSpeed = 5f;
    public Vector2 mBoundsTolerance = new Vector2(0.10f, 0.10f); // start scrolling at X% from border
    public bool bLockCameraOnStart = true; // confine camera to game screen
    

    // caching camera transform
    private Transform _mCameraTransform;

    // allocate memory to avoid GC
    private Vector3 _mMousePosition;
    private Vector3 _mCameraTranslation = new Vector3();

    private void Start()
    {
        _mCameraTransform = mCamera.transform;   

        if (bLockCameraOnStart) {
            Cursor.lockState = CursorLockMode.Confined;
        }
    }


    // Update is called once per frame
    void Update()
    {
        _mCameraTranslation.x = 0f;
        _mCameraTranslation.z = 0f;

        // exchanging y and z to reconcile different space references 
        // in this example, the camera is above ground, looking down on it so we are only interested
        // in the x and z axis.
        _mMousePosition = mCamera.ScreenToViewportPoint(Input.mousePosition);
        _mMousePosition.z = _mMousePosition.y;
        _mMousePosition.y = 0f;


        // check if out of screen? if it is, don't move. This is unnecesary if the cursor is confined to the game screen
        if (_mMousePosition.x < 0f || _mMousePosition.x > 1f || _mMousePosition.z < 0f || _mMousePosition.z > 1f)
        {
            // early return
            return;
        }

        // now check if the mouse position has reached the threshold.
        // We are using Viewport space, which is gives coordinates within our screen as belonging to the range [0, 1]
        // so we can consider our bounds as percentages: if the mouse is in the leftmost or rightmost X%
        // of the screen, we need to move the camera in that direction
        if(_mMousePosition.x < mBoundsTolerance.x)
        {
            _mCameraTranslation.x = -1f;
        } else if (_mMousePosition.x > 1f - mBoundsTolerance.x)
        {
            _mCameraTranslation.x = 1f;
        }

        if (_mMousePosition.z < mBoundsTolerance.y)
        {
            _mCameraTranslation.z = -1f;
        }
        else if (_mMousePosition.z > 1f - mBoundsTolerance.y)
        {
            _mCameraTranslation.z = 1f;
        }
        
        _mCameraTransform.localPosition += Time.deltaTime * _mCameraTranslation * mCameraSpeed;

    }
}