Camera control, camera follow

Time:2022-7-1

#Functions implemented

(1) Roll closer and push the camera farther (with inertia)

(2) Left mouse button, turn the camera up and down (with inertia)

(3) Camera follows character

#Functions to be realized

(1) When turning the camera, if there is an obstacle between the camera and the following character, the camera will automatically pull close

(2) When the following character walks left or right, the camera automatically rotates slowly

 

#Ground is the walkable ground (green), sphere is the non walkable area (gray), and player is the character followed by the camera (yellow)

#The eventsystem will use its drag trigger threshold

#Cameracontrol is hung on the main camera and playermove is hung on the player

 

#Camera control code

public class CameraControl : MonoBehaviour
{

    public Transform target; // Camera follows target
    private Vector3 _lastTargetPos;
    public Vector3 offset = new Vector3(0, 1.35f, 0); // Generally, the offset from the target position is only set in the Y direction (height)

    public float zDistanceMin = 2;
    public float zDistanceMax = 12;
    private float _ zDistance = 9; // Current distance between the camera and the following target

    private int _ zDistanceInertia = 0; // Inertial frames
    private float _ zDistanceInertiaStep; // Inertial stepping

    private int _ rotateInertia = 0; // Rotation inertia frames
    private float _ rotateDecelerateExp = 0.75f; // Deceleration index

    private Vector3 _lastMousePosition;
    private Vector3 _deltaMousePosition;
    private bool _drag;

    private float _xCurRotate = 5;
    public bool xRotateLock = false;
    public float xRotateMin = 5;
    public float xRotateMax = 40;

    private float _yCurRotate;
    public bool yRotateLock = false;

    private Quaternion _camRotation;
    private Vector3 _zDistanceVec3;

    void OnEnable()
    {
        UpdateCameraRotateAndPos();
    }

    void Update()
    {
        CheckZoomInertia();
        CheckRotateInertia();

        var mscroll = Input.GetAxis("Mouse ScrollWheel");
        If (mscoll < 0) //zoom out, push the camera away
        {
            if (_zDistance < zDistanceMax)
            {
                _zDistance -= mscroll;
                _zDistance = Mathf.Min(_zDistance, zDistanceMax);
                UpdateCameraPos();

                _zDistanceInertia = 15;
                _zDistanceInertiaStep = mscroll;
            }
        }
        Else if (mscroll > 0) //zoom in, pull in the camera
        {
            if (_zDistance > zDistanceMin)
            {
                _zDistance -= mscroll;
                _zDistance = Mathf.Max(_zDistance, zDistanceMin);
                UpdateCameraPos();

                _zDistanceInertia = 15;
                _zDistanceInertiaStep = mscroll;
            }
        }

        if (Input.GetMouseButtonDown(0))
        {
            _drag = false;
            _lastMousePosition = Input.mousePosition;

            //Stop inertia when pressed
            _zDistanceInertia = 0;
            _rotateInertia = 0;
        }
        else if (Input.GetMouseButton(0))
        {
            _deltaMousePosition = Input.mousePosition - _lastMousePosition;
            _lastMousePosition = Input.mousePosition;
            if (_drag)
            {
                RotateCameraByDeltaPos(ref _deltaMousePosition);
            }
            else
            {
                If (_deltamouseposition.sqrmagnitude > = mathf.sqrt (eventsystem.current.pixeldragthreshold)) // enter the drag mode only after exceeding the drag threshold
                {
                    _drag = true;
                }
            }
        }
        else if (Input.GetMouseButtonUp(0))
        {
            if (_drag)
            {
                _rotateInertia = 15;
            }
            _drag = false;
            _lastMousePosition = Input.mousePosition;
        }
    }

    ///Camera zoom inertia
    private void CheckZoomInertia()
    {
        if (_zDistanceInertia > 0)
        {
            _zDistanceInertia--;
            If (\zdistanceinertiastep < 0) // inertial pushing
            {
                if (_zDistance < zDistanceMax)
                {
                    _zDistance -= _zDistanceInertiaStep;
                    _zDistance = Mathf.Min(_zDistance, zDistanceMax);
                    UpdateCameraPos();
                }
                else
                {
                    _zDistanceInertia = 0;
                    _zDistanceInertiaStep = 0;
                }
            }
            Else if (\u zdistanceinertiastep > 0) // inertia pull in
            {
                if (_zDistance > zDistanceMin)
                {
                    _zDistance -= _zDistanceInertiaStep;
                    _zDistance = Mathf.Max(_zDistance, zDistanceMin);
                    UpdateCameraPos();
                }
                else
                {
                    _zDistanceInertia = 0;
                    _zDistanceInertiaStep = 0;
                }
            }
        }
    }

