Unity3D相机控制

实现目标:

  1. 视野随着鼠标指针进行上下左右移动
  2. 鼠标滚轮控制视野缩放

实现思路:

将摄像机作为其它游戏对象的子物体,通过获取鼠标输入控制其父节点的旋转、移动来实现相机的视野移动和缩放。

层次结构如下:

Unity3D相机控制

CameraCrl节点主要是控制视野左右移动,通过旋转其Y轴即可,该节点的position会和跟随目标的position保持一致

Unity3D相机控制

Zoom节点主要是负责控制视野缩放,向跟随目标移动即可,这里一般会做一个缩放距离限制,首先我们会将Zoom的Z轴对准目标节点

Unity3D相机控制

Pivot主要负责视野上下移动,同样需要作限制

Unity3D相机控制

接下来就是通过代码获取输入,控制这几个参数:

PlayerCameraFreeLook类实现跟随主角以及获取输入控制视野(来自官方DEMO),挂载在CameraCrl上

using System;
using UnityEngine;

public class PlayerCameraFreeLook : PlayerCamera
{
    // This script is designed to be placed on the root object of a camera rig,
    // comprising 3 gameobjects, each parented to the next:

    // 	Camera Rig
    // 		Pivot
    // 			Camera
    // This script is designed to be placed on the root object of a camera rig,
    // comprising 3 gameobjects, each parented to the next:
    protected Transform m_Zoom;
    protected Transform m_Pivot; // the point at which the camera pivots around
    protected Vector3 m_LastTargetPosition;

    [SerializeField] private float m_MoveSpeed = 1f;                      // How fast the rig will move to keep up with the target's position.
    [Range(0f, 10f)] [SerializeField] private float m_TurnSpeed = 1.5f;   // How fast the rig will rotate from user input.
    [SerializeField] private float m_TurnSmoothing = 0.0f;                // How much smoothing to apply to the turn input, to reduce mouse-turn jerkiness
    [SerializeField] private float m_TiltMax = 75f;                       // The maximum value of the x axis rotation of the pivot.
    [SerializeField] private float m_TiltMin = 45f;                       // The minimum value of the x axis rotation of the pivot.
    [SerializeField] private int m_ZoomMax = 10;
    [SerializeField] private int m_ZoomMin = 2;
    [SerializeField] private bool m_LockCursor = false;                   // Whether the cursor should be hidden and locked.
    [SerializeField] private bool m_VerticalAutoReturn = false;           // set wether or not the vertical axis should auto return
    [SerializeField] private float m_ZoomSpeed=5.0f;

    private float m_LookAngle;                    // The rig's y axis rotation.
    private float m_TiltAngle;                    // The pivot's x axis rotation.
    private const float k_LookDistance = 100f;    // How far in front of the pivot the character's look target is.
    private Vector3 m_PivotEulers;
    private Quaternion m_PivotTargetRot;
    private Quaternion m_TransformTargetRot;

    protected override void Start()
    {
        m_Pivot = cameraComponent.transform.parent;
        m_Zoom = cameraComponent.transform.parent.parent;

        // Lock or unlock the cursor.
        Cursor.lockState = m_LockCursor ? CursorLockMode.Locked : CursorLockMode.None;
        Cursor.visible = !m_LockCursor;
        m_PivotEulers = m_Pivot.rotation.eulerAngles;

        m_PivotTargetRot = m_Pivot.transform.localRotation;
        m_TransformTargetRot = transform.localRotation;


        base.Start();
    }


    protected void Update()
    {
        HandleRotationMovement();
        HandleZoomMovment();
        if (m_LockCursor && Input.GetMouseButtonUp(0))
        {
            Cursor.lockState = m_LockCursor ? CursorLockMode.Locked : CursorLockMode.None;
            Cursor.visible = !m_LockCursor;
        }
    }


    private void OnDisable()
    {
        Cursor.lockState = CursorLockMode.None;
        Cursor.visible = true;
    }


    protected override void FollowTarget(float deltaTime)
    {
        if (character == null) return;
        // Move the rig towards target position.
        transform.position = Vector3.Lerp(transform.position, character.position, deltaTime * m_MoveSpeed);
    }

