第三人称摄像机

第三人称摄像机代码:
Help


using UnityEngine;

public class Help_Qing {

    //剪切平面点
	public struct ClipPlanePoints
    {
        public Vector3 UpperLeft;
        public Vector3 UpperRight;
        public Vector3 LowerLeft;
        public Vector3 LowerRight;
    }
    //夹角
    public static float ClampAngle(float angle,float min,float max)
    {
        do
        {
            if (angle < -360)
                angle += 360;
            if (angle > 360)
                angle -= 360;
        } while (angle < -360 || angle > 360);


        return Mathf.Clamp(angle, min, max);
    }
    //近夹平面
    public static ClipPlanePoints ClipPlaneAtNear(Vector3 pos)
    {
        var clipPlanePoints = new ClipPlanePoints();

        if (Camera.main == null)
            return clipPlanePoints;
        
        //位置
        var transform = Camera.main.transform;
        //半视野
        var halfFOV = (Camera.main.fieldOfView / 2) * Mathf.Deg2Rad;
        //Mathf.Deg2Rad 角度转弧度
        var aspect = Camera.main.aspect;
        //aspect 设置摄像机视口比例
        var distance = Camera.main.nearClipPlane;
        //.nearClipPlane 近裁前面的距离
        var height = distance * Mathf.Tan(halfFOV);
        //Mathf 正切值
        var width = height * aspect;

        clipPlanePoints.LowerRight = pos + transform.right * width;
        //剪切平面的右下角 = pos + x轴 * 宽度
        clipPlanePoints.LowerRight -= transform.up * height;
        //剪切平面的右下角 -= y轴 * 高
        clipPlanePoints.LowerRight += transform.forward * distance;
        //剪切平面的右下角 += z轴 * 距离

        clipPlanePoints.LowerLeft = pos - transform.right * width;
        clipPlanePoints.LowerLeft -= transform.up * height;
        clipPlanePoints.LowerLeft += transform.forward * distance;

        clipPlanePoints.UpperRight = pos + transform.right * width;
        clipPlanePoints.UpperRight += transform.up * height;
        clipPlanePoints.UpperRight += transform.forward * distance;

        clipPlanePoints.UpperLeft = pos - transform.right * width;
        clipPlanePoints.UpperLeft += transform.up * height;
        clipPlanePoints.UpperLeft += transform.forward * distance;

        return clipPlanePoints;
    }
}

ScriptableObjectUtility(脚本表对象实用程序)

using UnityEngine;
using UnityEditor;

public class ScriptableObjectUtility_Qing
{
    public static void CreateAsset<T>() where T :ScriptableObject
    {
        var asset = ScriptableObject.CreateInstance<T>();
        //创建.asset文件
        ProjectWindowUtil.CreateAsset(asset, "New" + typeof(T).Name + ".asset");

        //public static ScriptableObject CreateInstance(string className)
        //创建脚本化对象类名为className的一个实例。
        //public static ScriptableObject CreateInstance(Type type)
        //创建脚本化对象类型为type的一个实例。

    }
}

ThirdCameraState(第三人称状态)

using UnityEngine;

[System.Serializable]
public class ThirdCameraState{

    public string Name;
    public float forward;
    public float right;
    public float maxDistance;
    public float minDistance;
    public float Height;

    public ThirdCameraState(string name)
    {
        this.Name = name;
        this.forward = -1f;
        this.right = 0.35f;
        this.maxDistance = 1.5f;
        this.minDistance = 0.5f;
        this.Height = 1.5f;
    }
}

ThirdameraListData (第三人称相机列表数据)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
//序列化:再次读取Unity序列化的变量是有值,不需要你再次去赋值,因为它已经保存下来
public class ThirdameraListData : ScriptableObject {

    [SerializeField] public string Name;
    [SerializeField] public List<ThirdCameraState> thirdCameraStates;
    //SerializeField:在Inspector面版中显示非public属性,并且序列化

    //ThirdameraListDat()方法
    //新建一ThirdCameraState("name")表
    public ThirdameraListData()
    {
        thirdCameraStates = new List<ThirdCameraState>();
        thirdCameraStates.Add(new ThirdCameraState("Default"));
    }
}

