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];
// 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