Keeping Scrollable Area Map Within A Boundary

Hello all.

So, I’ve created an area map in the game that I’m working on, using a top down, orthographic camera. Now, I have implemented touch scrolling with the following code:

//see if player scrolled the map
private void HandleScroll()
{		
	if (Input.touchCount == 1  Input.GetTouch(0).phase == TouchPhase.Moved)
	{			
		Vector2 touchDeltaPosition = Input.GetTouch(0).deltaPosition;
		float x = touchDeltaPosition.x * scrollModifier;
		float y = touchDeltaPosition.y * scrollModifier;
			
		camera.transform.Translate(-x, -y, 0);
	}
}

Now, besides some precision issues (which I can deal with later), it works just fine, except for one problem: it doesn’t stay with a boundary.

The way I wanted to implement this was to essentially create an invisible plane object to act as the boundary, and then when running the touch logic, check to see if the camera is outside of the boundary, and if so, correct it. The problem I’m having is conceptualizing how to determine this.

For instance, my first thought was to get the destination position of the touch, and do the checking there, but then I realized that I really need to know if the camera view itself (viewport?) is within the boundary. So, I need to be able to calculate the following:

  1. The world space coordinates of each side of the camera camera (north, south, east, west). However, how can I do this if all I have is the coordinates of where the camera will be?

  2. If I can get 1), then I can calculate if it’s outside of the boundary.

  3. If say the east side of the camera view ends up at 260.0, and the boundary is at 250.0, I need to calculate where the camera itself should be moved to, relative to the 250.0 boundary point.

Any ideas/thoughts/questions/comments would be greatly appreciated.

Thank you.

Mathf.Clamp

@ willc

Thanks for that, I’m sure I’ll be making use of that function when all is said and done.


So, I currently have some basic code working that will detect if the right side of the camera’s edge is crossing the boundary or not. Bear in mind, I do this check before I apply Transform.Translate:

//see if player scrolled the map
private void HandleScroll()
{		
	if (Input.touchCount == 1  Input.GetTouch(0).phase == TouchPhase.Moved)
	{	
		//get distance to move
		Vector2 touchDeltaPosition = Input.GetTouch(0).deltaPosition;
		float x = touchDeltaPosition.x * scrollModifier;
		float y = touchDeltaPosition.y * scrollModifier;
			
                //get delta position in world coordinates
		Vector3 touchDeltaWorldPosition = camera.ScreenToWorldPoint(new Vector3(touchDeltaPosition.x, touchDeltaPosition.y, camera.transform.position.z));

		//check map boundary
		Rect r = camera.pixelRect;			
		Vector3 worldRightEdge = camera.ScreenToWorldPoint(new Vector3(r.xMax, r.yMax, camera.transform.position.z));
		float worldRightEdgeDestination = worldRightEdge.z + touchDeltaWorldPosition.z;

                if (worldRightEdgeDestination >= 99.95072)
                {
                        Debug.Log("Reached Boundary!");
                        x = 0;
                }
                else
                        Debug.Log.("Haven't Reached Boundary!");

                
         	camera.transform.Translate(-x, -y, 0);
        }
}

Now, the boundary check itself seems to be working right. As I slowly scroll the map over, as soon as it hits the boundary, I get the right debug output. However, what is surprising to me is that the panning isn’t constrained at that point. I mean, since I’m setting “x” to zero once it’s been crossed, all horinzontal movement should stop, yet, I’m able to continue moving. However, I am not allowed to scroll left for some reason.

