How to keep camera viewport in bounds?

I’m working on a 3D game. The camera moves in parallel to a rectangle and always looks straight down onto that rectangle. The player can move the camera in parallel to the rectanlge and can zoom in and out by moving closer or further away.

The problem is that the rectangle isn’t infinite. How can I ensure that the viewport of the camera always stays within certain bounds, i.e. how do I ensure that the player never actually sees the boundaries?

Please note that this problem is not trivial and has several influence factors:

  • The aspect ratio of the rectangle
  • The aspect ratio of the screen
  • The X/Y position of the camera with respect to the rectangle
  • The zoom level (i.e. Z position) of the camera

For example, when the player is zoomed out very far, the range of valid X/Y coordinates is much smaller than when he is zoomed all the way in. Also, if the player uses a super widescreen monitor to look on a square plane, the range is also much smaller as compared to looking at a widescreen monitor on a rectangle where width > height. One could of course hard-code boundary values, but I would much rather calculate them dynamic. Consider the width and height of the rectangle to be known, as well as the aspect ratio of the camera.

Well, it’s not that hard. I quickly came up with this method:

public static void CalculateLimits(Camera aCam, Bounds aArea, out Rect aLimits, out float aMaxHeight)
{
    var angle = aCam.fieldOfView * Mathf.Deg2Rad * 0.5f;
    Vector2 tan = Vector2.one * Mathf.Tan(angle);
    tan.x *= aCam.aspect;
    Vector3 dim = aArea.extents;
    Vector3 center = aArea.center - new Vector3(0, aArea.extents.y, 0);
    float maxDist = Mathf.Min(dim.x / tan.x, dim.z / tan.y);
    float dist = aCam.transform.position.y - center.y;
    float f = 1f - dist / maxDist;
    dim *= f;
    aMaxHeight = center.y + maxDist;
    aLimits = new Rect(center.x-dim.x, center.z - dim.z, dim.x*2, dim.z*2);
}

This methods assumes that we have a camera looking straight down ( negative y axis). If you pass in a camera and a Bounds struct which should represent your rectangle it will return the max x and z range the camera can move as Rect and also a max height / y-value for the camera. You should never move the camera above this value as the rectangle would no longer fit onto the screen in at least one direction. Whenever you change the height of the camera you have to call that method again. However if the height doesn’t change the Rect limits are constant.

Note i haven’t tested this method at all ^^. If you want an explanation of what is happening i’ve put a commented version on my pastebin

ps: Since the “Bounds” actually represents a bounding box i use the bottom of the bounds as reference. To be on the safe side you should set the extent of that box to “0” on the y axis.