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 V 对 mode 的不同值有不同预设值,把这些预设值做成一个 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];
但是如果我想要 V 在 setting 之间使用 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 的缓存变量
那要解决这个问题的话,我目前只能想到是
- 对每一种
setting 里面可能出现的类型建一个类,叫 SwitchableObject 比如 Vector3 就是 SwitchableVector3 ,float 就是 SwitchableFloat 以 SwitchableFloat 为例,它包含一个 float Value ,一个 List<float> SwitchableValueList 和一个 float SmoothVelocity - 新建一个接口
ISwitchable 包含一个 void SwitchValue(int index) 函数,SwitchableFloat 继承 ISwitchable ,函数内容是 float 类型的 SmoothDamp 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];
}
}
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];
}
}
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
using System;
namespace MeowFramework.Core.Switchable
{
public interface ISwitchable
{
public void SwitchValue(Enum mode);
}
}
2.2 SwitchableFloat v1
Assets/MeowFramework/Core/Switchable/SwitchableFloat.cs
using System;
using System.Collections.Generic;
using UnityEngine;
namespace MeowFramework.Core.Switchable
{
public class SwitchableFloat : ISwitchable
{
[Tooltip("当前值")]
public float Value;
[Tooltip("预设值字典")]
public Dictionary<Enum, float> TargetValueDict;
private float smoothVelocity;
[Tooltip("平滑时间")]
public float SmoothTime = 0.2f;
public void SwitchValue(Enum mode)
{
float target = TargetValueDict[mode];
Value = Mathf.SmoothDamp(Value, target, ref smoothVelocity, SmoothTime);
}
}
}
2.3 TPSCharacterAnimationSetting v1
这是第一版方案,是废弃了的 拿出来是为了对比,显示这样做有多笨
Assets/MeowFramework/TPSCharacter/Scripts/Struct/TPSCharacterAnimationSetting.cs
namespace MeowFramework.TPSCharacter.Struct
{
public struct TPSCharacterAnimationSetting
{
public float NoWeaponLayerWeight;
public float RifleIdleLayerWeight;
public float RifleAimingLayerWeight;
public float NoWeaponRigWeight;
public float RifleIdleRigWeight;
public float RifleAimingRigWeight;
}
}
2.4 TPSCharacterAnimationController.Mode v1
这是一个修改前的半成品……确实有些地方直接就是跑不通的 拿出来是为了对比,显示这样做有多笨
Assets/MeowFramework/TPSCharacter/Scripts/Controller/TPSCharacterAnimationController.Mode.cs
using System.Collections;
using System.Collections.Generic;
using MeowFramework.TPSCharacter.Struct;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Animations.Rigging;
namespace MeowFramework.TPSCharacter
{
public partial class TPSCharacterAnimationController
{
[BoxGroup("Mode")]
[ShowInInspector]
[Sirenix.OdinInspector.ReadOnly]
[Tooltip("行动模式")]
private TPSCharacterBehaviourMode mode;
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("无武器层的动画层级的序号")]
private int noWeaponLayerIndex = 0;
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("持步枪待机层的动画层级的序号")]
private int rifleIdleLayerIndex = 0;
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("持步枪瞄准层的动画层级的序号")]
private int rifleAimingLayerIndex = 0;
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("无武器层的动画参数配置")]
private TPSCharacterAnimationSetting noWeaponSetting;
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("持步枪待机层的动画参数配置")]
private TPSCharacterAnimationSetting rifleIdleSetting;
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("持步枪瞄准层的动画参数配置")]
private TPSCharacterAnimationSetting rifleAimingSetting;
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("切换模式的过渡时间")]
private float modeTransitionTime = 1f;
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("层级平滑时间")]
private float layerWeightSmoothTime = 0.2f;
private Coroutine smoothSwitchLayerWeightCoroutine;
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;
}
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
using System.Collections;
using System.Collections.Generic;
using MeowFramework.Core.Switchable;
using Sirenix.OdinInspector;
using UnityEngine;
namespace MeowFramework.TPSCharacter
{
public partial class TPSCharacterAnimationController
{
[BoxGroup("Mode")]
[ShowInInspector]
[Sirenix.OdinInspector.ReadOnly]
[Tooltip("行动模式")]
private TPSCharacterBehaviourMode mode;
public TPSCharacterBehaviourMode Mode
{
get => mode;
set
{
if (mode != value)
{
if (switchValueCoroutine != null)
StopCoroutine(switchValueCoroutine);
switchValueCoroutine = StartCoroutine(ModeTransition(value));
mode = value;
}
}
}
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("切换模式的过渡时间")]
private float modeTransitionTime = 1f;
[BoxGroup("Mode")]
[Tooltip("动画状态机第 0 层的权重")]
public SwitchableFloat AnimLayer0Weight;
[BoxGroup("Mode")]
[Tooltip("动画状态机第 1 层的权重")]
public SwitchableFloat AnimLayer1Weight;
[BoxGroup("Mode")]
[Tooltip("动画状态机第 2 层的权重")]
public SwitchableFloat AnimLayer2Weight;
private List<ISwitchable> switchableObjectList = new List<ISwitchable>();
private Coroutine switchValueCoroutine;
private void InitSwitchableList()
{
switchableObjectList.Add(AnimLayer0Weight);
switchableObjectList.Add(AnimLayer1Weight);
switchableObjectList.Add(AnimLayer2Weight);
}
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
using System;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
namespace MeowFramework.Core.Switchable
{
public class SwitchableFloat : ISwitchable
{
[ShowInInspector]
[Tooltip("当前值")]
private float value;
public float Value
{
get => value;
set
{
if (this.value != value)
{
AfterValueChangeAction?.Invoke(this.value,value);
this.value = value;
}
}
}
[HideInInspector]
public Action<float, float> AfterValueChangeAction;
[Tooltip("预设值字典")]
public Dictionary<Enum, float> TargetValueDict = new Dictionary<Enum, float>();
private float smoothVelocity;
[Tooltip("平滑时间")]
public float SmoothTime = 0.2f;
public void SwitchValue(Enum mode)
{
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
using System.Collections;
using System.Collections.Generic;
using MeowFramework.Core.Switchable;
using Sirenix.OdinInspector;
using UnityEngine;
namespace MeowFramework.TPSCharacter
{
public partial class TPSCharacterAnimationController
{
[BoxGroup("Mode")]
[ShowInInspector]
[Sirenix.OdinInspector.ReadOnly]
[Tooltip("行动模式")]
private TPSCharacterBehaviourMode mode;
public TPSCharacterBehaviourMode Mode
{
get => mode;
set
{
if (mode != value)
{
if (switchValueCoroutine != null)
StopCoroutine(switchValueCoroutine);
switchValueCoroutine = StartCoroutine(ModeTransition(value));
mode = value;
}
}
}
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("切换模式的过渡时间")]
private float modeTransitionTime = 1f;
[BoxGroup("Mode")]
[Tooltip("动画状态机第 0 层的权重")]
public SwitchableFloat AnimLayer0Weight = new SwitchableFloat();
[BoxGroup("Mode")]
[Tooltip("动画状态机第 1 层的权重")]
public SwitchableFloat AnimLayer1Weight = new SwitchableFloat();
[BoxGroup("Mode")]
[Tooltip("动画状态机第 2 层的权重")]
public SwitchableFloat AnimLayer2Weight = new SwitchableFloat();
[BoxGroup("Mode")]
[Tooltip("步枪待机姿态所用到的骨骼绑定的权重")]
public SwitchableFloat RifleIdleRigWeight = new SwitchableFloat();
[BoxGroup("Mode")]
[Tooltip("步枪瞄准姿态所用到的骨骼绑定的权重")]
public SwitchableFloat RifleAimingRigWeight = new SwitchableFloat();
private List<ISwitchable> switchableObjectList = new List<ISwitchable>();
private Coroutine switchValueCoroutine;
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; };
}
private void ClearSwitchableList()
{
AnimLayer0Weight.AfterValueChangeAction = null;
AnimLayer1Weight.AfterValueChangeAction = null;
AnimLayer2Weight.AfterValueChangeAction = null;
RifleIdleRigWeight.AfterValueChangeAction = null;
RifleAimingRigWeight.AfterValueChangeAction = null;
switchableObjectList.Clear();
}
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
using System.Collections;
using System.ComponentModel;
using Cinemachine;
using Sirenix.OdinInspector;
using UnityEngine;
namespace MeowFramework.TPSCharacter
{
public partial class TPSCharacterLocomotionController
{
[BoxGroup("Mode")]
[ShowInInspector]
[Sirenix.OdinInspector.ReadOnly]
[Tooltip("行动模式")]
private TPSCharacterBehaviourMode mode;
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("切换模式的过渡时间")]
private float modeTransitionTime = 1f;
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("没有武器时角色移动速度")]
private float noWeaponWalkSpeed = 4f;
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("持步枪时角色移动速度")]
private float rifleWalkSpeed = 2f;
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("没有武器时摄像机的 FOV")]
private float noWeaponFOV = 40f;
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("持步枪瞄准时摄像机的 FOV")]
private float rifleAimingFOV = 30f;
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("摄像机的目标 FOV 的平滑时间")]
private float fovSmoothTime = 0.2f;
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("没有武器时摄像机的侧向位置")]
private float noWeaponSide = 0.5f;
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("持步枪瞄准时摄像机的侧向位置")]
private float rifleAimingSide = 1f;
[BoxGroup("Mode")]
[ShowInInspector]
[Tooltip("摄像机侧向位置的平滑时间")]
private float cameraSideSmoothTime = 0.2f;
private Coroutine modeChangeCoroutine;
private float fovSmoothVelocity;
private float cameraSideSmoothVelocity;
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;
}
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
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using Cinemachine;
using MeowFramework.Core.Switchable;
using Sirenix.OdinInspector;
using UnityEngine;
namespace MeowFramework.TPSCharacter
{
public partial class TPSCharacterLocomotionController
{
[BoxGroup("Mode")]
[ShowInInspector]
[Sirenix.OdinInspector.ReadOnly]
[Tooltip("行动模式")]
private TPSCharacterBehaviourMode mode;
public TPSCharacterBehaviourMode Mode
{
get => mode;
set
{
if (mode != value)
{
if (switchValueCoroutine != null)
StopCoroutine(switchValueCoroutine);
switchValueCoroutine = StartCoroutine(ModeTransition(value));
mode = value;
}
}
}
[BoxGroup("Mode")]
[Tooltip("切换模式的过渡时间")]
public float ModeTransitionTime = 1f;
[BoxGroup("Mode")]
[Tooltip("行走速度")]
public SwitchableFloat WalkSpeed = new SwitchableFloat();
[BoxGroup("Mode")]
[Tooltip("摄像机 FOV")]
public SwitchableFloat CameraFOV = new SwitchableFloat();
[BoxGroup("Mode")]
[Tooltip("摄像机侧向位置")]
public SwitchableFloat CameraSide = new SwitchableFloat();
private List<ISwitchable> switchableObjectList = new List<ISwitchable>();
private Coroutine switchValueCoroutine;
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; };
}
private void ClearSwitchableList()
{
WalkSpeed.AfterValueChangeAction = null;
CameraFOV.AfterValueChangeAction = null;
CameraSide.AfterValueChangeAction = null;
switchableObjectList.Clear();
}
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;
}
}
}
|