    private void HandleZoomMovment()
    {
        if (Time.timeScale < float.Epsilon)
            return;
        var z = Input.GetAxis("Mouse ScrollWheel");
        float dist = (m_Zoom.position - character.position).magnitude;
        dist += m_ZoomSpeed * -z;
        dist = Mathf.Clamp(dist, m_ZoomMin, m_ZoomMax);
        m_Zoom.position = Vector3.Lerp(m_Zoom.position,character.position - m_Zoom.forward * dist,m_ZoomSpeed*Time.deltaTime);
    }
    private void HandleRotationMovement()
    {
        if (Time.timeScale < float.Epsilon)
            return;
     
        // Read the user input
        var x = Input.GetAxis("Mouse X");
        var y = Input.GetAxis("Mouse Y");

   
        // Adjust the look angle by an amount proportional to the turn speed and horizontal input.
        m_LookAngle += x * m_TurnSpeed;

        // Rotate the rig (the root object) around Y axis only:
        m_TransformTargetRot = Quaternion.Euler(0f, m_LookAngle, 0f);

        if (m_VerticalAutoReturn)
        {
            // For tilt input, we need to behave differently depending on whether we're using mouse or touch input:
            // on mobile, vertical input is directly mapped to tilt value, so it springs back automatically when the look input is released
            // we have to test whether above or below zero because we want to auto-return to zero even if min and max are not symmetrical.
            m_TiltAngle = y > 0 ? Mathf.Lerp(0, -m_TiltMin, y) : Mathf.Lerp(0, m_TiltMax, -y);
        }
        else
        {
            // on platforms with a mouse, we adjust the current angle based on Y mouse input and turn speed
            m_TiltAngle -= y * m_TurnSpeed;
            // and make sure the new value is within the tilt range
            m_TiltAngle = Mathf.Clamp(m_TiltAngle, -m_TiltMin, m_TiltMax);
        }

        // Tilt input around X is applied to the pivot (the child of this object)
        m_PivotTargetRot = Quaternion.Euler(m_TiltAngle, m_PivotEulers.y, m_PivotEulers.z);

        if (m_TurnSmoothing > 0)
        {
            m_Pivot.localRotation = Quaternion.Slerp(m_Pivot.localRotation, m_PivotTargetRot, m_TurnSmoothing * Time.deltaTime);
            transform.localRotation = Quaternion.Slerp(transform.localRotation, m_TransformTargetRot, m_TurnSmoothing * Time.deltaTime);
        }
        else
        {
            m_Pivot.localRotation = m_PivotTargetRot;
            transform.localRotation = m_TransformTargetRot;
        }
    }
}

父类PlayerCamera实现主要是获取跟随目标,以及根据不同的生命周期模式跟随目标:

using System;
using UnityEngine;

public abstract class PlayerCamera : MonoBehaviour
{
    public enum UpdateType // The available methods of updating are:
    {
        FixedUpdate, // Update in FixedUpdate (for tracking rigidbodies).
        LateUpdate, // Update in LateUpdate. (for tracking objects that are moved in Update)
        ManualUpdate, // user must call to update camera
    }

    [SerializeField] protected Transform character;            // The target object to follow
    [SerializeField] private bool m_AutoTargetPlayer = true;  // Whether the rig should automatically target the player.
    [SerializeField] private UpdateType m_UpdateType;         // stores the selected update type

    [HideInInspector]
    public Camera cameraComponent;

    protected virtual void Awake()
    {
        cameraComponent = GetComponentInChildren<Camera>();
    }

    protected virtual void Start()
    {
        // if auto targeting is used, find the object tagged "Player"
        // any class inheriting from this should call base.Start() to perform this action!
        if (m_AutoTargetPlayer)
        {
            FindAndTargetPlayer();
        }
        if (character == null) return;
 
    }


    private void FixedUpdate()
    {
        // we update from here if updatetype is set to Fixed, or in auto mode,
        // if the target has a rigidbody, and isn't kinematic.
        if (m_AutoTargetPlayer && (character == null || !character.gameObject.activeSelf))
        {
            FindAndTargetPlayer();
        }
        if (m_UpdateType == UpdateType.FixedUpdate)
        {
            FollowTarget(Time.deltaTime);
        }
    }


    private void LateUpdate()
    {
        // we update from here if updatetype is set to Late, or in auto mode,
        // if the target does not have a rigidbody, or - does have a rigidbody but is set to kinematic.
        if (m_AutoTargetPlayer && (character == null || !character.gameObject.activeSelf))
        {
            FindAndTargetPlayer();
        }
        if (m_UpdateType == UpdateType.LateUpdate)
        {
            FollowTarget(Time.deltaTime);
        }
    }


    public void ManualUpdate()
    {
        // we update from here if updatetype is set to Late, or in auto mode,
        // if the target does not have a rigidbody, or - does have a rigidbody but is set to kinematic.
        if (m_AutoTargetPlayer && (character == null || !character.gameObject.activeSelf))
        {
            FindAndTargetPlayer();
        }
        if (m_UpdateType == UpdateType.ManualUpdate)
        {
            FollowTarget(Time.deltaTime);
        }
    }

    protected abstract void FollowTarget(float deltaTime);


    public void FindAndTargetPlayer()
    {
        // auto target an object tagged player, if no target has been assigned
        var targetObj = GameObject.FindGameObjectWithTag("Player");
        if (targetObj)
        {
            SetTarget(targetObj.transform);
        }
    }


    public virtual void SetTarget(Transform newTransform)
    {
        character = newTransform;
    }


    public Transform Target
    {
        get { return character; }
    }
}

接下来只需要将需要跟随的目标的Tag设置成Player即可实现跟随视野控制效果:

Unity3D相机控制