1. 批量 SmoothDamp 变量的需求

1.1 例子

这是我一个改到一半的函数……我懒得改了
这个函数的目的是从一个模式转换到另外一个模式的时候开始对一堆变量在一个时间内 SmoothDamp
目前是只有两个变量,所以我可以这么写,但是万一我都很多个变量呢?万一我要频繁地修改变量的名字,个数啥的呢?
我就感觉很麻烦

         // 初始化计时器var timeLeft = modeTransitionTime;// 旧层级权重平滑速度float fromLayerWeightSmoothVelocity = 0f;// 新层级权重平滑速度float toLayerWeightSmoothVelocity = 0f;// 旧层级权重var fromWeight = Anim.GetLayerWeight(fromLayer);// 新层级权重var toWeight = Anim.GetLayerWeight(toLayer);// 在给定时间内平滑// 平滑时间结束时,被平滑项接近终点值但不是终点值// 因此最后需要给被平滑项赋终点值,这可能产生一个抖动// 因此平滑时间需要在保证效果的同时尽可能小,才能让最后的抖动变小while (timeLeft > 0){timeLeft -= Time.deltaTime;fromWeight = Mathf.SmoothDamp(fromWeight, 0,ref fromLayerWeightSmoothVelocity, layerWeightSmoothTime);toWeight = Mathf.SmoothDamp(toWeight, 1,ref toLayerWeightSmoothVelocity, layerWeightSmoothTime);Anim.SetLayerWeight(fromLayer, fromWeight);Anim.SetLayerWeight(toLayer, toWeight);yield return null;}// 赋终点值Anim.SetLayerWeight(fromLayer, 0);Anim.SetLayerWeight(toLayer, 1);yield return null;}

本来其实在不同模式之间切换的时候,需要 SmoothDamp 的变量其实是不同的,从 A 到 B 要动三个变量,但是从 B 到 C 可能就只需要动一个
你要说用状态机把,其实也没必要,因为这并没有 OnEnter OnUpdate OnExist 啥的需求
所以我就想怎么方便地做这个切换的函数

1.2 分析

一个 mono 中有一个变量组,记为 V,一个表示模式的 Enum 变量,命名为 mode
Vmode 的不同值有不同预设值,把这些预设值做成一个 Struct 命名为 Setting

那么如果我想 V 的值在 mode 变化时,可以在与 mode 对应的 Setting 之间切换,我可以这么写

伪代码

EnumXXX mode;
Setting setting0;
Setting setting1;
Setting setting2;
Dictionary<EnumXXX, Setting> SettingDictionary = new Dictionary<EnumXXX, Setting>{{0,setting0},{1,setting1},{2,setting2}}
V = SettingDictionary[mode];

但是如果我想要 Vsetting 之间使用 smoothdamp 过渡,我就必须要对 setting 中的每一项都创建一个缓存变量 velocity 输入到 smoothdamp 函数里面,感觉这样有点麻烦

并且对于每一个不同的 V 我都要写一堆 smoothdamp 代码
比如如果 V 要转到 setting0

伪代码

V1 = Math.SmoothDamp(V1, setting0.V1, Velocity1, smoothTime);
V2 = Math.SmoothDamp(V2, setting0.V2, Velocity2, smoothTime);
V3 = Math.SmoothDamp(V3, setting0.V3, Velocity3, smoothTime);

这样就会很冗余……但是又不能做成一个大的列表然后遍历这个列表 smoothdamp
比如

伪代码

for(int i = 1;i < V.Count; ++i)
{V[i] = Math.SmoothDamp(V[i], setting0[i], Velocity[i], smoothTime);
}

因为 V 中的变量的类型可能是不同的,不能放到一个列表中
要放到一个列表中也可以,那就装箱成 object,然后多一个数组记录第 i 个变量的类型,再转回去,这样效率就太低了,而且感觉很蠢

所以问题就是,当我需要批量给一组变量插值的时候,我会写出数量为 n 的插值语句和数量为 n 的缓存变量

那要解决这个问题的话,我目前只能想到是

  1. 对每一种 setting 里面可能出现的类型建一个类,叫 SwitchableObject
    比如 Vector3 就是 SwitchableVector3float 就是 SwitchableFloat
    SwitchableFloat 为例,它包含一个 float Value,一个 List<float> SwitchableValueList 和一个 float SmoothVelocity
  2. 新建一个接口 ISwitchable 包含一个 void SwitchValue(int index) 函数,SwitchableFloat 继承 ISwitchable,函数内容是 float 类型的 SmoothDamp
  3. mono 里面有一个 List<ISwitchable> switchableObjectList 用于批量调用 SwitchValue

这样,法一

伪代码


// 法一float switchTime = 1f;
float smoothTime = 0.2f;T0 V0;
T0 velocity0;T1 V1;
T1 velocity1;T2 V2;
T2 velocity2;Setting setting0;
Setting setting1;
Setting setting2;Setting setting0 =
{T0 V0;T1 V1;T2 V2;
}Setting setting1 =
{T0 V0;T1 V1;T2 V2;
}Setting setting2 =
{T0 V0;T1 V1;T2 V2;
}Dictionary<EnumXXX, Setting> settingDictionary;void Start()
{ settingDictionary = new Dictionary<EnumXXX, Setting>{{0,setting0},{1,setting1},{2,setting2}};
}