Is it something about the delta position I’m not understanding? My understanding is that it simply returns how much the movement has been, presumably positive numbers when moving right, negative when moving left. However, when I output that value, it seems to be all over the place (both positive/negative no matter which way I scroll.

Anyone have any ideas?

Thanks.

Would you be able to use a simple Collider Setup, and stop the camera from going out of bounds during OnCollisonEnter ?
Sorry If I did not understand the question correctly?

Hello joshimoo.

So, the problem is (at least as I see it), is I’m not checking to see if the “camera” itself has moved past the boundary, but rather, the borders of the camera (since the camera is in top down mode). So, this border as I understand it is more abstract than concrete, so I can’t just do a collider check on it. That’s why I have the above code, trying to calculate where the border of it is, and then checking to see if it crossed the boundary.

I hope I’m making sense, and using the right terminology. When I say border of the camera, I mean the edges (north, east, south, west). Not sure what the Unity technical term for it is, so I apologize if I am mis-using it.

Figured I would go ahead and post my solution. To be specific, I’m doing the following:

  • getting the delta of the scroll
  • detecting where the edge of the camera will be if that delta is applied
  • checking to see if the edge of the camera + delta (destination) will cross the boundary
  • if the boundary is crossed, then the delta is set to 0 so the camera cannot be scrolled in that particular direction anymore

Here’s the main code (removed things not needed for his topic):

using UnityEngine;
using System.Collections;

// place this object in each scene that has an area map

[RequireComponent(typeof(Camera))]

public class AreaMap : MonoBehaviour
{		
	//boundary coordinates
	public float eastBoundary;
	public float westBoundary;
	public float northBoundary;
	public float southBoundary;
	
	private Boundary boundary;
	
	//see if player scrolled the map
	private void HandleScroll()
	{		
		//check for player moving finger across screen
		if (Input.touchCount == 1  Input.GetTouch(0).phase == TouchPhase.Moved)
		{	
			//get distance to move
			Vector2 positionDelta = GetPositionDeltaWithinBoundary(Input.GetTouch(0).deltaPosition);
			
			//do translation
			camera.transform.Translate(-(positionDelta.x), -(positionDelta.y), 0);
		}
	}
	
	//this will get a position delta within the confines of a boundary
	//if it is not within the boundary, then the corresponding x/y value
	//will be locked, so movement will be stopped in that direction
	private Vector2 GetPositionDeltaWithinBoundary(Vector2 inDelta)
	{
		//get distance to move
		Vector2 touchDeltaPosition = Input.GetTouch(0).deltaPosition;
		float x = touchDeltaPosition.x * scrollModifier;
		float y = touchDeltaPosition.y * scrollModifier;	
		
		//get camera in pixls
		Rect r = camera.pixelRect;		
			
		//create boundary object
		boundary = new Boundary(camera, eastBoundary, westBoundary, northBoundary, southBoundary);
		
		//get x/y coordinates within the boundary
		x = GetAxisDeltaWithinBoundary(x, r.xMin, r.xMax, boundary.xMin, boundary.xMax);
		y = GetAxisDeltaWithinBoundary(y, r.yMin, r.yMax, boundary.yMin, boundary.yMax);
		
		return new Vector2(x, y);
	}
	
	//this will check the boundary for a particular x or y coordinate
	private float GetAxisDeltaWithinBoundary(float axisDelta, float recMin, float recMax, float boundaryMinPixels, float boundaryMaxPixels)
	{		
		//get where edge of camera will have moved if full translate is done
		float boundaryMinPixelsDestination = recMin - Mathf.Abs(axisDelta);
		float boundaryMaxPixelsDestination = recMax + Mathf.Abs(axisDelta);
		
		//check to see if you're within the border
		if ((boundaryMinPixelsDestination <= boundaryMinPixels  axisDelta > 0) ||
		    (boundaryMaxPixelsDestination >= boundaryMaxPixels  axisDelta < 0))
		{
			axisDelta = 0;
		}
		return axisDelta;
	}
	
	//Helper class to encapsulate min/max values for each edge of Boundary.
	private class Boundary
	{
		public float xMin;
		public float xMax;
		public float yMin;
		public float yMax;
		
		public Boundary(Camera camera, float east, float west, float north, float south)
		{
			xMax = camera.WorldToScreenPoint(new Vector3(0, 0, east)).x;
			xMin = camera.WorldToScreenPoint(new Vector3(0, 0, west)).x;
			yMax = camera.WorldToScreenPoint(new Vector3(north, 0, 0)).y;
			yMin = camera.WorldToScreenPoint(new Vector3(south, 0, 0)).y;
		}
		
		public override string ToString()
		{
			return "xMin: " + xMin + " xMax: " + xMax + " yMin: " + yMin + " yMax: " + yMax;
		}
	}
}

There is a bug where the boundary may be crossed by a few pixels, which I’m working on, but the core of this is done (finally!).