Restrict Camera Pan

I know there was a lot if question like this. But usually there are about player movement and simple camera restriction by clamping bounds in the inspector.

Here is what I am trying to do. I want the centre (Vector3.zero) was always inside camera frustum at specific distance. I am using Perspective camera.

So I managed to find width and height of the camera frustum at specific distance from the camera. And I can detect when target ( centre of the world is outside the frustum)

The question I have is to how to clamp camera movement?
To move the camera I am basically moving parent game object using Vector3.Lerp. It looks good :slight_smile:

Here is my Camera Pan:

/ Moves camera controller rig object
    public void CameraPan(Vector3 zeroTouchPosition, Vector3 firstTouchPosition)
    {
        // Find mid point between two touches
        twoTouchesMidPoint = (zeroTouchPosition + firstTouchPosition) / 2;
        // Get touch end position
        twoTouchesEnd = Utils.GetMouseWorldPosition(twoTouchesMidPoint);
        // Calculate touch vector direction
        Vector3 touchDirection = twoTouchesStart - twoTouchesEnd;
        // Pan by moving camera rig
        transform.position = Vector3.Lerp(transform.position, transform.position + touchDirection, panSpeed * Time.deltaTime);
    }

And this how I detect if object is outside Camera Frustum:

 private void CameraMovementWithRestriction()
    {
        Vector3 worldCentre = Vector3.zero;
        //Finding a frustum plane that include object position point
        //The vector from camera to frustum plane
        Vector3 cameraToFrustum = mainCamera.transform.forward;
        //Debug.DrawRay(mainCamera.transform.position, cameraToFrustum * 10000, Color.red, 10000);

        //Vector between camera and object
        Vector3 cameraToObject = worldCentre - Camera.main.transform.position;
       // Debug.DrawRay(worldCentre, cameraToObject, Color.green, 10000);

        //The angle dot product between center of frustum and object postion
        float angleDot = Vector3.Dot(cameraToFrustum, cameraToObject) / (cameraToFrustum.magnitude * cameraToObject.magnitude);
        //Debug.Log("Dot producte between Frustum centre and target: " + angleDot);

        //Distance between camera and frustum
        float distanceCameraToFrustum = cameraToObject.magnitude * angleDot;
        //Debug.Log("Distance: " + distanceCameraToFrustum);

        //Center of frustum plane
        Vector3 frustumPlaneCentre = mainCamera.transform.position + distanceCameraToFrustum * (cameraToFrustum / cameraToFrustum.magnitude);
        //Debug.Log("Frustum Centre: " + frustumPlaneCentre);

        //Vector between center of frustum and object
        Vector3 FrustumToObject = worldCentre - frustumPlaneCentre;
        //Debug.DrawRay(frustumPlaneCentre, FrustumToObject, Color.blue, 10000);

        //Width And Height of the distance between center of frustum and object in the screen
        float width = Vector3.Dot(FrustumToObject, mainCamera.transform.right);
        float height = Vector3.Dot(FrustumToObject, mainCamera.transform.up);


        //Frustum Width and Height
        float frustumHeight = 2.0f * distanceCameraToFrustum * Mathf.Tan(mainCamera.fieldOfView * 0.5f * Mathf.Deg2Rad);
        float frustumWidth = frustumHeight * mainCamera.aspect;

        //Check if the object is out of the screen
        if (((Mathf.Abs(width) + panBoundsBufferRadius) >= frustumWidth * 0.5f) || ((Mathf.Abs(height) + panBoundsBufferRadius) >= frustumHeight * 0.5f))
        {
            //Debug.Log("OUTSIDE");

        }

    }

Camera stuff is pretty tricky… you may wish to consider using Cinemachine from the Unity Package Manager.

Otherwise if you want to debug what you have above, try this approach:

You must find a way to get the information you need in order to reason about what the problem is.

What is often happening in these cases is one of the following:

  • the code you think is executing is not actually executing at all
  • the code is executing far EARLIER or LATER than you think
  • the code is executing far LESS OFTEN than you think
  • the code is executing far MORE OFTEN than you think
  • the code is executing on another GameObject than you think it is
  • you’re getting an error or warning and you haven’t noticed it in the console window