现在是

伪代码


// 法二List<ISwitchable> switchableObjectList;SwitchableFloat V0;
V0.SwitchableValueList =
{float target0;float target1;float target2;
}SwitchableVector2 V1;
V1.SwitchableValueList =
{Vector2 target0;Vector2 target1;Vector2 target2;
}SwitchableVector3 V2;
V2.SwitchableValueList =
{Vector3 target0;Vector3 target1;Vector3 target2;
}void Start()
{switchableObjectList.Add(V0);switchableObjectList.Add(V1);switchableObjectList.Add(V2);
}

以前我需要

伪代码


// 法一Enumator SwitchSettingCoroutine(EnumXXX mode)
{float time = switchTime;while(time > 0){time -= Time.DeltaTime;V0 = Math.SmoothDamp(V0, settingDictionary[mode].V0, ref velocity0, smoothTime);V1 = Math.SmoothDamp(V1, settingDictionary[mode].V1, ref velocity1, smoothTime);V2 = Math.SmoothDamp(V2, settingDictionary[mode].V2, ref velocity2, smoothTime);}yield return null;
}void SwitchSetting(EnumXXX mode)
{StartCoroutine(SwitchSettingCoroutine(mode));
}

现在我需要

伪代码


// 法二Enumator SwitchSettingCoroutine(EnumXXX mode)
{float time = switchTime;while(time > 0){time -= Time.DeltaTime;for(ISwitchable s in switchableObjectList)s.SwitchValue(mode);}yield return null;
}void SwitchSetting(EnumXXX mode)
{StartCoroutine(SwitchSettingCoroutine(mode));
}

不知道我这样写行不行,会有什么问题……

这个看上去是很好的

一个数据表,假设行号是 Enum 列号是变量名
这样做把数据表的每一列拆到每一个变量里面
但是实际上符合习惯的做法是一行一行的

如果我真的要把数据表的一行放到一起,比如放到 ScriptableObject 里面
那么我取变量的目标值的流程就是:输入一个变量,然后通过反射拿到这个变量的名字,然后根据 Enum 在 字典 <Enum, Setting> 拿到 Setting,然后这个 Setting 也是一个 <string, 变量> 的字典,根据这个变量的名字在这个字典中拿到目标值
但是这样的话,这个函数有不同类型,Setting 中的字典也有不同类型,Setting 中还要写一个初始化函数把目标值放到不同类型的字典中
要不然就写成 Setting 里面只有不同类型的字典,这样就省去了初始化的麻烦

那么用的时候就是

伪代码


// 法三private float value1;
private Vector3 value2;
private Vector2 value3;
private Setting setting;private void GetTargetValueFromSetting()
{string name;name = nameof(value1);float target = setting.GetFloatDict()[name];name = nameof(value2);Vector3 target = setting.GetVector3Dict()[name];name = nameof(value3);Vector2 target = setting.GetVector2Dict()[name];
}

由于要获取名字,所以不可避免写 n 条语句……这就太麻烦了

伪代码