    ///Rotating camera inertia
    private void CheckRotateInertia()
    {
        if (_rotateInertia > 0)
        {
            _rotateInertia--;

            if (_deltaMousePosition.sqrMagnitude >= 0.0001f)
            {
                RotateCameraByDeltaPos(ref _deltaMousePosition);
                _deltaMousePosition *= _rotateDecelerateExp;
            }
            else
            {
                _rotateInertia = 0;
            }
        }
    }

    private void RotateCameraByDeltaPos(ref Vector3 deltaPos)
    {
        const float Scale_ Factor = 0.2f; // Zoom factor, just adjust it to look comfortable
        if (!xRotateLock)
        {
            _xCurRotate += deltaPos.y * Scale_Factor;
            _xCurRotate = Mathf.Min(Mathf.Max(xRotateMin, _xCurRotate), xRotateMax);
        }

        if (!yRotateLock)
        {
            _yCurRotate += deltaPos.x * Scale_Factor;
        }

        UpdateCameraRotateAndPos();
    }

    private void UpdateCameraRotateAndPos()
    {
        _camRotation = Quaternion.Euler(_xCurRotate, _yCurRotate, 0);
        transform.rotation = _camRotation;
        UpdateCameraPos(); // Changes in angle will also cause changes in position
    }

    private void UpdateCameraPos()
    {
        _zDistanceVec3.z = -_zDistance;

        var camPos = _camRotation * _zDistanceVec3 + target.position + offset;
        transform.position = camPos;
    }


    void LateUpdate()
    {
        //Camera follow
        var diff = target.position - _lastTargetPos;
        if (diff.sqrMagnitude >= 0.0001f)
        {
            _lastTargetPos = target.position;
            var camPos = _camRotation * _zDistanceVec3 + target.position + offset;
            transform.position = camPos;
        }
    }

}

 

#Character walking control code. WASD is manual walking. Right click the ground to go to the clicked point

public class PlayerMove : MonoBehaviour
{
    private NavMeshAgent _agent;
    ///Follow character's camera
    public Camera followCamera;

    private Vector3[] _pathCorners = new Vector3[10];

    void Start()
    {
        _agent = GetComponent();
        _agent.isStopped = true;

        if (null == followCamera)
            followCamera = Camera.main;
    }

    void Update()
    {
        if (CheckNavToClickPoint() || CheckWASDMove())
            return;

        if (_agent.enabled)
        {
            if (_agent.remainingDistance <= 0 || _agent.isStopped)
            {
                _agent.isStopped = true;
                _agent.enabled = false;
            }
            else
            {
                DrawNavMeshPath();
            }
        }
    }

    ///During the route finding process, the path is drawn
    private void DrawNavMeshPath()
    {
        if (_agent.hasPath)
        {
            var len = _agent.path.GetCornersNonAlloc(_pathCorners);
            for (var i = 1; i < len; ++i)
            {
                var p1 = _pathCorners[i - 1];
                var p2 = _pathCorners[i];
                Debug.DrawLine(p1, p2, Color.red);
            }
        }
    }

    ///Find the way to the click position
    private bool CheckNavToClickPoint()
    {
        if (Input.GetMouseButtonUp(1))
        {
            const int maxDistance = 300;
            var ray = Camera. main. ScreenPointToRay(Input.mousePosition); // One ray is emitted in the direction of the camera
            Debug. DrawRay(ray.origin, ray.direction * maxDistance, Color.yellow); // Draw this ray

            if (Physics.Raycast(ray, out var hit, maxDistance))
            {
                Debug.DrawLine(ray.origin, hit.point, Color.red);
                _agent.enabled = true;
                //_agent.Warp(transform.position);
                _agent.SetDestination(hit.point);
                return true;
            }
        }

        return false;
    }

    private bool CheckWASDMove()
    {
        var x = 0;
        if (Input.GetKey(KeyCode.W)) x = 1;
        else if (Input.GetKey(KeyCode.S)) x = -1;

        var y = 0;
        if (Input.GetKey(KeyCode.A)) y = -1;
        else if (Input.GetKey(KeyCode.D)) y = 1;

        if (0 != y || 0 != x)
        {
            If (\u agent.enabled) // you are still searching. If you operate WASD, stop searching
            {
                _agent.isStopped = true;
                _agent.ResetPath();
                _agent.enabled = false;
            }

            var relativeAngle = Mathf. Atan2(y, x) * Mathf. Rad2Deg; // The upward direction of the rocker indicates that it is 0 degrees forward from the camera, the rightward direction of the rocker indicates that it is 90 degrees forward from the camera, and so on
            var playerTransform = transform;
            var cameraForwardAngle = followCamera.transform.eulerAngles.y;
            playerTransform. rotation = Quaternion. Euler(0, cameraForwardAngle + relativeAngle, 0); // Todo: here we can consider changing it to LERP for smoothing

            playerTransform. Translate(Vector3.forward * 3 * Time.deltaTime, Space.Self); // The steering has been adjusted. Just go forward here
            return true;
        }

        return false;
    }

}