To help gain more insight into your problem, I recommend liberally sprinkling Debug.Log() statements through your code to display information in realtime.

Doing this should help you answer these types of questions:

  • is this code even running? which parts are running? how often does it run? what order does it run in?
  • what are the values of the variables involved? Are they initialized? Are the values reasonable?
  • are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

Knowing this information will help you reason about the behavior you are seeing.

If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

You could also just display various important quantities in UI Text elements to watch them change as you play the game.

If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer or iOS: How To - Capturing Device Logs on iOS or this answer for Android: How To - Capturing Device Logs on Android

Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

Here’s an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

You can probably wrap your Vector3.Lerp in a Mathf.Clamp

Thank you guys. I will give it another shot.

But essentially the code works. It just trying to find a simple way to stop the Lerp movement when I am outside the bounds.
This tells me when Vector3.zero is outside:

//Check if the object is out of the screen
        if (((Mathf.Abs(width) + panBoundsBufferRadius) >= frustumWidth * 0.5f) || ((Mathf.Abs(height) + panBoundsBufferRadius) >= frustumHeight * 0.5f))
        {
            //Debug.Log("OUTSIDE");

        }

And this, moves my camera:

// Pan by moving camera rig
        transform.position = Vector3.Lerp(transform.position, transform.position + touchDirection, panSpeed * Time.deltaTime);

So typically I would use Math.Clamp for x, z of my camera parent object. But I need world coordinates for it.
Unless there is a smart way to override objects position when it reaches the edge of screen?

Then try what Dex suggests above… clamp it.

You may wish to make that code a little more friendly to the eyes.

If you have more than one or two dots (.) in a single statement, you’re just being mean to yourself.

How to break down hairy lines of code:

http://plbm.com/?p=248

Break it up, practice social distancing in your code, one thing per line please.

1 Like

Thought to do something like this:

 // Set min and max for camera clamp
        float minPositionX;
        float maxPositionX;
        float minPositionZ;
        float maxPositionZ;

        // Get min and max X positions
        if ((Mathf.Abs(width) + panBoundsBufferRadius) >= frustumWidth * 0.5f)
        {
            minPositionX = Mathf.Abs(target.position.x) * -1;
            maxPositionX = Mathf.Abs(target.position.x);
        }
        // Get min and max Z positions
        if ((Mathf.Abs(height) + panBoundsBufferRadius) >= frustumHeight * 0.5f)
        {
            minPositionZ = Mathf.Abs(target.position.z) * -1;
            maxPositionZ = Mathf.Abs(target.position.z);
        }

        // Restrict Camera parent object movement
        transform.position = new Vector3(
            Mathf.Clamp(transform.position.x, minPositionX, maxPositionX),
            transform.position.y,
            Mathf.Clamp(transform.position.z, minPositionZ, maxPositionZ));

Hi guys. I am trying a bit more simpler way to limit camera movement. So the world centre is never outside the screen.
I am using Viewport Coordinates.
One question. Because I am using Lerp I am over shooting the limit in if statement ?
Is there a way to fix it?

// Scene target
        Vector3 target = Vector3.zero;

        // Target position in Viewport coordinates
        Vector2 pos = mainCamera.WorldToViewportPoint(target);

        // Limit camera rig movement if taget is not within viwport bounds
        if(pos.x > 0.0f && pos.x < 1.0f)
        {
            //Pan by moving camera rig
            cameraRig.position = Vector3.Lerp(cameraRig.position, desiredPosition, panSpeed * Time.deltaTime);
        }

Use Mathf.Clamp01 to clamp the x coordinate of the return value of Vector3.Lerp before assigning it to cameraRig.position. You wouldn’t need the if statement after that either.

Thank you @Dextozz ,
Good idea, but my camera position (Lerp) is in world coordinates. Target (pos) position is in Viewport coordinates.
I am moving camera not the target. I don’t want the target to be outside the screen
So if I would clamp Lerp it will not move the camera

Or am I missing something ?