Search code examples
unity-game-enginecamera

Unity3D - Move camera perpendicular to where it's facing


I'm adding the option for players to move the camera to the sides. I also want to limit how far they can move the camera to the sides.

If the camera was aligned with the axis, I could simply move around X/Z axis and set a limit on each axis as to how far it can go. But my problem is that the camera is rotated, so I'm stuck figuring out how to move it and set a limit. How could I implement this?

using UnityEngine;

[RequireComponent(typeof(Camera))]
public class CameraController : MonoBehaviour
{
    Camera cam;
    Vector3 dragOrigin;
    bool drag = false;

    void Awake()
    {
        cam = GetComponent<Camera>();
    }

    void LateUpdate()
    {
        // Camera movement with mouse
        Vector3 diff = (cam.ScreenToWorldPoint(Input.mousePosition)) - cam.transform.position;

        if (Input.GetMouseButton(0))
        {
            if (drag == false)
            {
                drag = true;
                dragOrigin = cam.ScreenToWorldPoint(Input.mousePosition);
            }
        }
        else
        {
            drag = false;
        }

        if (drag)
        {
            // Here I want to set a constraint in a rectangular plane perpendicular to camera view
            transform.position = dragOrigin - diff; 
        }
    }
}

Solution

  • It can be tricky to deal with constraints on rotated objects. The math behind this includes some vector/rotation math to figure out the correct limits relative to the object's orientation, and whether you've exceeded them.

    Luckily though, Unity gives you some shortcuts to skip this math: Transform.InverseTransformPoint() and Transform.TransformPoint()! These two methods allow you to transform a point in world space into a point in local space, and vice versa.

    That means that no matter how your camera is oriented, you can interpret a position from the orientation of the camera - and with just a couple extra steps, your X/Z constraints are usable because you can calculate X/Z from the camera's point of view.

    Let's try to adapt your current script to use this:

    using UnityEngine;
    
    [RequireComponent(typeof(Camera))]
    public class CameraController : MonoBehaviour
    {
        // Set the X and Z values in the editor to define the rectangle within
        // which your camera can move
        public Vector3 maxConstraints;
        public Vector3 minConstraints;
    
        Camera cam;
        Vector3 dragOrigin;
        bool drag = false;
        Vector3 cameraStart;
    
        void Awake()
        {
            cam = GetComponent<Camera>();
    
            // Here, we record the start since we'll need a reference to determine
            // how far the camera has moved within the allowed rectangle
            cameraStart = transform.position;
        }
    
        void LateUpdate()
        {
            // Camera movement with mouse
            Vector3 diff = (cam.ScreenToWorldPoint(Input.mousePosition)) - cam.transform.position;
    
            if (Input.GetMouseButton(0))
            {
                if (drag == false)
                {
                    drag = true;
                    dragOrigin = cam.ScreenToWorldPoint(Input.mousePosition);
                }
            }
            else
            {
                drag = false;
            }
    
            if (drag)
            {
                // Now, rather than setting the position directly, let's make sure it's
                // within the valid rectangle first
                Vector3 newPosition = dragOrigin - diff;
    
                // First, we get into the local space of the camera and determine the delta
                // between the start and possible new position
                Vector3 localStart = transform.InverseTransformPoint(cameraStart);
                Vector3 localNewPosition = transform.InverseTransformPoint(newPosition);
                Vector3 localDelta = localNewPosition - localStart;
    
                // Now, we calculate constrained values for the X and Z coordinates
                float clampedDeltaX = Mathf.Clamp(localDelta.x, minConstraint.x, maxConstraint.x);
                float clampedDeltaZ = Mathf.Clamp(localDelta.z, minConstraint.z, maxConstraint.z);
    
                // Then, we can use the constrained values to determine the constrained position
                // within local space
                Vector3 localClampedPosition = new Vector3(clampedDeltaX, localDelta.y, clampedDeltaZ)
                    + localStart;
    
                // Finally, we can convert the local position back to world space and use it
                transform.position = transform.TransformPoint(localConstrainedPosition);
            }
        }
    }
    

    Note that I'm somewhat assuming dragOrigin - diff moves your camera correctly in its present state. If it doesn't do what you want, please include details on the unwanted behaviour and we can sort that out too.