ThirdCamera (第三人称摄像机)

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityStandardAssets.CrossPlatformInput;

public class ThirdCamera : MonoBehaviour {

    private static ThirdCamera _instance;
    public static ThirdCamera instance
    {
        get
        {
            if(_instance == null)
            {
                _instance =GameObject.FindObjectOfType<ThirdCamera>();
            }
            return _instance;
        }
    }

    #region 检查面板特性
    //玩家的位置
    public Transform Player;
    //锁定目标
    public Transform lockTarget;
    //X轴的鼠标的灵敏度
    public float X_MouseSensitivity = 3f;
    //Y轴的鼠标的灵敏度
    public float Y_MouseSensitivity = 3f;
    //两数之间平滑状态
    public float SmoothBetweenState = 0.05f;
    //摄像机旋转平滑
    public float SmoothCameraRotation = 12f;
    //平滑跟随
    public float SmoothFollow = 10f;
    //Y轴的最小限制 
    public float Y_MinLimit = -40f;
    //Y轴的最大限制 
    public float Y_MaxLimit = 80f;
    //X轴的最小限制 
    public float X_MinLimit = -360f;
    //X轴的最大限制 
    public float X_MaxLimit = 360f;
    //剔除高度
    public float cullingHeight = 1f;
    //剔除层
    public LayerMask cullingLayer = 1 << 0;
    //是否锁定摄像机
    public bool lockCamera;
    //当前状态名
    public string currentStateName;
    #endregion

    #region 隐藏属性
    //目标
    [HideInInspector] public Transform target;
    //目标位置
    [HideInInspector] public Vector3 targetPos;
    //摄像机状态表
    [HideInInspector] public ThirdameraListData CameraStateList;
    //指数/标志
    [HideInInspector] public int index;
    //目标看向
    [HideInInspector] public Transform TargetLookAt;
    //距离
    [HideInInspector] public float Distance = 5f;
    //鼠标Y轴
    [HideInInspector] public float mouseY = 0f;
    //鼠标X轴
    [HideInInspector] public float mouseX = 0f;
    //当前摄像机状态
    [HideInInspector] private ThirdCameraState currentState;
    //平滑摄像机状态
    [HideInInspector] public ThirdCameraState lerpState;
    //看向的点
    [HideInInspector] public Vector3 LookPoint;
    //目标高度
    private float targetHeight;
    #endregion	

    void Start()
    {
        Init();
    }
    void Init()
    {
        //隐藏鼠标
        Cursor.visible = false;
        if (Player == null)
        {
            Debug.Log("请分配Player");
            return;
        }

        target = Player;
        targetPos = target.position;
        TargetLookAt = new GameObject("targetLookAt").transform;
        TargetLookAt.position = target.position;
        TargetLookAt.hideFlags = HideFlags.HideInHierarchy;
        //HideFlags.HideInHierarchy :隐藏在Hierarchy
        TargetLookAt.rotation = target.rotation;

        ChangeState("Normal", false);
        mouseY = target.eulerAngles.x;
        mouseX = target.eulerAngles.y;
        //返回或设置旋转的euler角度表示。
        //围绕z轴旋转euler.z度的旋转,围绕x轴旋转euler.x度,围绕y轴旋转euler.y度(

    }
    private void FixedUpdate()
    {
        if (TargetLookAt == null)
            return;

       CameraMovement();
    }
    //改变状态(名,是否平滑)
    public void ChangeState(string stateName,bool hasSmooth)
    {
        var state = CameraStateList.thirdCameraStates
            .Find(delegate (ThirdCameraState obj)
            {
                return obj.Name.Equals(stateName);
                //Equals.判断两值是不相等 
            });

        if(state !=null)
        {
            currentStateName = stateName;
            lerpState = state;
            if (currentState != null && !hasSmooth)
                currentState.CopyState(state);
            target = Player;
        }
        else
        {
            currentStateName = stateName;
            state = CameraStateList.thirdCameraStates[0];
            lerpState = state;
            if (currentState != null && !hasSmooth)
                currentState.CopyState(state);
            target = Player;
        }

        if (currentState == null)
            currentState = new ThirdCameraState("");

        index = CameraStateList.thirdCameraStates.IndexOf(state);
        //IndexOf :查找字串中指定字符或字串首次出现的位置,返首索引值

    }