// 法四public class SwitchableFloat : ISwitchable
{public float value;public Dictionary<EnumXXX, float> targetValueDict;public override void SwitchValue(EnumXXX mode){float target = targetValueDict[mode];// SmoothDamp }
}public interface ISwitchable
{public void SwitchValue(EnumXXX mode);
}public float switchTime;
public SwitchableFloat value1;
public SwitchableFloat value2;
public SwitchableFloat value3;
public List<ISwitchable> switchableObjectList;void Start()
{switchableObjectList.Add(value1);switchableObjectList.Add(value2);switchableObjectList.Add(value3);
}public IEnumerator SwitchSettingCoroutine(EnumXXX mode)
{float time = switchTime;while(time > 0){time -= Time.deltaTime;for(ISwitchable s in switchableObjectList)s.SwitchValue(mode);}yield return null;
}void SwitchSetting(EnumXXX mode)
{StartCoroutine(SwitchSettingCoroutine(mode));
}

我不用泛型一个原因是在监视器上配置实现泛型的变量会出错,即使用了 Odin,另一个原因是 SmoothDamp 没有泛型

跟我讨论的朋友问我为什么不用 Animator,我说那是用来做骨骼动画的
但是后来我又想到 timeline,他是可以控制脚本的
首先的问题是我不知道怎么制作变量轨道
就算我知道了,变量轨道也需要一个确定的初值和终值,我这个是需要随时切换状态,比如按住右键瞄准,他可能一会瞄准一会不秒,切换时间小于动画时间,那么如果第一次动画时间,当前值就是初值,第二次相反的退出瞄准动画开始时,当前值也不会是退出瞄准动画的初值
除非我可以让动画的初值为当前值,或者我可以根据当前值,动画的初值终值得到我应该在哪个百分比进度播放动画……

好吧我后面知道了 Playable Track 是可以自定义 Clip 的
好麻烦……不想看……我就是懒hhhh

而且按照我最后的法四,我可以在监视器中对每一项变量设置对每一个 mode 的可能值的目标值,如果我不设置说明我就不用平滑这个变量
这是点开组件就能在监视器中看到的,timeline 可不行

再化简一点就是

伪代码


// 法四public class SwitchableFloat : ISwitchable
{public float value;public Dictionary<EnumXXX, float> targetValueDict;public override void SwitchValue(EnumXXX mode){float target = targetValueDict[mode];// SmoothDamp }
}public interface ISwitchable
{public void SwitchValue(EnumXXX mode);
}public float switchTime;
public SwitchableFloat value1;
public SwitchableFloat value2;
public SwitchableFloat value3;
public List<ISwitchable> switchableObjectList;private Coroutine switchSettingCoroutine;private EnumXXX mode;public EnumXXX Mode
{get => mode;set{if (mode != value){if (switchSettingCoroutine != null)StopCoroutine(switchSettingCoroutine);switchSettingCoroutine = StartCoroutine(SwitchSettingCoroutine(value));mode = value;}}
}void Start()
{switchableObjectList.Add(value1);switchableObjectList.Add(value2);switchableObjectList.Add(value3);
}public IEnumerator SwitchSettingCoroutine(EnumXXX mode)
{float time = switchTime;while(time > 0){time -= Time.deltaTime;for(ISwitchable s in switchableObjectList)s.SwitchValue(mode);}yield return null;
}

2. Switchable v1

正式地开始写了

2.1 ISwitchable v1

Assets/MeowFramework/Core/Switchable/ISwitchable.cs

// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 22/04/2022 9:00
// 最后一次修改于: 22/04/2022 9:11
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------using System;namespace MeowFramework.Core.Switchable
{/// <summary>/// 切换变量的接口/// </summary>public interface ISwitchable{/// <summary>/// 使变量在不同预设值之间切换/// </summary>/// <param name="mode">预设模式</param>public void SwitchValue(Enum mode);}
}

2.2 SwitchableFloat v1

Assets/MeowFramework/Core/Switchable/SwitchableFloat.cs

// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 22/04/2022 9:01
// 最后一次修改于: 22/04/2022 9:11
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------using System;
using System.Collections.Generic;
using UnityEngine;namespace MeowFramework.Core.Switchable
{/// <summary>/// 可切换浮点/// </summary>public class SwitchableFloat : ISwitchable{/// <summary>/// 当前值/// </summary>[Tooltip("当前值")]public float Value;/// <summary>/// 预设值字典/// </summary>[Tooltip("预设值字典")]public Dictionary<Enum, float> TargetValueDict;// 缓存/// <summary>/// 平滑速度/// </summary>private float smoothVelocity;/// <summary>/// 平滑时间/// </summary>[Tooltip("平滑时间")]public float SmoothTime = 0.2f;// 实现接口/// <summary>/// 使变量在不同预设值之间切换/// </summary>/// <param name="mode">预设模式</param>public void SwitchValue(Enum mode){// SmoothDamp float target = TargetValueDict[mode];Value = Mathf.SmoothDamp(Value, target, ref smoothVelocity, SmoothTime);}}
}

2.3 TPSCharacterAnimationSetting v1

这是第一版方案,是废弃了的
拿出来是为了对比,显示这样做有多笨

Assets/MeowFramework/TPSCharacter/Scripts/Struct/TPSCharacterAnimationSetting.cs

// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 20/04/2022 17:52
// 最后一次修改于: 20/04/2022 17:57
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------namespace MeowFramework.TPSCharacter.Struct
{public struct TPSCharacterAnimationSetting{/// <summary>/// 无武器层的动画层级权重/// </summary>public float NoWeaponLayerWeight;/// <summary>/// 持枪待机层的动画层级权重/// </summary>public float RifleIdleLayerWeight;/// <summary>/// 持枪瞄准层的动画层级权重/// </summary>public float RifleAimingLayerWeight;/// <summary>/// 无武器层的骨骼绑定权重/// </summary>public float NoWeaponRigWeight;/// <summary>/// 持枪待机层的骨骼绑定权重/// </summary>public float RifleIdleRigWeight;/// <summary>/// 持枪瞄准层的骨骼绑定权重/// </summary>public float RifleAimingRigWeight;}
}

2.4 TPSCharacterAnimationController.Mode v1

这是一个修改前的半成品……确实有些地方直接就是跑不通的
拿出来是为了对比,显示这样做有多笨

Assets/MeowFramework/TPSCharacter/Scripts/Controller/TPSCharacterAnimationController.Mode.cs

// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 12/04/2022 15:55
// 最后一次修改于: 22/04/2022 9:01
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------using System.Collections;
using System.Collections.Generic;
using MeowFramework.TPSCharacter.Struct;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Animations.Rigging;namespace MeowFramework.TPSCharacter
{/// <summary>/// 第三人称动画状态机控制器/// </summary>public partial class TPSCharacterAnimationController{/// <summary>/// 行动模式/// </summary>[BoxGroup("Mode")][ShowInInspector][Sirenix.OdinInspector.ReadOnly][Tooltip("行动模式")]private TPSCharacterBehaviourMode mode;/// <summary>/// 无武器层的动画层级的序号/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("无武器层的动画层级的序号")]private int noWeaponLayerIndex = 0;/// <summary>/// 持步枪待机层的动画层级的序号/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("持步枪待机层的动画层级的序号")]private int rifleIdleLayerIndex = 0;/// <summary>/// 持步枪瞄准层的动画层级的序号/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("持步枪瞄准层的动画层级的序号")]private int rifleAimingLayerIndex = 0;/// <summary>/// 无武器层的动画参数配置/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("无武器层的动画参数配置")]private TPSCharacterAnimationSetting noWeaponSetting;/// <summary>/// 持步枪待机层的动画参数配置/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("持步枪待机层的动画参数配置")]private TPSCharacterAnimationSetting rifleIdleSetting;/// <summary>/// 持步枪瞄准层的动画参数配置/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("持步枪瞄准层的动画参数配置")]private TPSCharacterAnimationSetting rifleAimingSetting;/// <summary>/// 切换模式的过渡时间/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("切换模式的过渡时间")]private float modeTransitionTime = 1f;/// <summary>/// 层级平滑时间/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("层级平滑时间")]private float layerWeightSmoothTime = 0.2f;// 缓存 - 模式改变/// <summary>/// 平滑切换层级权重的协程/// </summary>private Coroutine smoothSwitchLayerWeightCoroutine;/// <summary>/// 平滑切换层级权重/// </summary>/// <param name="fromLayer">旧层级</param>/// <param name="toLayer">新层级</param>/// <returns></returns>private IEnumerator SwitchAnimationSetting(TPSCharacterBehaviourMode mode){// 初始化计时器var timeLeft = modeTransitionTime;// 旧层级权重平滑速度float fromLayerWeightSmoothVelocity = 0f;// 新层级权重平滑速度float toLayerWeightSmoothVelocity = 0f;// 旧层级权重var fromWeight = Anim.GetLayerWeight(fromLayer);// 新层级权重var toWeight = Anim.GetLayerWeight(toLayer);// 在给定时间内平滑// 平滑时间结束时,被平滑项接近终点值但不是终点值// 因此最后需要给被平滑项赋终点值,这可能产生一个抖动// 因此平滑时间需要在保证效果的同时尽可能小,才能让最后的抖动变小while (timeLeft > 0){timeLeft -= Time.deltaTime;fromWeight = Mathf.SmoothDamp(fromWeight, 0,ref fromLayerWeightSmoothVelocity, layerWeightSmoothTime);toWeight = Mathf.SmoothDamp(toWeight, 1,ref toLayerWeightSmoothVelocity, layerWeightSmoothTime);Anim.SetLayerWeight(fromLayer, fromWeight);Anim.SetLayerWeight(toLayer, toWeight);yield return null;}// 赋终点值Anim.SetLayerWeight(fromLayer, 0);Anim.SetLayerWeight(toLayer, 1);yield return null;}private IEnumerator SwitchRigWeight(TPSCharacterBehaviourMode mode){List<Rig> rigs = new List<Rig> {rifleIdleRig, rifleAimingRig};switch (mode){case rigs.Remove(rifleIdleRig);}yield return null;}/// <summary>/// 设置动画模式/// </summary>/// <param name="mode">模式</param>public void SetAnimationMode(TPSCharacterBehaviourMode mode){// 更新模式记录this.mode = mode;// 如果有正在进行的模式切换相关的协程,就关闭这个协程if(smoothSwitchLayerWeightCoroutine != null)StopCoroutine(smoothSwitchLayerWeightCoroutine);// switch (mode){case TPSCharacterBehaviourMode.NoWeapon:smoothSwitchLayerWeightCoroutine = StartCoroutine(SwitchLayerWeight(1, 0));break;case TPSCharacterBehaviourMode.RifleIdle:smoothSwitchLayerWeightCoroutine = StartCoroutine(SwitchLayerWeight(0, 1));break;}}}
}

2.5 TPSCharacterAnimationController.Mode v2

按照我最后的思路就是写成

Assets/MeowFramework/TPSCharacter/Scripts/Controller/TPSCharacterAnimationController.Mode.cs

// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 12/04/2022 15:55
// 最后一次修改于: 22/04/2022 9:39
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------using System.Collections;
using System.Collections.Generic;
using MeowFramework.Core.Switchable;
using Sirenix.OdinInspector;
using UnityEngine;namespace MeowFramework.TPSCharacter
{/// <summary>/// 第三人称动画状态机控制器/// </summary>public partial class TPSCharacterAnimationController{/// <summary>/// 行动模式/// </summary>[BoxGroup("Mode")][ShowInInspector][Sirenix.OdinInspector.ReadOnly][Tooltip("行动模式")]private TPSCharacterBehaviourMode mode;/// <summary>/// 行动模式/// </summary>public TPSCharacterBehaviourMode Mode{get => mode;set{if (mode != value){if (switchValueCoroutine != null)StopCoroutine(switchValueCoroutine);switchValueCoroutine = StartCoroutine(ModeTransition(value));mode = value;}}}/// <summary>/// 切换模式的过渡时间/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("切换模式的过渡时间")]private float modeTransitionTime = 1f;/// <summary>/// 动画状态机第 0 层的权重/// </summary>[BoxGroup("Mode")][Tooltip("动画状态机第 0 层的权重")]public SwitchableFloat AnimLayer0Weight;/// <summary>/// 动画状态机第 1 层的权重/// </summary>[BoxGroup("Mode")][Tooltip("动画状态机第 1 层的权重")]public SwitchableFloat AnimLayer1Weight;/// <summary>/// 动画状态机第 2 层的权重/// </summary>[BoxGroup("Mode")][Tooltip("动画状态机第 2 层的权重")]public SwitchableFloat AnimLayer2Weight;/// <summary>/// 可切换变量列表/// </summary>private List<ISwitchable> switchableObjectList = new List<ISwitchable>();// 缓存 - 模式改变/// <summary>/// 切换变量的协程/// </summary>private Coroutine switchValueCoroutine;/// <summary>/// 初始化可切换变量列表/// </summary>private void InitSwitchableList(){switchableObjectList.Add(AnimLayer0Weight);switchableObjectList.Add(AnimLayer1Weight);switchableObjectList.Add(AnimLayer2Weight);}/// <summary>/// 模式过渡:使变量在不同预设值之间切换/// </summary>/// <param name="mode">预设模式</param>/// <returns></returns>private IEnumerator ModeTransition(TPSCharacterBehaviourMode mode){float time = modeTransitionTime;while(time > 0){time -= Time.deltaTime;foreach (ISwitchable switchable in switchableObjectList){switchable.SwitchValue(mode);}}yield return null;}}
}

但是我发现我还需要做一个数据绑定,把 AnimLayer0Weight, AnimLayer1Weight, AnimLayer2Weight 的值绑定到 Animator

不能在外部替换类里面的属性,那就只能用委托了
所以 SwitchableFloat 还要改

2.6 SwitchableFloat v1.1

修改后的可切换变量,提供了变量改变时的钩子

Assets/MeowFramework/Core/Switchable/SwitchableFloat.cs

// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 22/04/2022 9:01
// 最后一次修改于: 22/04/2022 10:01
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------using System;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;namespace MeowFramework.Core.Switchable
{/// <summary>/// 可切换浮点/// </summary>public class SwitchableFloat : ISwitchable{/// <summary>/// 当前值/// </summary>[ShowInInspector][Tooltip("当前值")]private float value;/// <summary>/// 当前值/// </summary>public float Value{get => value;set{if (this.value != value){AfterValueChangeAction?.Invoke(this.value,value);this.value = value;}}}/// <summary>/// 值改变后触发的委托/// </summary>[HideInInspector]public Action<float, float> AfterValueChangeAction;/// <summary>/// 预设值字典/// </summary>[Tooltip("预设值字典")]public Dictionary<Enum, float> TargetValueDict = new Dictionary<Enum, float>();// 缓存/// <summary>/// 平滑速度/// </summary>private float smoothVelocity;/// <summary>/// 平滑时间/// </summary>[Tooltip("平滑时间")]public float SmoothTime = 0.2f;// 实现接口/// <summary>/// 使变量在不同预设值之间切换/// </summary>/// <param name="mode">预设模式</param>public void SwitchValue(Enum mode){// SmoothDamp if (TargetValueDict.ContainsKey(mode)){float target = TargetValueDict[mode];Value = Mathf.SmoothDamp(Value, target, ref smoothVelocity, SmoothTime);}}}
}

2.7 TPSCharacterAnimationController.Mode v3

添加了数值绑定

Assets/MeowFramework/TPSCharacter/Scripts/Controller/TPSCharacterAnimationController.Mode.cs

// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 12/04/2022 15:55
// 最后一次修改于: 22/04/2022 14:40
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------using System.Collections;
using System.Collections.Generic;
using MeowFramework.Core.Switchable;
using Sirenix.OdinInspector;
using UnityEngine;namespace MeowFramework.TPSCharacter
{/// <summary>/// 第三人称动画状态机控制器/// </summary>public partial class TPSCharacterAnimationController{/// <summary>/// 行动模式/// </summary>[BoxGroup("Mode")][ShowInInspector][Sirenix.OdinInspector.ReadOnly][Tooltip("行动模式")]private TPSCharacterBehaviourMode mode;/// <summary>/// 行动模式/// </summary>public TPSCharacterBehaviourMode Mode{get => mode;set{if (mode != value){if (switchValueCoroutine != null)StopCoroutine(switchValueCoroutine);switchValueCoroutine = StartCoroutine(ModeTransition(value));mode = value;}}}/// <summary>/// 切换模式的过渡时间/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("切换模式的过渡时间")]private float modeTransitionTime = 1f;/// <summary>/// 动画状态机第 0 层的权重/// </summary>[BoxGroup("Mode")][Tooltip("动画状态机第 0 层的权重")]public SwitchableFloat AnimLayer0Weight = new SwitchableFloat();/// <summary>/// 动画状态机第 1 层的权重/// </summary>[BoxGroup("Mode")][Tooltip("动画状态机第 1 层的权重")]public SwitchableFloat AnimLayer1Weight = new SwitchableFloat();/// <summary>/// 动画状态机第 2 层的权重/// </summary>[BoxGroup("Mode")][Tooltip("动画状态机第 2 层的权重")]public SwitchableFloat AnimLayer2Weight = new SwitchableFloat();/// <summary>/// 步枪待机姿态所用到的骨骼绑定的权重/// </summary>[BoxGroup("Mode")][Tooltip("步枪待机姿态所用到的骨骼绑定的权重")]public SwitchableFloat RifleIdleRigWeight = new SwitchableFloat();/// <summary>/// 步枪瞄准姿态所用到的骨骼绑定的权重/// </summary>[BoxGroup("Mode")][Tooltip("步枪瞄准姿态所用到的骨骼绑定的权重")]public SwitchableFloat RifleAimingRigWeight = new SwitchableFloat();/// <summary>/// 可切换变量列表/// </summary>private List<ISwitchable> switchableObjectList = new List<ISwitchable>();// 缓存 - 模式改变/// <summary>/// 切换变量的协程/// </summary>private Coroutine switchValueCoroutine;/// <summary>/// 初始化可切换变量列表/// </summary>private void InitSwitchableList(){switchableObjectList.Add(AnimLayer0Weight);switchableObjectList.Add(AnimLayer1Weight);switchableObjectList.Add(AnimLayer2Weight);switchableObjectList.Add(RifleIdleRigWeight);switchableObjectList.Add(RifleAimingRigWeight);AnimLayer0Weight.AfterValueChangeAction += (oldValue, newValue) => { Anim.SetLayerWeight(0, newValue); };AnimLayer1Weight.AfterValueChangeAction += (oldValue, newValue) => { Anim.SetLayerWeight(1, newValue); };AnimLayer2Weight.AfterValueChangeAction += (oldValue, newValue) => { Anim.SetLayerWeight(2, newValue); };RifleIdleRigWeight.AfterValueChangeAction += (oldValue, newValue) => { rifleIdleRig.weight = newValue; };RifleAimingRigWeight.AfterValueChangeAction += (oldValue, newValue) => { rifleAimingRig.weight = newValue; };}/// <summary>/// 清空可切换变量列表/// </summary>private void ClearSwitchableList(){AnimLayer0Weight.AfterValueChangeAction = null;AnimLayer1Weight.AfterValueChangeAction = null;AnimLayer2Weight.AfterValueChangeAction = null;RifleIdleRigWeight.AfterValueChangeAction = null;RifleAimingRigWeight.AfterValueChangeAction = null;switchableObjectList.Clear();}/// <summary>/// 模式过渡:使变量在不同预设值之间切换/// </summary>/// <param name="mode">预设模式</param>/// <returns></returns>private IEnumerator ModeTransition(TPSCharacterBehaviourMode mode){float time = modeTransitionTime;while(time > 0){time -= Time.deltaTime;foreach (ISwitchable switchable in switchableObjectList){switchable.SwitchValue(mode);}}yield return null;}}
}

2.8 TPSCharacterLocomotionController.Mode v1

已经改造过了 AnimationController,别的就是照葫芦画瓢

旧版 LocomotionController

可以说得上是最需要 Switchable 的了
你看这一开始写了多少变量和 Smooth 语句出来,都是冗余的

Assets/MeowFramework/TPSCharacter/Scripts/Controller/TPSCharacterLocomotionController.Mode.cs

// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 12/04/2022 15:48
// 最后一次修改于: 20/04/2022 16:41
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------using System.Collections;
using System.ComponentModel;
using Cinemachine;
using Sirenix.OdinInspector;
using UnityEngine;namespace MeowFramework.TPSCharacter
{/// <summary>/// 第三人称运动控制器/// </summary>public partial class TPSCharacterLocomotionController{// 模式/// <summary>/// 行动模式/// </summary>[BoxGroup("Mode")][ShowInInspector][Sirenix.OdinInspector.ReadOnly][Tooltip("行动模式")]private TPSCharacterBehaviourMode mode;/// <summary>/// 切换模式的过渡时间/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("切换模式的过渡时间")]private float modeTransitionTime = 1f;/// <summary>/// 没有武器时角色移动速度/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("没有武器时角色移动速度")]private float noWeaponWalkSpeed = 4f;/// <summary>/// 持步枪时角色移动速度/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("持步枪时角色移动速度")]private float rifleWalkSpeed = 2f;/// <summary>/// 没有武器时摄像机的 FOV/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("没有武器时摄像机的 FOV")]private float noWeaponFOV = 40f;/// <summary>/// 持步枪瞄准时摄像机的 FOV/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("持步枪瞄准时摄像机的 FOV")]private float rifleAimingFOV = 30f;/// <summary>/// 摄像机的 FOV 的平滑时间/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("摄像机的目标 FOV 的平滑时间")]private float fovSmoothTime = 0.2f;/// <summary>/// 没有武器时摄像机的侧向位置/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("没有武器时摄像机的侧向位置")]private float noWeaponSide = 0.5f;/// <summary>/// 持步枪瞄准时摄像机的侧向位置/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("持步枪瞄准时摄像机的侧向位置")]private float rifleAimingSide = 1f;/// <summary>/// 摄像机侧向位置的平滑时间/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("摄像机侧向位置的平滑时间")]private float cameraSideSmoothTime = 0.2f;// 缓存// 缓存 - 运动模式/// <summary>/// 模式改变协程/// </summary>private Coroutine modeChangeCoroutine;/// <summary>/// 摄像机的目标 FOV 平滑速度/// </summary>private float fovSmoothVelocity;/// <summary>/// 摄像机侧向位置的平滑速度/// </summary>private float cameraSideSmoothVelocity;/// <summary>/// 切换摄像机配置的协程函数/// </summary>/// <param name="targetFOV">目标 FOV</param>/// <param name="targetSide">目标侧向位置</param>/// <returns></returns>private IEnumerator SwitchCameraSetting(float targetFOV, float targetSide){// 摄像机第三人称跟随组件var camera3rdPersonFollow =PlayerFollowCamera.GetCinemachineComponent<Cinemachine3rdPersonFollow>();// 初始化计时器var timeLeft = modeTransitionTime;// 在给定时间内平滑// 平滑时间结束时,被平滑项接近终点值但不是终点值// 因此最后需要给被平滑项赋终点值,这可能产生一个抖动// 因此平滑时间需要在保证效果的同时尽可能小,才能让最后的抖动变小while (timeLeft > 0){timeLeft -= Time.deltaTime;PlayerFollowCamera.m_Lens.FieldOfView = Mathf.SmoothDamp(PlayerFollowCamera.m_Lens.FieldOfView,targetFOV, ref fovSmoothVelocity, fovSmoothTime);camera3rdPersonFollow.CameraSide = Mathf.SmoothDamp(camera3rdPersonFollow.CameraSide, targetSide,ref cameraSideSmoothVelocity, cameraSideSmoothTime);yield return null;}// 摄像机焦距设置赋终点值PlayerFollowCamera.m_Lens.FieldOfView = targetFOV;// 摄像机侧向位置赋终点值camera3rdPersonFollow.CameraSide = targetSide;yield return null;}/// <summary>/// 改变运动模式/// </summary>/// <param name="mode">模式</param>public void SetLocomotionMode(TPSCharacterBehaviourMode mode){this.mode = mode;if(modeChangeCoroutine != null)StopCoroutine(modeChangeCoroutine);switch (mode){case TPSCharacterBehaviourMode.NoWeapon:shouldRotateToCameraForward = false;modeChangeCoroutine = StartCoroutine(SwitchCameraSetting(noWeaponFOV, noWeaponSide));break;case TPSCharacterBehaviourMode.RifleIdle:shouldRotateToCameraForward = true;modeChangeCoroutine = StartCoroutine(SwitchCameraSetting(noWeaponFOV, noWeaponSide));break;case TPSCharacterBehaviourMode.RifleAiming:shouldRotateToCameraForward = true;modeChangeCoroutine = StartCoroutine(SwitchCameraSetting(rifleAimingFOV, rifleAimingSide));break;}}}
}

2.9 TPSCharacterLocomotionController.Mode v2

Assets/MeowFramework/TPSCharacter/Scripts/Controller/TPSCharacterLocomotionController.Mode.cs

// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 12/04/2022 15:48
// 最后一次修改于: 22/04/2022 18:33
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using Cinemachine;
using MeowFramework.Core.Switchable;
using Sirenix.OdinInspector;
using UnityEngine;namespace MeowFramework.TPSCharacter
{/// <summary>/// 第三人称运动控制器/// </summary>public partial class TPSCharacterLocomotionController{// 模式/// <summary>/// 行动模式/// </summary>[BoxGroup("Mode")][ShowInInspector][Sirenix.OdinInspector.ReadOnly][Tooltip("行动模式")]private TPSCharacterBehaviourMode mode;/// <summary>/// 行动模式/// </summary>public TPSCharacterBehaviourMode Mode{get => mode;set{if (mode != value){if (switchValueCoroutine != null)StopCoroutine(switchValueCoroutine);switchValueCoroutine = StartCoroutine(ModeTransition(value));mode = value;}}}/// <summary>/// 切换模式的过渡时间/// </summary>[BoxGroup("Mode")][Tooltip("切换模式的过渡时间")]public float ModeTransitionTime = 1f;/// <summary>/// 行走速度/// </summary>[BoxGroup("Mode")][Tooltip("行走速度")]public SwitchableFloat WalkSpeed = new SwitchableFloat();/// <summary>/// 摄像机 FOV/// </summary>[BoxGroup("Mode")][Tooltip("摄像机 FOV")]public SwitchableFloat CameraFOV = new SwitchableFloat();/// <summary>/// 摄像机侧向位置/// </summary>[BoxGroup("Mode")][Tooltip("摄像机侧向位置")]public SwitchableFloat CameraSide = new SwitchableFloat();// 缓存// 缓存 - 运动模式/// <summary>/// 可切换变量列表/// </summary>private List<ISwitchable> switchableObjectList = new List<ISwitchable>();// 缓存 - 模式改变/// <summary>/// 切换变量的协程/// </summary>private Coroutine switchValueCoroutine;/// <summary>/// 初始化可切换变量列表/// </summary>private void InitSwitchableList(){// 摄像机第三人称跟随组件var camera3rdPersonFollow =PlayerFollowCamera.GetCinemachineComponent<Cinemachine3rdPersonFollow>();switchableObjectList.Add(WalkSpeed);switchableObjectList.Add(CameraFOV);switchableObjectList.Add(CameraSide);WalkSpeed.AfterValueChangeAction += (oldValue, newValue) => { walkSpeed = newValue; };CameraFOV.AfterValueChangeAction += (oldValue, newValue) => { PlayerFollowCamera.m_Lens.FieldOfView = newValue; };CameraSide.AfterValueChangeAction += (oldValue, newValue) => { camera3rdPersonFollow.CameraSide = newValue; };}/// <summary>/// 清空可切换变量列表/// </summary>private void ClearSwitchableList(){WalkSpeed.AfterValueChangeAction = null;CameraFOV.AfterValueChangeAction = null;CameraSide.AfterValueChangeAction = null;switchableObjectList.Clear();}/// <summary>/// 模式过渡:使变量在不同预设值之间切换/// </summary>/// <param name="mode">预设模式</param>/// <returns></returns>private IEnumerator ModeTransition(TPSCharacterBehaviourMode mode){float time = ModeTransitionTime;while(time > 0){time -= Time.deltaTime;foreach (ISwitchable switchable in switchableObjectList){switchable.SwitchValue(mode);}}yield return null;}}
}

[Unity] 战斗系统学习 12:Switchable 1相关推荐

  1. [Unity] 战斗系统学习 4:FlowCanvas 中的 LatentActionNode

    [Unity] 战斗系统学习 2:FlowCanvas 中的 SubGraph 1. 并行执行 FlowScript 可能的魔改方向 其实我原本是想魔改 FlowCanvas 使其支持并行 FlowS ...

  2. [Unity] 战斗系统学习 5:构建 TPS 框架 1

    [Unity] ACT 战斗系统学习 4:重构前的第三人称控制器 以前看猴与花果山的文章,感觉大开眼界,但是没做过所以没更多体会 https://zhuanlan.zhihu.com/p/416805 ...

  3. [Unity] 战斗系统学习 13:Switchable 2

    1. Switchable v2 改了这两个 TPSCharacterAnimationController.Mode TPSCharacterLocomotionController.Mode 之后 ...

  4. [Unity] 战斗系统学习 8:构建 TPS 框架 3:mono 组件

    1. 框架组件 1.1 FrameworkComponent v1 一开始我想的是这样做框架组件嘛,跟 GF 学的 但是后来我才知道 static 变量是默认在监视器上不显示的,怪不得 GF 不在 A ...

  5. [Unity] 战斗系统学习 11:Buff 框架 1

    1. Time 一开始我是希望有一个 ScriptableObject 可以用来计时的 对于一些需要持续计时的事情可以用 1.1 ScriptableTimer Assets/MeowFramewor ...

  6. [Unity] 战斗系统学习 10:ActorAttribute

    1. ActorAttribute v1 一开始我是想做一个可以使用泛型 ScriptableObject 的角色属性 1.1 ScriptableGenericVariable v1 泛型 Scri ...

  7. [Unity] 战斗系统学习 6:构建 TPS 框架 2

    1. Port 与 BBParameter 我一开始就是公开组件接口,那个时候是因为我还只会用 AddValueInput,不会用 BBParameter 但是后来我发现 BBParameter 很简 ...

  8. [Unity] 战斗系统学习 9:构建 TPS 框架 4

    1. 技能框架 我的需求是要在 FlowScript 中写技能,然后在角色中引用这个 FlowScript,当需要释放技能的时候就启用这个 FlowScript 1.1 ScriptableAbili ...

  9. [Unity] 战斗系统学习 3:FlowCanvas 中的 Input System

    1. 常规 Input System 使用 Assets/MeowFramework/MeowACT/InputSystem/MeowACTInputController.cs // -------- ...

最新文章

  1. 使用Visual Studio工作流发布SharePoint网页
  2. Swift 4正式发布,新功能概览
  3. 0 重新学习Ubuntu -- 这一段没怎么学习
  4. Android之简单的文件夹选择器实现
  5. javascript 常量_JavaScript中的常量
  6. 计算机音乐奔跑,跑起来!100首最适合跑步的音乐
  7. LightOJ 1096 - nth Term 矩阵快速幂
  8. Virtualbox 2.1突发性错误解决办法(也许是BUG)
  9. 交换排序 java_Java交换排序:冒泡排序和快速排序
  10. Internet Download Manager永久版功能强大的网络下载器
  11. 在Linux中修复U盘
  12. 天网防火墙存在一个安全隐患
  13. ensp华为防火墙及应用
  14. Opencv求轮廓的中心点坐标
  15. Sketch52 52.1 新功能介绍(包含下载链接)
  16. 【MATLAB】一个宝藏博主公开的代码,给它加个速——水晶爱心模块
  17. ECSHOP去掉版权
  18. 手把手MATLAB 简单连续信号表示 指数 正弦 抽样 矩形 三角波 信号
  19. MODE-CSR相关
  20. 钟汉良日记:网络也是江湖,有恩怨情仇有利益纠葛

热门文章

  1. 测试VS开发 [ 光影人像 东海陈光剑 的博客 ]
  2. 阿里云态势感知和安骑士区别有哪些?
  3. Linux python PyQt5调用百度API实现图片文字转换
  4. EOS开发HelloWorld智能合约
  5. 读《月亮和六便士》感触
  6. 文献 Application of deep learning tothe diagnosis of cervical lymph node metastasis from thyroid阅读笔记
  7. 使用Gson将Java对象转换为JSON
  8. 什么是显卡,显卡的主要部件和主要技术规格有哪些
  9. ISCC2019线上赛杂项Misc Write up
  10. 引文分析工具HistCite Pro与CiteSpace的安装