zoukankan      html  css  js  c++  java
  • 各种Camera,总有一款适合你(二)

      在实际的项目开发中,一般需要程序抽象出一些在几何意义上有明确意义的参数,这样方便策划或美术在自己的机器上进行调试。

      下面是一个可变参的地下城摄像机的简单实现:

    // 第三人称摄像机,平移和旋转会同时进行平滑
    public class ThirdPersonalCamera : MonoBehaviour
    {
        /// Camera Control Params
        public GameObject Target = null;
        public float Distance = 10f;
        public float RotateX = 0f;                                  // pitch 俯仰
        public float RotateY = 0f;                                  // yaw 偏航
        public float SmoothTime = 0.01f;                            // 平滑时间,默认为1s
        public float ScrollWheelSpeed = 1000f;                      // 中轴调整速度
        public Vector3 TargetOffset = Vector3.zero;                 // 目标偏移量
    
        private Vector3 Velocity = Vector3.zero;                    // 平滑初速度
        private Vector3 Offset = Vector3.zero;                      // 根据上面三个变量计算出
        private const float MinDistance = 5f;                       // 镜头最近距离
        private const float MaxDistance = 100f;                     // 镜头最远距离
    
        void Start()
        {
            if (Target == null) return;
        }
    
        public void Shake()
        {
            StartCoroutine("ShakeCoroutine");
        }
    
        void LateUpdate()
        {
            if (Target == null) return;
    
            UpdateDistance();
    
            float radX = Mathf.Deg2Rad * RotateX;
            float radY = Mathf.Deg2Rad * RotateY;
    
            Offset.x = Distance * Mathf.Cos(radX) * Mathf.Cos(radY);
            Offset.z = Distance * Mathf.Cos(radX) * Mathf.Sin(radY);
            Offset.y = Distance * Mathf.Sin(radX);
    
            Vector3 targetPos = Target.transform.position + Offset + TargetOffset;
            transform.position = Vector3.SmoothDamp(transform.position, targetPos, ref Velocity, SmoothTime);
    
            transform.LookAt(Target.transform.position + TargetOffset);
        }
    
        private void UpdateDistance()
        {
            float horizontal = Input.GetAxis("Mouse ScrollWheel") * ScrollWheelSpeed * Time.deltaTime;
            Distance -= horizontal;
        }
    
    }

      可以看到抽象出的控制参数主要包括:摄像机分别绕X轴和Y轴的旋转、摄像机距离角色的距离、以及用来控制平滑时间的参数。

      注意,上面这段代码的实现中,position和rotation是同时进行平滑的,下面来看另外一种实现:

    using UnityEngine;
    using System.Collections;
    
    [ExecuteInEditMode]
    public class MyDungeonCamera : MonoBehaviour
    {
        /// [摄像机控制参数]
        public GameObject Target = null;
        public float RotateX = 0f;                                  // pitch 俯仰
        public float RotateY = 0f;                                  // yaw 偏航
        public float Distance = 10f;                                // 摄像机远近
        public float MoveSmoothTime = 0.3f;                         // 位置平滑时间,默认为1s
        public float RotateSmoothTime = 0.3f;                       // 旋转平滑时间
        public float ScrollWheelSpeed = 1000f;                      // 中轴调整速度
        public Vector3 TargetOffset = Vector3.zero;                 // 目标偏移量
    
        private Vector3 Velocity = Vector3.zero;                    // 平滑初速度
        private Vector3 Offset = Vector3.zero;                      // 根据上面三个变量计算出
        private const float MinDistance = 5f;                       // 镜头最近距离
        private const float MaxDistance = 100f;                     // 镜头最远距离
        private Quaternion tmpRotation = Quaternion.identity;
        private Vector3 tmpPosition = Vector3.zero;
    
        private float RotateDamping
        {
            get
            {
                if (RotateSmoothTime <= 0f) RotateSmoothTime = 0.001f;
                return 1f / RotateSmoothTime;
            }
        }
    
        void LateUpdate()
        {
            if (Target == null) return;
    
            tmpRotation = transform.rotation;
            tmpPosition = transform.position;
    
            UpdateDistance();
            UpdateRotation();
            UpdatePosition();
    
            transform.rotation = tmpRotation;
            transform.position = tmpPosition;
        }
    
        private void UpdateRotation()
        {
            if (!NeedRotate()) return;
    
            Quaternion wantedRotation = Quaternion.Euler(RotateX, RotateY, 0f);
            // 旋转采用球形插值
            tmpRotation = Quaternion.Slerp(tmpRotation, wantedRotation, Time.deltaTime * RotateDamping);
        }
    
        private void UpdatePosition()
        {
            // 如果有旋转插值,则位置根据旋转变换;否则,位置自己进行插值过渡
            if (!NeedRotate())
            {
                Offset = Quaternion.Euler(RotateX, RotateY, 0f) * Vector3.forward * Distance;
                Vector3 wantedPos = Target.transform.position - Offset + TargetOffset;
                // 位置采用平滑阻尼过渡
                tmpPosition = Vector3.SmoothDamp(tmpPosition, wantedPos, ref Velocity, MoveSmoothTime);
            }
            else
            {
                Offset = tmpRotation * Vector3.forward * Distance;
                tmpPosition = Target.transform.position - Offset + TargetOffset;
            }
        }
    
        private void UpdateDistance()
        {
            float horizontal = Input.GetAxis("Mouse ScrollWheel") * ScrollWheelSpeed * Time.deltaTime;
            Distance -= horizontal;
            Distance = Mathf.Clamp(Distance, MinDistance, MaxDistance);
        }
    
        private bool NeedRotate()
        {
            Vector3 eulerAngles = transform.rotation.eulerAngles;
            return !(FloatEqual(eulerAngles.x, RotateX) && FloatEqual(eulerAngles.y, RotateY));
        }
    
        public static bool FloatEqual(float value1, float value2)
        {
            float ret = value1 - value2;
            return ret > -0.0005f && ret < 0.0005f;
        }
    
    }

      这个实现中,有几点需要注意的:

      (1)rotation使用四元素球形插值,这样保证每次旋转的角速度是恒定的,不过两次旋转各自的角速度不相等,这里可以改进;

      (2)position使用Vector3.SmoothDamp进行平滑阻尼过渡效果会比较好;

      (3)浮点数相等判断,不要直接用等号哟;

      (4)当同时有rotation和position时,以rotation为主;

      (5)是否需要旋转的判断,只有需要的时候才走旋转逻辑,这样可以减轻Update的压力。

      摄像机振动脚本:

    public class ShakeCamera : MonoBehaviour
    {
        public float ShakeTime = 1f;
        public float ShakeStrength = 0.2f;
        private Vector3 ShakeOffset = Vector3.zero;
        private Vector3 preShakeOffset = Vector3.zero;
    
        void LateUpdate()
        {
            transform.position = transform.position - preShakeOffset + ShakeOffset;
        }
    
        public void Shake()
        {
            StopCoroutine("ShakeCoroutine");
            StartCoroutine("ShakeCoroutine");
        }
    
        public void Shake(float time)
        {
            ShakeTime = time;
            Shake();
        }
    
        IEnumerator ShakeCoroutine()
        {
            float endTime = Time.time + ShakeTime;
            while (Time.time < endTime)
            {
                ShakeOffset = Random.insideUnitSphere * ShakeStrength;
                yield return null;
            }
            ShakeOffset = Vector3.zero;
        }
    
    }
  • 相关阅读:
    hdoj-1005-Number Sequences
    FOJ-1058-粗心的物理学家
    程序设计第三次作业附加 代码规范
    简单数论
    FOJ-1001-Duplicate Pair
    链表初尝试-链表的构建与输出-指针
    函数递归简单题-hdoj-2044 2018-一只小蜜蜂 母牛的故事
    电路与电子学-第一章直流电路分析方法小概括
    DFS回溯-函数递归-xiaoz triangles
    进制转换 hdoj-2031
  • 原文地址:https://www.cnblogs.com/sifenkesi/p/3994594.html
Copyright © 2011-2022 走看看