    //改变状态(状态名,位置:范围目标,物理节点:获取范围图片)
    public void ChangeState(string stateName,Transform scopeTarget,SpringJoint scopeImage)
    {
        var state = CameraStateList.thirdCameraStates
            .Find(delegate (ThirdCameraState obj)
                {
                    return obj.Name.Equals(stateName);
                });

        if(scopeTarget !=null)
        {
            transform.position = scopeTarget.position;
        }

        if(state !=null)
        {
            currentStateName = stateName;
            lerpState = state;
            if (currentState != null)
                currentState.CopyState(state);

            if (scopeTarget != null)
                target = scopeTarget;
            else
                target = Player;
        }
        else
        {
            currentStateName = stateName;
            state = CameraStateList.thirdCameraStates[0];
            lerpState = state;
            if (currentState != null)
                currentState.CopyState(state);
            if (scopeTarget != null)
                target = Player;
        }

        if (currentState == null)
            currentState = new ThirdCameraState("");

        index = CameraStateList.thirdCameraStates.IndexOf(state);
     
    }

    //摄像机移动
    void CameraMovement()
    {
        currentState.Slerp(lerpState, SmoothBetweenState);

        RaycastHit hitInfo;

        Distance = currentState.maxDistance;
        targetHeight = currentState.Height;

        var camDir = (currentState.forward * TargetLookAt.forward)
            +
            (currentState.right * TargetLookAt.right);
        camDir = camDir.normalized;

        if (target == null)
            return;

        targetPos = Vector3.Slerp(targetPos, target.position, SmoothFollow * Time.deltaTime);
        var cPos = targetPos + new Vector3(0, targetHeight, 0);

        if(Physics.Raycast(cPos,camDir,out hitInfo,Distance,cullingLayer))
        {
            var t = hitInfo.distance - 0.1f;
            t -= currentState.minDistance;
            t /= (Distance - currentState.minDistance);

            targetHeight = Mathf.Lerp(cullingHeight, targetHeight, Mathf.Clamp(t, 0.0f, 1.0f));
            cPos = target.position + new Vector3(0, targetHeight, 0);

            //显示摄像机目标位置
            //Debug.DrawLine(cPos,transform.position,Color.red)
        }

        if (Physics.Raycast(cPos, camDir, out hitInfo, Distance + 0.2f,cullingLayer))
        {
            Distance = hitInfo.distance - 0.1f;
        }

        var lookPoint = cPos;

        lookPoint += (TargetLookAt.right * Vector3.Dot(camDir * Distance, TargetLookAt.right));
        //Vector3.Dot两个向量的点乘积。
        //a*b =x1 * x2 + y1 * y2 +z1 * z2
        //a*b=a*b*cos(Angle)
        //对于normalized向量,
        //如果他们指向在完全相同的方向,Dot返回1。
        //如果他们指向完全相反的方向,返回 - 1。
        //对于其他的情况返回一个数(例如:如果是垂直的Dot返回0)。
        //对于任意长度的向量,Dot返回值是相同的:当向量之间的角度减小,它们得到更大的值。

        transform.position = cPos + (camDir * Distance);

        if (lockTarget != null)
        {
            Vector3 relatvePos = lockTarget.position - transform.position;
            Quaternion rotation = Quaternion.LookRotation(relatvePos);
            transform.rotation = Quaternion.Slerp(transform.rotation, rotation, 3.5f * Time.deltaTime);
            //transform.LookAt(lockTarget.position);
        }
        else
            transform.LookAt(lookPoint);

        TargetLookAt.position = cPos;

        Quaternion newRot = Quaternion.Euler(mouseY, mouseX, 0);
        TargetLookAt.rotation = Quaternion.Slerp(TargetLookAt.rotation, newRot, SmoothCameraRotation * Time.deltaTime);
    }
}
public static class ExtensionMethods_Qing
{
    //两摄像机状态平滑
    public static void Slerp(this ThirdCameraState to,ThirdCameraState from,float time)
    {
        to.forward = Mathf.Lerp(to.forward, from.forward, time);
        to.right = Mathf.Lerp(to.right, from.right, time);
        to.maxDistance = Mathf.Lerp(to.maxDistance, from.maxDistance, time);
        to.Height = Mathf.Lerp(to.Height, from.Height, time);
    }
    //复制一个摄像状态
    public static void CopyState(this ThirdCameraState to, ThirdCameraState from)
    {
        to.forward = from.forward;
        to.right = from.right;
        to.maxDistance = from.maxDistance;
        to.Height = from.Height;
    }
}

ThirdCameraEditor (第三人称摄像机设置)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

[CustomEditor (typeof(ThirdCamera))]
public class ThirdCameraEditor : Editor {

    ThirdCamera thirdCamera;

    //在Inspector面板创建一个GUI
    public override void OnInspectorGUI()
    {
        thirdCamera = (ThirdCamera)target;

        EditorGUILayout.Space();
        //在前一个控制和下一个控件之间留一个空格

        //if(thirdCamera.CameraStateList == null)
        //{
        //    GUILayout.EndVertical();
        //    return;
        //}
        //开始一个垂直控件的组
        GUILayout.BeginVertical("摄像机状态", "window");
        //创建一个包含用户消息的帮助框。
        EditorGUILayout.HelpBox("此设置将始终加载到此列表中,您可以为其他人物创建更多的列表,并具有不同的设置",MessageType.Info);

        thirdCamera.CameraStateList = (ThirdameraListData)EditorGUILayout.ObjectField("摄像机状态 列表", thirdCamera.CameraStateList, typeof(ThirdameraListData), false);
        
        //开始一个水平控件的组
        GUILayout.BeginHorizontal();
            //创建按钮新建摄像机状态
        if(GUILayout.Button(new GUIContent("创建摄像机状态")))
        {
            if (thirdCamera.CameraStateList.thirdCameraStates == null)
                thirdCamera.CameraStateList.thirdCameraStates = new List<ThirdCameraState>();

            thirdCamera.CameraStateList.thirdCameraStates.Add(new ThirdCameraState("New State" + thirdCamera.CameraStateList.thirdCameraStates.Count));
            thirdCamera.index = thirdCamera.CameraStateList.thirdCameraStates.Count - 1;
        }
        //创建按钮删除摄像机状态
        if (GUILayout.Button(new GUIContent("删除摄像机状态")) && thirdCamera.CameraStateList.thirdCameraStates.Count > 1 && thirdCamera.index != 0)
        {
            if (thirdCamera.index - 1 >= 0)
                thirdCamera.index--;
        }
        GUILayout.EndHorizontal();
        //结束一个水平控件的组

        if(thirdCamera.CameraStateList.thirdCameraStates.Count > 0)
        {
            thirdCamera.index = EditorGUILayout.Popup("State", thirdCamera.index, getListName(thirdCamera.CameraStateList.thirdCameraStates));

            StateData(thirdCamera.CameraStateList.thirdCameraStates[thirdCamera.index]);
        }

        GUILayout.EndVertical();
        //结束垂直控件的组

        GUILayout.BeginVertical("box");
        base.OnInspectorGUI();
        GUILayout.EndVertical();

        EditorGUILayout.Space();

        if(GUI.changed)
        //如果任何控制按钮改变输入数据的值那么返回true。
        {
            EditorUtility.SetDirty(thirdCamera);
            EditorUtility.SetDirty(thirdCamera.CameraStateList);
            //将目标对象标记为脏,(仅适用于非场景对象)
        }
    }

    void StateData(ThirdCameraState camState)
    {
        camState.Name = EditorGUILayout.TextField("状态 名字", camState.Name);
        //创建一个文本字段。
        if(CheckName(camState.Name,thirdCamera.index))
        {
            EditorGUILayout.HelpBox("这个名字已经存在,请选择另一个名字", MessageType.Error);
        }
        camState.forward = (float)((int)EditorGUILayout.Slider("前方", camState.forward, -1f, 1f));
        camState.right = EditorGUILayout.Slider("水平", camState.right, -3f, 3f);
        camState.maxDistance = EditorGUILayout.FloatField("最大距离", camState.maxDistance);
        //文本框输入浮点值。
        camState.minDistance = EditorGUILayout.FloatField("最小距离", camState.minDistance);
        camState.Height = EditorGUILayout.FloatField("高度", camState.Height);
    }

