Every Wednesday on Twitch I experiment with ideas I don’t normally have time to work out into my games. I don’t always expect to have usable code at the end of the stream but one recent experiment yielded a simple camera panning code I thought I would share. This type of technique works great in RPGs and RTS style games with a large map the player must move around in order to see what is going on.
The basic idea is to calculate the position difference between where the user clicks and moves the mouse in order to move the camera in a specific direction. The other constraint is to make sure the camera doesn’t travel out of bounds. After some searching I came across this Gist, which helped me with the basic foundation which I modified for my needs. Here is how it works:
To get started, I created a new script on the camera called MoveCamera. From there we need some properties to store the panning logic as well as the boundaries of the scrolling.
public float panSpeed = 4.0f;
public bool useBoundary = true;
public Vector2 boundaryMin = Vector2.zero;
public Vector2 boundaryMax = Vector2.zero;
private Vector3 mouseOrigin; // Position of cursor when mouse dragging starts
private bool isPanning;
Next I added the following to a FixedUpdate method:
// Listen for mouse right click
if (Input.GetMouseButtonDown(1))
{
// Get mouse origin
mouseOrigin = Input.mousePosition;
isPanning = true;
}
// Disable movements on button release
if (!Input.GetMouseButton(1)) isPanning = false;
// Move the camera on it's XY plane
if (isPanning)
{
Vector3 pos = Camera.main.ScreenToViewportPoint(Input.mousePosition - mouseOrigin);
Vector3 move = new Vector3(pos.x * panSpeed, pos.y * panSpeed, 0);
transform.Translate(move, Space.Self);
BoundaryCheck();
}
Here you can see we listen for the right mouse button to be down, and set the isPanning flag to true. If the mouse is released, we reset the value. It’s important to know when the mouse is down so we can use that as the origin for our movement detection logic. As you can see when we detect that isPanning is true we calculate the position of the mouse, the modifier to move, and use transform translate to make the camera move in that new direction. The last thing we need to do is make sure the camera’s position is still within bounds so we’ll need one more method to handle this check.
void BoundaryCheck()
{
if (!useBoundary)
return;
var newPos = transform.position;
newPos.x = Mathf.Clamp(newPos.x, boundaryMin.x, boundaryMax.x);
newPos.y = Mathf.Clamp(newPos.y, boundaryMin.y, boundaryMax.y);
transform.position = newPos;
}
The BoundaryCheck looks to see if useBoundar is set to true, we wouldn’t want this calculating if the boundary min and max values were set to 0. From there we get the current position of the camera, clamp its x and y values based on the min and max boundaries, then reset the value back to the camera’s transform.
Once we’ve done this we can create one last method to jump to a specific position on the screen and still stay within the bounds.
public void MoveTo(Vector2 pos)
{
transform.position = new Vector3(pos.x, pos.y, transform.position.z);
BoundaryCheck();
}
As you can see, whenever we change the camera’s position we simply call BoundaryCheck to make sure that the x and y positions are inside of the set boundaries.
At this point I needed to calculate the boundaries of my map. This was a little tricky. My map is created from a random number of tiles and each tile is 16 x 16. I start generating the tiles at the 0,0 position of the screen space which means we need to offset the camera accordingly. Take a look at what happens with the map and camera at 0,0.
To adjust for the we need to calculate half of the screen resolution and offset the camera. To do this, we need to do the following:
Camera cam = Camera.main;
float screenHeight = (2f * cam.orthographicSize);
float screenWidth = screenHeight * cam.aspect;
Here you can see we use the orthographicSize to calculate the height, then we figure out the width. Next we need to set the bounds of the camera. To do this we want to use the new camera offset as well as calculate the map size. Here is how I do this.
var tileSize = 16;
var mapWidth = (tileSize * map.width);
var mapHeight = (tileSize * map.height);
var halfScreenWidth = screenWidth/2;
var halfScreenHeight = screenHeight / 2;
moveCameraManager.boundaryMin = new Vector2(halfScreenWidth, -(mapHeight - halfScreenHeight));
moveCameraManager.boundaryMax = new Vector2(mapWidth - halfScreenWidth, - halfScreenHeight);
All I am doing here is calculating the map size based on how many tiles there are multiplied by the tile size. Next I calculate half of the screen’s width and height for the offset to make sure we align the contents of the camera to its edges. Finally, we set the min and max boundaries. One thing to note is that for the y value they will be reversed. So you’ll notice that we use negative values here since the map is being rendered below the center point of the screen space so to move the camera down we need to set it to negative y value.
All that is left to do now is set the map to a position when it’s loading up so that it centers itself on a focal point. In this case where the player starts the game. Here is how I do that:
var startTile = map.startTile.position;
startTile.x = startTile.x * tileSize + tileSize/2;
startTile.y = -((startTile.y * tileSize) + tileSize/2);
And this is the final product. Once the map is done rendering you can scroll around until you hit the edges.
If you liked this experiment and want to see more, make sure you tune into my Twitch channel every Wednesday from 9am – 12pm EST for Experimental Wednesday.
If you are a first time game developer or a seasoned pro, the Amazon Appstore is the perfect place for your latest creation. Publishing to the Amazon Appstore is free and easy, especially if you are already building Android games. We offer some great tools and services to help make your game more successful like Amazon IAP, Ads, Merch, Underground and our collection of Fire OS devices. Here are some additional links to help you gets started: