[Unity] ACT 战斗系统学习 4:重构前的第三人称控制器
以前看猴与花果山的文章,感觉大开眼界,但是没做过所以没更多体会 https://zhuanlan.zhihu.com/p/416805924 现在我才发现这真的是网上能找到的最详细的实现思路了 我犯的错误刚好就是没有想着流程,其实属性不需要流程,一个 OnChange 就行了,属性是别的流程的改变的结果,我们不是直接改变流程
说实话 FlowCanvas 学起来好麻烦 做成想要的样子也有点麻烦 为了让底层逻辑适应 FlowCanvas,还要修改底层逻辑,也好麻烦 但是毕竟策划最大……
1. Scriptable 用于角色属性
我有一段时间在纠结的就是,我到底什么时候应该用 scriptable ……因为感觉创建素材太多了很会乱 虽然直觉上他们是用在单个例子中的,需要多次引用的地方 但是我在想,对于需要动态生成的敌人,就不能要求他们都有对应的 scriptableobject 了…… 这样的话,感觉框架在处理不同角色的时候行为会不一样,这容易发生一些混乱?
最初我还不知道什么东西是应该用来序列化的 然后我就列了这些

然后当我重构的时候我就发现,为了能够在监视器中配置 scriptableobject 我必须要把这个使用 scriptableobject 的脚本做成 mono 那么既然已经做成了 mono,那其实有些变量已经可以在监视器上序列化了,我的初衷是为了序列化才基于 scriptableobject 重构的,但是我发现我要是改成 mono 也能实现一部分的序列化 这就有点令人迷茫
不过之后我觉得我应该想懂了,像是血量体力这种被大量引用的,关系到 UI 啊,声音 啊啥的,做成 scriptableobject 很合理,像是 IsSpring 这种状态的,其实也是要影响外部的,比如我想冲刺的时候也有 UI 和 声音的变化 但是像是 timeout cost 这种,他看上去像是状态,但是又其实没那么大可能被其他部件引用的,就不用了
理所当然的,其实 GameFramework 中的 EntityData 就没有必要了,还需要自己在 Excel 中填表,填了还要生成类…… 直接写成一个 ScriptableObject 就好了

然后接下来我在重构的时候,写的时候我就感觉关于一些状态的也是没有必要的 因为这些状态其实是一个瞬时更新的东西,无论我保存或者不保存,我打开游戏的时候都会征程更新,比如 IsGround,我打开游戏之后立即更新,那么打开游戏之前的值就无关紧要了 至于霸体这种东西,确实要存,但是这应该存在一个 Buff 列表里面而不是一个简单的变量一面 霸体是 Buff 流程的结果,而不是一个 Bool 值
2. InputController
2.1 第一版


2.2 第二版
添加了控制接受输入的功能

2.3 第三版
为了控制输入清零,不能直接使用布尔值控制接受输入,而是应该使用调用函数的方法

Assets/MeowFramework/MeowACT/InputSystem/MeowACTInputController.cs
using System;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.InputSystem;
namespace MeowFramework.MeowACT
{
public class MeowACTInputController : SerializedMonoBehaviour
{
[HorizontalGroup("MoveInput")]
[HorizontalGroup("MoveInput/Left")]
[ShowInInspector]
[ReadOnly]
[Tooltip("是否接受运动输入")]
private bool CanMoveInput = true;
[HorizontalGroup("MoveInput/Right")]
[LabelWidth(50)]
[Tooltip("移动")]
public Vector2 Move;
[HorizontalGroup("LookInput")]
[HorizontalGroup("LookInput/Left")]
[ShowInInspector]
[ReadOnly]
[Tooltip("是否接受摄像机旋转输入")]
private bool CanLookInput = true;
[HorizontalGroup("LookInput/Right")]
[LabelWidth(50)]
[Tooltip("鼠标移动")]
public Vector2 Look;
[HorizontalGroup("SprintInput")]
[HorizontalGroup("SprintInput/Left")]
[ShowInInspector]
[ReadOnly]
[Tooltip("是否接受冲刺输入")]
private bool CanSprintInput = true;
[HorizontalGroup("SprintInput/Right")]
[LabelWidth(50)]
[Tooltip("冲刺")]
public bool Sprint;
[HorizontalGroup("AttackInput")]
[HorizontalGroup("AttackInput/Left")]
[ShowInInspector]
[ReadOnly]
[Tooltip("是否接受冲刺输入")]
private bool CanAttackInput = true;
[HorizontalGroup("AttackInput/Right")]
[LabelWidth(50)]
[Tooltip("攻击")]
public bool Attack;
[Tooltip("鼠标锁定")]
public bool CursorLocked = true;
[HideInInspector]
public Action<Vector2> OnMoveAction;
[HideInInspector]
public Action<Vector2> OnLookAction;
[HideInInspector]
public Action OnSprintAction;
[HideInInspector]
public Action OnAttackAction;
[HideInInspector]
public Action<bool> OnApplicationFocusAction;
private void OnMove(InputValue value)
{
if (CanMoveInput)
{
Move = value.Get<Vector2>();
OnMoveAction?.Invoke(Move);
}
}
private void OnLook(InputValue value)
{
if (CanLookInput)
{
Look = value.Get<Vector2>();
OnLookAction?.Invoke(Look);
}
}
private void OnSprint(InputValue value)
{
if (CanSprintInput)
{
Sprint = value.isPressed;
OnSprintAction?.Invoke();
}
}
private void OnAttack(InputValue value)
{
if (CanAttackInput)
{
Attack = value.isPressed;
OnAttackAction?.Invoke();
}
}
private void OnApplicationFocus(bool hasFocus)
{
SetCursorState(hasFocus);
CursorLocked = hasFocus;
OnApplicationFocusAction?.Invoke(CursorLocked);
}
private void SetCursorState(bool newState)
{
Cursor.lockState = newState ? CursorLockMode.Locked : CursorLockMode.None;
}
public void EnableMoveInput(bool shouldEnable)
{
CanMoveInput = shouldEnable;
Move = Vector2.zero;
}
public void EnableLookInput(bool shouldEnable)
{
CanLookInput = shouldEnable;
Look = Vector2.zero;
}
public void EnableSprintInput(bool shouldEnable)
{
CanSprintInput = shouldEnable;
Sprint = false;
}
public void EnableAttackInput(bool shouldEnable)
{
CanAttackInput = shouldEnable;
Attack = false;
}
}
}
3. LocomotionController
3.1 第一版

3.2 第二版
将冲刺逻辑抽象为速度覆盖,使用速度覆盖逻辑替换冲刺+行走逻辑

Assets/MeowFramework/ThirdPerson/Scripts/ThirdPersonLocomotionController.cs
using Cinemachine;
using MeowFramework.Core;
using MeowFramework.MeowACT;
using Sirenix.OdinInspector;
using UnityEngine;
namespace MeowFramework
{
public class ThirdPersonLocomotionController : SerializedMonoBehaviour
{
private const float Threshold = 0.01f;
[BoxGroup("Component")]
[Required]
[Tooltip("角色控制器")]
public CharacterController CharacterCtr;
[BoxGroup("Component")]
[Required]
[Tooltip("ACT 输入控制器")]
public MeowACTInputController ACTInput;
[BoxGroup("Component")]
[Required]
[Tooltip("摄像机跟随点")]
public GameObject CMCameraFollowTarget;
[BoxGroup("Component")]
[Sirenix.OdinInspector.ReadOnly]
[Tooltip("主摄像机")]
public Camera MainCamera;
[BoxGroup("Component")]
[Sirenix.OdinInspector.ReadOnly]
[Tooltip("跟随主角的摄像机")]
public CinemachineVirtualCamera PlayerFollowCamera;
[BoxGroup("Velocity")]
[Required]
[Tooltip("可资产化水平速度")]
public ScriptableVector3Variable HorizontalVelocity;
[BoxGroup("Velocity")]
[Required]
[Tooltip("可资产化竖直速度")]
public ScriptableFloatVariable VerticalVelocity;
[BoxGroup("Velocity")]
[HorizontalGroup("Velocity/HorizontalVelocityOverride")]
[ShowInInspector]
[Sirenix.OdinInspector.ReadOnly]
[Tooltip("水平速度覆盖值")]
private bool isHorizontalVelocityOverrided;
[BoxGroup("Velocity")]
[HorizontalGroup("Velocity/HorizontalVelocityOverride")]
[ShowInInspector]
[Sirenix.OdinInspector.ReadOnly]
[Tooltip("水平速度覆盖值")]
private float HorizontalVelocityOverride;
private Vector3 HorizontalVelocityDirectionOverride;
[BoxGroup("Walk")]
[Tooltip("移动速度")]
public float walkSpeed = 7f;
private Vector3 walkSmoothVelocity;
[BoxGroup("Walk")]
[Tooltip("玩家行走的过渡时间")]
public float walkSmoothTime = 0.2f;
private float rotationSmoothVelocity;
[BoxGroup("Walk")]
[Tooltip("玩家旋转的过渡时间")]
public float rotationSmoothTime = 0.1f;
[BoxGroup("Gravity")]
[Tooltip("重力系数")]
public float gravity = -9.8f;
[BoxGroup("Gravity")]
[Tooltip("最大下落速度")]
public float terminalVelocity = 53f;
[BoxGroup("GroundCheck")]
[Sirenix.OdinInspector.ReadOnly]
[Tooltip("是否落地")]
public bool IsGrounded;
[BoxGroup("GroundCheck")]
[Tooltip("落地球形碰撞检测中心点的竖向偏移量")]
public float groundedOffset = -0.14f;
[BoxGroup("GroundCheck")]
[Tooltip("落地球形碰撞检测的半径")]
public float groundedRadius = 0.28f;
[BoxGroup("GroundCheck")]
[Tooltip("落地球形碰撞检测的层级")]
public int groundLayers = 1;
[BoxGroup("Camera")]
[Tooltip("摄像机是否固定")]
public bool IsCameraFixed = false;
[BoxGroup("Camera")]
[Tooltip("摄像机俯仰角覆盖值")]
public float cameraPitchOverride = 0f;
[BoxGroup("Camera")]
[Tooltip("摄像机转动速度")]
public float cameraRotSpeed = 25f;
[BoxGroup("Camera")]
[PropertyRange(0,90)]
[Tooltip("摄像机最大俯仰角")]
public float topClamp = 70f;
[BoxGroup("Camera")]
[PropertyRange(-90,0)]
[Tooltip("摄像机最小俯仰角")]
public float bottomClamp = -30f;
[BoxGroup("Camera")]
[Tooltip("摄像机跟随点的当前俯仰角的过渡时间")]
public float cinemachinePitchSmoothTime = 0.1f;
[BoxGroup("Camera")]
[Tooltip("摄像机跟随点的当前偏航角的过渡时间")]
public float cinemachineYawSmoothTime = 0.1f;
private float cinemachineTargetPitch;
private float cinemachineTargetYaw;
private Vector2 cinemachineCurrPY;
private float cinemachinePitchSmoothVelocity;
private float cinemachineYawSmoothVelocity;
public void Awake()
{
MainCamera = GameObject.FindGameObjectWithTag("MainCamera").GetComponent<Camera>();
PlayerFollowCamera = GameObject.Find("PlayerFollowCamera").GetComponent<CinemachineVirtualCamera>();
}
public void Start()
{
PlayerFollowCamera.Follow = CMCameraFollowTarget.transform;
}
protected void Update()
{
ApplyGravity();
GroundedCheck();
Move();
}
protected void LateUpdate()
{
CameraRotate();
}
private void GroundedCheck()
{
var spherePosition = new Vector3(transform.position.x, transform.position.y - groundedOffset, transform.position.z);
IsGrounded = Physics.CheckSphere(spherePosition, groundedRadius, groundLayers, QueryTriggerInteraction.Ignore);
}
private void ApplyGravity()
{
if (IsGrounded && VerticalVelocity.Value < 0.0f)
VerticalVelocity.Value = -2f;
else if (VerticalVelocity.Value < terminalVelocity)
VerticalVelocity.Value += gravity * Time.deltaTime;
}
private void Move()
{
HorizontalVelocity.Value = GetSpeed();
CharacterCtr.Move((HorizontalVelocity.Value + VerticalVelocity.Value * new Vector3(0,1,0)) * Time.deltaTime);
}
private Vector3 GetSpeed()
{
Vector3 targetDirection = GetInputDirectionBaseOnCamera();
RotateToMoveDir(targetDirection);
if (isHorizontalVelocityOverrided)
{
return HorizontalVelocityDirectionOverride * HorizontalVelocityOverride;
}
Vector3 targetVelocity = (ACTInput.Move == Vector2.zero) ? Vector3.zero : targetDirection * walkSpeed;
return Vector3.SmoothDamp(HorizontalVelocity.Value, targetVelocity, ref walkSmoothVelocity, walkSmoothTime);
}
private void RotateToMoveDir(Vector3 inputDirection)
{
if (ACTInput.Move != Vector2.zero || isHorizontalVelocityOverrided == true)
{
float targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg;
float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetRotation, ref rotationSmoothVelocity, rotationSmoothTime);
transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);
}
}
private void CameraRotate()
{
if (ACTInput.Look.sqrMagnitude >= Threshold && !IsCameraFixed)
{
cinemachineTargetPitch += ACTInput.Look.y * Time.deltaTime * cameraRotSpeed / 100.0f;
cinemachineTargetYaw += ACTInput.Look.x * Time.deltaTime * cameraRotSpeed / 100.0f;
}
cinemachineTargetPitch = MathUtility.ClampAngle(cinemachineTargetPitch, bottomClamp, topClamp);
cinemachineTargetYaw = MathUtility.ClampAngle(cinemachineTargetYaw, float.MinValue, float.MaxValue);
cinemachineCurrPY.x = Mathf.SmoothDampAngle(cinemachineCurrPY.x, cinemachineTargetPitch,
ref cinemachinePitchSmoothVelocity, cinemachinePitchSmoothTime);
cinemachineCurrPY.y = Mathf.SmoothDampAngle(cinemachineCurrPY.y, cinemachineTargetYaw,
ref cinemachineYawSmoothVelocity, cinemachineYawSmoothTime);
CMCameraFollowTarget.transform.rotation = Quaternion.Euler(cinemachineCurrPY.x + cameraPitchOverride, cinemachineCurrPY.y, 0.0f);
}
private Vector3 GetInputDirectionBaseOnCamera()
{
Vector3 inputDirection = new Vector3(ACTInput.Move.x, 0.0f, ACTInput.Move.y).normalized;
float targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg + cinemachineTargetYaw;
Vector3 targetDirection = Quaternion.Euler(0.0f, targetRotation, 0.0f) * Vector3.forward;
return targetDirection;
}
public void CancelHorizontalVelocityOverride()
{
isHorizontalVelocityOverrided = false;
}
public void SetHorizontalVelocityOverride(float speed)
{
isHorizontalVelocityOverrided = true;
HorizontalVelocityOverride = speed;
HorizontalVelocityDirectionOverride = GetInputDirectionBaseOnCamera();
}
public void SetHorizontalVelocityOverride(float speed, Vector3 dir)
{
isHorizontalVelocityOverrided = true;
HorizontalVelocityOverride = speed;
HorizontalVelocityDirectionOverride = dir;
}
}
}
4. AnimationController
4.1 第一版
最初是功能键很少的

Assets/MeowFramework/ThirdPerson/Scripts/ThirdPersonAnimationController.cs
using System;
using Sirenix.OdinInspector;
using UnityEngine;
namespace MeowFramework
{
public class ThirdPersonAnimationController : SerializedMonoBehaviour
{
[BoxGroup("Component")]
[Required]
[Tooltip("第三人称运动管理器")]
public ThirdPersonLocomotionController LocomotionController;
[BoxGroup("Component")]
[Required]
[Tooltip("动画控制器")]
public Animator Anim;
private int animIDSpeed;
private int animIDGrounded;
private int animIDFreeFall;
private int animIDMeleeAttack;
private float moveAnimBlend;
private float moveAnimBlendSmoothVelocity;
[BoxGroup("Move")]
[Tooltip("跑步动画混合值的过渡时间")]
public float moveAnimBlendSmoothTime = 0.2f;
public void Start()
{
AssignAnimationIDs();
}
public void Update()
{
SetAnimatorValue();
}
public void AssignAnimationIDs()
{
animIDSpeed = Animator.StringToHash("ForwardSpeed");
animIDGrounded = Animator.StringToHash("Grounded");
animIDFreeFall = Animator.StringToHash("FreeFall");
animIDMeleeAttack = Animator.StringToHash("Attack");
}
public void SetAnimatorValue()
{
Anim.SetBool(animIDGrounded, LocomotionController.IsGrounded);
if (LocomotionController.IsGrounded)
Anim.SetBool(animIDFreeFall, false);
else
Anim.SetBool(animIDFreeFall, true);
moveAnimBlend = Mathf.SmoothDamp(moveAnimBlend, LocomotionController.HorizontalVelocity.Value.magnitude, ref moveAnimBlendSmoothVelocity, moveAnimBlendSmoothTime);
Anim.SetFloat(animIDSpeed, moveAnimBlend);
}
private void OnBeginMeleeAttack()
{
Anim.SetTrigger(animIDMeleeAttack);
}
}
}
5. EventNode
5.1 第一版
我本来希望直接拿到 Action,对一个泛型 Action 操作 但是后面发现我这样拿到的是一个值类型,我还拿不到引用类型

5.2 第二版
没有办法的话那就只能一个事件做一个节点了

5.3 第三版
把 InpurController 做成 BBParameter 还是更好看……

6. 冲刺
6.1 第一版
直接在 ThirdPersonLocomotionController 中,进行移动前,判断是否冲刺,是冲刺则进入冲刺速度计算公式
6.2 第二版
在 ThirdPersonLocomotionController 中将冲刺速度判断逻辑改造为速度覆盖逻辑,进行移动前,判断是否有速度覆盖,有速度覆盖则进入速度覆盖计算公式

6.3 第三版
第二版这个速度覆盖的逻辑是稳定的,不管怎么覆盖都是这样 唯一可能改变的就是前面控制时间的部分 因此我就把后面的部分封装起来

封装成的节点的代码
Assets/MeowFramework/ParadoxNotionExtension/Ability/OverrideHorizontalVelocity.cs
using System.Collections;
using MeowFramework;
using MeowFramework.MeowACT;
using ParadoxNotion.Design;
using UnityEngine;
namespace FlowCanvas.Nodes
{
[Category("MeowFramework/Ability")]
[Description("在一段时间内覆盖水平速度")]
public class OverrideHorizontalVelocity : LatentActionNode<MeowACTInputController, ThirdPersonLocomotionController, float, float, float>
{
public float timeLeft { get; private set; }
public float normalized { get; private set; }
public override IEnumerator Invoke(MeowACTInputController inputController, ThirdPersonLocomotionController locomotionController, float velocityOverride = 0f, float time = 1f, float timeScale = 1f)
{
if (inputController == null)
yield return null;
if (locomotionController == null)
yield return null;
inputController.EnableMoveInput(false);
locomotionController.SetHorizontalVelocityOverride(velocityOverride);
timeLeft = time;
while ( timeLeft > 0 ) {
timeLeft -= Time.deltaTime * timeScale;
timeLeft = Mathf.Max(timeLeft, 0);
normalized = timeLeft / time;
yield return null;
}
locomotionController.CancelHorizontalVelocityOverride();
inputController.EnableMoveInput(true);
}
}
}
6.4 第四版
组件的连线还是用黑板值比较好,连线太多会很难看 然后要中断协程应该用 yield break

6.5 第五版
想想还是把 TimeScale 做成 BBParameter 把

事件节点也改了参数之后

节点代码:
using System.Collections;
using MeowFramework;
using MeowFramework.MeowACT;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
using UnityEngine;
namespace FlowCanvas.Nodes
{
[Category("MeowFramework/Ability")]
[Description("在一段时间内覆盖水平速度")]
public class OverrideHorizontalVelocity : LatentActionNode<float, float, float>
{
public BBParameter<MeowACTInputController> InputController;
public BBParameter<ThirdPersonLocomotionController> LocomotionController;
public float timeLeft { get; private set; }
public float normalized { get; private set; }
public override IEnumerator Invoke(float velocityOverride = 0f, float time = 1f, float timeScale = 1f)
{
if (InputController.value == null)
yield return null;
if (LocomotionController.value == null)
yield return null;
InputController.value.EnableMoveInput(false);
LocomotionController.value.SetHorizontalVelocityOverride(velocityOverride);
timeLeft = time;
while ( timeLeft > 0 ) {
timeLeft -= Time.deltaTime * timeScale;
timeLeft = Mathf.Max(timeLeft, 0);
normalized = timeLeft / time;
yield return null;
}
LocomotionController.value.CancelHorizontalVelocityOverride();
InputController.value.EnableMoveInput(true);
yield return null;
}
}
}
7. 瞄准
7.1 第一版
最初是想用类似 Wait Until 的思路做

节点代码:
Assets/MeowFramework/ParadoxNotionExtension/Ability/Aiming.cs
using System.Collections;
using Cinemachine;
using MeowFramework;
using MeowFramework.MeowACT;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
using UnityEngine;
namespace FlowCanvas.Nodes
{
[Category("MeowFramework/Ability")]
[Description("进入瞄准状态")]
public class Aiming : LatentActionNode
{
[Description("输入控制器")]
public BBParameter<MeowACTInputController> InputController;
[Description("移动控制器")]
public BBParameter<ThirdPersonLocomotionController> LocomotionController;
[Description("玩家摄像机")]
public BBParameter<CinemachineVirtualCamera> playerCamera;
[Description("时间比尺")]
public BBParameter<float> TimeScale = 1f;
private ValueInput<bool> condition;
[Description("摄像机移动的过渡时间")]
public BBParameter<float> CameraMoveTransitionTime = 1f;
[Description("待机状态下摄像机的侧向位置")]
public BBParameter<float> IdleSide = 0.5f;
[Description("瞄准状态下摄像机的侧向位置")]
public BBParameter<float> AimingSide = 1f;
private float cameraSideSmoothVelocity;
[Description("摄像机侧向位置的平滑时间")]
public BBParameter<float> CameraSideSmoothTime = 0.2f;
private float timeLeft { get; set; }
public override IEnumerator Invoke()
{
yield return CameraMove(AimingSide.value);
yield return new UnityEngine.WaitUntil(condition.GetValue);
yield return CameraMove(IdleSide.value);
}
private IEnumerator CameraMove(float newSide)
{
var camera3rdPersonFollow =
playerCamera.value.GetCinemachineComponent<Cinemachine3rdPersonFollow>();
timeLeft = CameraMoveTransitionTime.value;
while ( timeLeft > 0 ) {
timeLeft -= Time.deltaTime * TimeScale.value;
camera3rdPersonFollow.CameraSide = Mathf.SmoothDamp(camera3rdPersonFollow.CameraSide, newSide,
ref cameraSideSmoothVelocity, CameraSideSmoothTime.value);
yield return null;
}
camera3rdPersonFollow.CameraSide = newSide;
yield return null;
}
protected override void OnRegisterExtraPorts(FlowNode node)
{
condition = node.AddValueInput<bool>("Condition");
}
}
}
但是这样有个问题就是第一个协程启动了,需要等到它执行完才能执行 因此如果你在第一个协程启动的时候满足了退出条件,它还要等到第一个协程执行完才能退出 想要退出时再立即进入也同理办不到 因此是不能用协程控制摄像机移动的
7.2 第二版
之前想着用一个协程解决进入和退出 想想就感觉有点乱 之后把过程分解到只有一个移动 进入和退出是通过打断正在进行的移动实现的 这就很方便了

7.3 第三版
添加了 FOV 变化

添加速度改变

节点代码:
Assets/MeowFramework/ParadoxNotionExtension/Ability/PlayerCameraSmoothMove.cs
using System.Collections;
using Cinemachine;
using MeowFramework;
using MeowFramework.MeowACT;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
using UnityEngine;
namespace FlowCanvas.Nodes
{
[Category("MeowFramework/Ability")]
[Description("玩家摄像机平滑移动")]
public class PlayerCameraSmoothMove : LatentActionNode
{
[Description("输入控制器")]
public BBParameter<MeowACTInputController> InputController;
[Description("移动控制器")]
public BBParameter<ThirdPersonLocomotionController> LocomotionController;
[Description("玩家摄像机")]
public BBParameter<CinemachineVirtualCamera> playerCamera;
[Description("时间比尺")]
public BBParameter<float> TimeScale = 1f;
[Description("摄像机移动的过渡时间")]
public BBParameter<float> CameraMoveTransitionTime = 1f;
[Description("摄像机的目标 FOV")]
public BBParameter<float> TargetFOV = 30f;
private float FOVSmoothVelocity;
[Description("摄像机的目标 FOV 的平滑时间")]
public BBParameter<float> FOVSmoothTime = 0.2f;
[Description("摄像机的目标侧向位置")]
public BBParameter<float> TargetSide = 0.5f;
private float cameraSideSmoothVelocity;
[Description("摄像机侧向位置的平滑时间")]
public BBParameter<float> CameraSideSmoothTime = 0.2f;
private float timeLeft { get; set; }
public override IEnumerator Invoke()
{
var camera3rdPersonFollow =
playerCamera.value.GetCinemachineComponent<Cinemachine3rdPersonFollow>();
timeLeft = CameraMoveTransitionTime.value;
while ( timeLeft > 0 ) {
timeLeft -= Time.deltaTime * TimeScale.value;
playerCamera.value.m_Lens.FieldOfView = Mathf.SmoothDamp(playerCamera.value.m_Lens.FieldOfView, TargetFOV.value,
ref FOVSmoothVelocity, FOVSmoothTime.value);
camera3rdPersonFollow.CameraSide = Mathf.SmoothDamp(camera3rdPersonFollow.CameraSide, TargetSide.value,
ref cameraSideSmoothVelocity, CameraSideSmoothTime.value);
yield return null;
}
playerCamera.value.m_Lens.FieldOfView = TargetFOV.value;
camera3rdPersonFollow.CameraSide = TargetSide.value;
yield return null;
}
}
}
|