RTS Top-Down Map-Constrained Camera

I’m an extremely new coder, but I feel like some people could benefit from my little camera script. Believe it or not, I’ve actually spent around 15 hours trying to get a working camera controller, and after many iterations I created this.

This scrolls smoothly, uses both the mouse and the keyboard, and can zoom in or out using the mouse wheel.

There are also camera constrains, so that the camera can not escape the bounds of a rectangular map.

I would appreciate any suggestions for improvement, and if you find it useful yourself just mention it in the comments! I don’t think this is nearly complicated enough to be worth selling, but I hope it makes someone happy :slight_smile:

/* 
RTS Camera Controller
By Wyko ter Haar
Moves the camera within the bounds of the map variables.
An origin point of 0,0 is assumed. 
*/

// The camera bounds
var mapMinX : int;
var mapMinZ : int;
var mapMaxX : int;
var mapMaxZ : int;

// Zoom limits for the camera
var mapMaxY : int = 9.5;
var mapMinY : int = 4;

var scrollArea = 7; // Defines the distance from the edge of the window that mouse scrolling starts
var scrollSpeed = 22; // Defines how fast the window scrolls


// Translates the camera
function moveMe(myDir, mySpeed) {
	
	switch (myDir)
	{
		case ("Left") :
			myVector = (Vector3(mySpeed,0,0) * scrollSpeed * Time.deltaTime);
			break;
			
		case ("Right") : 	
			myVector = (Vector3(mySpeed,0,0) * scrollSpeed * Time.deltaTime);		
			break;
			
		case ("Forwards") : 		
			myVector = (Vector3(0,0,mySpeed) * scrollSpeed * Time.deltaTime);		
			break;
			
		case ("Backwards") : 	
			myVector = (Vector3(0,0,mySpeed) * scrollSpeed * Time.deltaTime);		
			break;
			
		case ("Up") :
			myVector = (Vector3(0,mySpeed,0));		
			break;
		
		case ("Down") :
			myVector = (Vector3(0,mySpeed,0));		
			break;
		
		default : Debug.Log("Can't Move.");
	}
	
	if (InBounds(myVector))
		{transform.Translate(myVector, Space.World);}

		
	
}
	
function Update () {
	
	var mPosX = Input.mousePosition.x;
	var mPosY = Input.mousePosition.y;
	

	// Do camera movement by mouse position
	if (mPosX < scrollArea) {moveMe("Left", -1);}
	if (mPosX >= Screen.width-scrollArea) {moveMe("Right", 1);}
	if (mPosY >= Screen.height-scrollArea) {moveMe("Forwards", 1);}
	if (mPosY < scrollArea) {moveMe("Backwards", -1);} 
	
	// Do camera movement by keyboard
	if (Input.GetAxis("Horizontal") < 0) {moveMe("Left", Input.GetAxis("Horizontal"));}
	if (Input.GetAxis("Horizontal") > 0) {moveMe("Right", Input.GetAxis("Horizontal"));}
	if (Input.GetAxis("Vertical") > 0) {moveMe("Forwards", Input.GetAxis("Vertical"));}
	if (Input.GetAxis("Vertical") < 0) {moveMe("Backwards", Input.GetAxis("Vertical"));}
	
	// Zoom Camera in or out
	if (Input.GetAxis("Mouse ScrollWheel") < 0) {
		moveMe("Up", .2);
	}
	if (Input.GetAxis("Mouse ScrollWheel") > 0) {
		moveMe("Down", -.2);
	}
}

// Checks to see if the camera would be in bounds after the move
// if not, it brings the camera back to the edge of the bounds
function InBounds (vector : Vector3) : boolean {
	var answer : boolean = true;
	
	if ((transform.position.x + vector.x) < mapMinX) {
		transform.position.x = mapMinX;
		answer = false;
	}
	if ((transform.position.z + vector.z) < mapMinZ) {
		transform.position.z = mapMinZ;
		answer = false;
	}
	if ((transform.position.x + vector.x) > mapMaxX) {
		transform.position.x = mapMaxX;
		answer = false;
	}
	if ((transform.position.z + vector.z) > mapMaxZ) {
		transform.position.z = mapMaxZ;
		answer = false;
	}
	if ((transform.position.y + vector.y) > mapMaxY) {
		transform.position.y = mapMaxY;
		answer = false;
	}
	
	if ((transform.position.y + vector.y) < mapMinY) {
		transform.position.y = mapMinY;
		answer = false;
	}
	
	return answer;
}

610457–21711–$RTSCameraControllerEasyConstrain.js (3.09 KB)

Saved me time thanks.

Here is the c# version of the code modified a bit

using UnityEngine;
using System.Collections;

public class CamController : MonoBehaviour {

	public float verticalScrollArea = 10f;
	public float horizontalScrollArea = 10f;
	public float verticalScrollSpeed = 10f;
	public float horizontalScrollSpeed = 10f;
	
	public void EnableControls(bool _enable)
	{
		if(_enable)
		{
			ZoomEnabled = true;
			MoveEnabled = true;
		}
		else
		{
			ZoomEnabled = false;
			MoveEnabled = false;
		}
	}
	public bool ZoomEnabled = true;
	public bool MoveEnabled = true;
	
	private Vector2 _mousePos;
	private Vector3 _moveVector;
	private int _xMove;
	private int _yMove;
	private int _zMove;
	// Use this for initialization
	void Start () {
	
	}
	