    //检查名字
    bool CheckName(string Name,int _index)
    {
        foreach (ThirdCameraState state in thirdCamera.CameraStateList.thirdCameraStates)
            if (state.Name.Equals(Name) && thirdCamera.CameraStateList.thirdCameraStates.IndexOf(state) != _index)
                return true;
        return false;

        //foreach循环用于列举出集合中所有的元素,
        //foreach语句中的表达式由关键字in隔开的两个项组成。
        //in右边的项是集合名,in左边的项是变量名,用来存放该集合中的每个元素。
    }
    [MenuItem("ThirdPerson Controller/Resources/New CameraState List Data")]
    //MenuItem属性允许您将菜单项添加到主菜单和检查器上下文菜单。
    static void NewCameraStateData()
    {
        ScriptableObjectUtility_Qing.CreateAsset<ThirdameraListData>();
    }

    private string[] getListName(List<ThirdCameraState> list)
    {
        string[] names = new string[list.Count];
        for(int i = 0;i<list.Count;i++)
        {
            names[i] = list[i].Name;
        }
        return names;
    }
}

效果:
在这里插入图片描述

### Unity 中实现第三人称摄像机跟随的方法 在 Unity 中创建一个高效且易于使用的第三人称摄像机系统涉及多个方面,包括但不限于摄像机的位置更新、视角旋转以及响应用户的输入来调整距离。 #### 创建基础结构 对于第三人称摄像机的设计,采用一种分层架构有助于简化逻辑并提高灵活性。具体来说,通常会构建三个主要的游戏对象层次: - **FollowCamera**:负责处理整体位置跟踪目标角色。 - **Pivot (空物体)**:作为中间节点用于管理俯仰角(即绕 X 轴的旋转),它位于 `FollowCamera` 下方。 - **Main Camera**:实际渲染视图的部分,挂载于 Pivot 上面,仅需关注水平方向上的转向[^4]。 ```csharp // C# Code Example for setting up the hierarchy structure programmatically. public class ThirdPersonCameraSetup : MonoBehaviour { public GameObject player; void Start() { // Create an empty game object as a pivot point between camera and target var pivot = new GameObject("Pivot"); pivot.transform.SetParent(player.transform); // Reposition this script's owner, which should be Main Camera, under the newly created pivot transform.SetParent(pivot.transform); // Optionally set initial offsets here... } } ``` #### 处理摄像机运动与交互 为了让玩家能够自然地操控摄像机,需要编写脚本来监听键盘/鼠标的事件,并据此改变摄像机的角度和距离。下面是一个简单的例子展示了如何基于用户输入来进行这些操作[^2]。 ```csharp using UnityEngine; public class ThirdPersonCameraController : MonoBehaviour { private Transform _target; // The character to follow private float distanceFromTarget = 10f; // Distance from the target [Range(0.01f, 5)] public float rotationSpeed = 2f; [Range(0.01f, 5)] public float zoomSpeed = 2f; [Range(1, 20)] public float minDistance = 1f; [Range(1, 20)] public float maxDistance = 20f; void Update () { HandleRotation(); HandleZooming(); PositionUpdate(); } void HandleRotation(){ if(Input.GetMouseButton(1)){ float horizontalInput = Input.GetAxis("Mouse X") * rotationSpeed; float verticalInput = Input.GetAxis("Mouse Y") * rotationSpeed; Vector3 eulerAngle = transform.eulerAngles; eulerAngle.y += horizontalInput; eulerAngle.x -= verticalInput; eulerAngle.x = Mathf.Clamp(eulerAngle.x,-89f,89f); // Prevent flipping over transform.rotation = Quaternion.Euler(eulerAngle); } } void HandleZooming(){ float scrollValue = Input.mouseScrollDelta.y * Time.deltaTime * zoomSpeed; distanceFromTarget = Mathf.Clamp(distanceFromTarget - scrollValue,minDistance,maxDistance); } void PositionUpdate(){ Vector3 desiredPosition = _target.position - transform.forward * distanceFromTarget; transform.position = desiredPosition; } } ``` 此代码片段实现了基本的鼠标右键拖拽以旋转视角的功能,同时也支持通过滚动滚轮来动态调节摄像机到目标之间的相对距离[^1]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值