IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> [Unity] 战斗系统学习 12:Switchable 1 -> 正文阅读

[游戏开发][Unity] 战斗系统学习 12:Switchable 1

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;
        }
    }
}

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-04-23 11:07:26  更:2022-04-23 11:07:44 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:38:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码