	// Update is called once per frame
	void Update () {
		_mousePos = Input.mousePosition;
		
		//Move camera if mouse is at the edge of the screen
		if(MoveEnabled)
		{
			if(_mousePos.x < horizontalScrollArea) { _xMove = -1; }
			else if (_mousePos.x >= Screen.width - horizontalScrollArea) { _xMove = 1; }
			else { _xMove = 0; }
				
			
			if(_mousePos.y < verticalScrollArea) { _zMove = -1; }
			else if(_mousePos.y >= Screen.height - verticalScrollArea) { _zMove = 1; }
			else { _zMove = 0; }
		}
		else
		{
			_xMove = 0;
			_yMove = 0;
		}
		// Zoom Camera in or out
		if(ZoomEnabled)
		{
			if (Input.GetAxis("Mouse ScrollWheel") < 0) {
				_yMove = 1;
			}
			else if (Input.GetAxis("Mouse ScrollWheel") > 0) {
				_yMove = -1;
			}
			else
			{
				_yMove = 0;
			}
		}
		else
		{
			_zMove = 0;
		}
		//move the object
		MoveMe(_xMove, _yMove, _zMove);
	}
		
	private void MoveMe(int x, int y, int z)
	{
		_moveVector = (new Vector3(x * horizontalScrollSpeed,
		                                 y * verticalScrollSpeed, z * horizontalScrollSpeed) * Time.deltaTime);
		transform.Translate(_moveVector, Space.World);
	}
}

Here’s the C# version to also include movement with WASD and Arrow keys (makes use of Input.GetAxis).

Please correct if wrong, this is actually first thing I’ve ever done in Unity, but it seems to work well for me.

You can now set public bool CombinedMovement.

With it set to true (default), pressing D and moving mouse over right edge would double scroll speed, just as trying to go in two opposite directions would cancel the axis altogether.

With it set to false, moving with the keyboard will overwrite any movement of the mouse. This behavior can be reversed (mouse overwrites keyboard) by re-arranging the Update function a little.

I am currently working on constraining the camera to a GameObject (eg locking it so it doesn’t go too much past the bounds of the plane I’m using for my world). Forgive me if I forget to post it here, just message and I will reply if I have progress. This is an old thread, but I did just find it today and it was immensely helpful, so maybe there’s someone else out there that could benefit from this.

using UnityEngine;
using System.Collections;

public class CameraController : MonoBehaviour {

    public float verticalScrollArea = 10f;
    public float horizontalScrollArea = 10f;
    public float verticalScrollSpeed = 10f;
    public float horizontalScrollSpeed = 10f;
	
    public void EnableControls(bool _enable) {

        if(_enable) {
            ZoomEnabled = true;
            MoveEnabled = true;
			CombinedMovement = true;
        } else {
			ZoomEnabled = false;
			MoveEnabled = false;
			CombinedMovement = false;
		}
    }

    public bool ZoomEnabled = true;
    public bool MoveEnabled = true;
	public bool CombinedMovement = true;

	private Vector2 _mousePos;
	private Vector3 _moveVector;
	private float _xMove;
	private float _yMove;
	private float _zMove;
	
	void Update () {
		_mousePos = Input.mousePosition;
		
		//Move camera if mouse is at the edge of the screen
        if (MoveEnabled) {
			
			//Move camera if mouse is at the edge of the screen
            if (_mousePos.x < horizontalScrollArea)
			{
				_xMove = -1;
			}
			else if (_mousePos.x >= Screen.width - horizontalScrollArea) {
				_xMove = 1;
			}
            else {
				_xMove = 0;
			}
			
            if (_mousePos.y < verticalScrollArea) {
				_zMove = -1;
			}
            else if (_mousePos.y >= Screen.height - verticalScrollArea) {
				_zMove = 1;
			}
            else {
				_zMove = 0;
			}
			
			//Move camera if wasd or arrow keys are pressed
			float xAxisValue = Input.GetAxis("Horizontal");
		    float zAxisValue = Input.GetAxis("Vertical");
			
			if (xAxisValue != 0) {
				if (CombinedMovement) {
					_xMove += xAxisValue;
				}
				else {
					_xMove = xAxisValue;
				}
			}
			
			if (zAxisValue != 0) {
				if (CombinedMovement) {
					_zMove += zAxisValue;
				}
				else {
					_zMove = zAxisValue;
				}
			}
 
        }
		else {
            _xMove = 0;
            _yMove = 0;
		}

        // Zoom Camera in or out
        if(ZoomEnabled) {
            if (Input.GetAxis("Mouse ScrollWheel") < 0) {
				_yMove = 1;
            }
            else if (Input.GetAxis("Mouse ScrollWheel") > 0) {
				_yMove = -1;
			}
			else {
				_yMove = 0;
			}
        }
        else {
            _zMove = 0;
        }

        //move the object
       MoveMe(_xMove, _yMove, _zMove);
    }
	
	private void MoveMe(float x, float y, float z) {
        _moveVector = (new Vector3(x * horizontalScrollSpeed,
		y * verticalScrollSpeed, z * horizontalScrollSpeed) * Time.deltaTime);
		transform.Translate(_moveVector, Space.World);
    }
}

A bit of advice I learned the hard way:
Only move camera if the condition requires it, else you are setting variables and checking if statements in EVERY frame in your update method at all times when it would not be needed.

Does this work for a perspective camera that slightly tilts upwards as it zooms in?

Very Good pal, this may be of use to me. Thanks for sharing :slight_smile:

I looked through your code, it is very thorough and well thought out, but I can think of an easier way.

Just put 4 GUITextures along the 4 edges of the screen. Then attach the same script to all 4 GUITextures. Then do something like this:

function OnMouseOver () {
if(this.gameObject.name == “Top”)
camera.transform.position.z +=1; //or y depending on how you placed the map
if(this.gameObject.name == “Bottom”)
camera.transform.position.z -=1;
//repeat for Left and right with the x axis
}

Should be able to get this prototype done in a few minutes :smile:

1 Like