[Unity] 战斗系统学习 12:Switchable 1
1. 批量 SmoothDamp 變量的需求
1.1 例子
這是我一個(gè)改到一半的函數(shù)……我懶得改了
這個(gè)函數(shù)的目的是從一個(gè)模式轉(zhuǎn)換到另外一個(gè)模式的時(shí)候開始對(duì)一堆變量在一個(gè)時(shí)間內(nèi) SmoothDamp
目前是只有兩個(gè)變量,所以我可以這么寫,但是萬一我都很多個(gè)變量呢?萬一我要頻繁地修改變量的名字,個(gè)數(shù)啥的呢?
我就感覺很麻煩
本來其實(shí)在不同模式之間切換的時(shí)候,需要 SmoothDamp 的變量其實(shí)是不同的,從 A 到 B 要?jiǎng)尤齻€(gè)變量,但是從 B 到 C 可能就只需要?jiǎng)右粋€(gè)
你要說用狀態(tài)機(jī)把,其實(shí)也沒必要,因?yàn)檫@并沒有 OnEnter OnUpdate OnExist 啥的需求
所以我就想怎么方便地做這個(gè)切換的函數(shù)
1.2 分析
一個(gè) mono 中有一個(gè)變量組,記為 V,一個(gè)表示模式的 Enum 變量,命名為 mode
V 對(duì) mode 的不同值有不同預(yù)設(shè)值,把這些預(yù)設(shè)值做成一個(gè) Struct 命名為 Setting
那么如果我想 V 的值在 mode 變化時(shí),可以在與 mode 對(duì)應(yīng)的 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 過渡,我就必須要對(duì) setting 中的每一項(xiàng)都創(chuàng)建一個(gè)緩存變量 velocity 輸入到 smoothdamp 函數(shù)里面,感覺這樣有點(diǎn)麻煩
并且對(duì)于每一個(gè)不同的 V 我都要寫一堆 smoothdamp 代碼
比如如果 V 要轉(zhuǎn)到 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);這樣就會(huì)很冗余……但是又不能做成一個(gè)大的列表然后遍歷這個(gè)列表 smoothdamp
比如
偽代碼
for(int i = 1;i < V.Count; ++i) {V[i] = Math.SmoothDamp(V[i], setting0[i], Velocity[i], smoothTime); }因?yàn)?V 中的變量的類型可能是不同的,不能放到一個(gè)列表中
要放到一個(gè)列表中也可以,那就裝箱成 object,然后多一個(gè)數(shù)組記錄第 i 個(gè)變量的類型,再轉(zhuǎn)回去,這樣效率就太低了,而且感覺很蠢
所以問題就是,當(dāng)我需要批量給一組變量插值的時(shí)候,我會(huì)寫出數(shù)量為 n 的插值語句和數(shù)量為 n 的緩存變量
那要解決這個(gè)問題的話,我目前只能想到是
比如 Vector3 就是 SwitchableVector3,float 就是 SwitchableFloat
以 SwitchableFloat 為例,它包含一個(gè) float Value,一個(gè) List<float> SwitchableValueList 和一個(gè) float SmoothVelocity
這樣,法一
偽代碼
// 法一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}}; }現(xiàn)在是
偽代碼
// 法二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)); }現(xiàn)在我需要
偽代碼
// 法二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)); }不知道我這樣寫行不行,會(huì)有什么問題……
這個(gè)看上去是很好的
一個(gè)數(shù)據(jù)表,假設(shè)行號(hào)是 Enum 列號(hào)是變量名
這樣做把數(shù)據(jù)表的每一列拆到每一個(gè)變量里面
但是實(shí)際上符合習(xí)慣的做法是一行一行的
如果我真的要把數(shù)據(jù)表的一行放到一起,比如放到 ScriptableObject 里面
那么我取變量的目標(biāo)值的流程就是:輸入一個(gè)變量,然后通過反射拿到這個(gè)變量的名字,然后根據(jù) Enum 在 字典 <Enum, Setting> 拿到 Setting,然后這個(gè) Setting 也是一個(gè) <string, 變量> 的字典,根據(jù)這個(gè)變量的名字在這個(gè)字典中拿到目標(biāo)值
但是這樣的話,這個(gè)函數(shù)有不同類型,Setting 中的字典也有不同類型,Setting 中還要寫一個(gè)初始化函數(shù)把目標(biāo)值放到不同類型的字典中
要不然就寫成 Setting 里面只有不同類型的字典,這樣就省去了初始化的麻煩
那么用的時(shí)候就是
偽代碼
// 法三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)); }我不用泛型一個(gè)原因是在監(jiān)視器上配置實(shí)現(xiàn)泛型的變量會(huì)出錯(cuò),即使用了 Odin,另一個(gè)原因是 SmoothDamp 沒有泛型
跟我討論的朋友問我為什么不用 Animator,我說那是用來做骨骼動(dòng)畫的
但是后來我又想到 timeline,他是可以控制腳本的
首先的問題是我不知道怎么制作變量軌道
就算我知道了,變量軌道也需要一個(gè)確定的初值和終值,我這個(gè)是需要隨時(shí)切換狀態(tài),比如按住右鍵瞄準(zhǔn),他可能一會(huì)瞄準(zhǔn)一會(huì)不秒,切換時(shí)間小于動(dòng)畫時(shí)間,那么如果第一次動(dòng)畫時(shí)間,當(dāng)前值就是初值,第二次相反的退出瞄準(zhǔn)動(dòng)畫開始時(shí),當(dāng)前值也不會(huì)是退出瞄準(zhǔn)動(dòng)畫的初值
除非我可以讓動(dòng)畫的初值為當(dāng)前值,或者我可以根據(jù)當(dāng)前值,動(dòng)畫的初值終值得到我應(yīng)該在哪個(gè)百分比進(jìn)度播放動(dòng)畫……
好吧我后面知道了 Playable Track 是可以自定義 Clip 的
好麻煩……不想看……我就是懶hhhh
而且按照我最后的法四,我可以在監(jiān)視器中對(duì)每一項(xiàng)變量設(shè)置對(duì)每一個(gè) mode 的可能值的目標(biāo)值,如果我不設(shè)置說明我就不用平滑這個(gè)變量
這是點(diǎn)開組件就能在監(jiān)視器中看到的,timeline 可不行
再化簡(jiǎn)一點(diǎ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;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
// ---------------------------------------------- // 作者: 廉價(jià)喵 // 創(chuàng)建于: 22/04/2022 9:00 // 最后一次修改于: 22/04/2022 9:11 // 版權(quán)所有: CheapMeowStudio // 描述: // ----------------------------------------------using System;namespace MeowFramework.Core.Switchable {/// <summary>/// 切換變量的接口/// </summary>public interface ISwitchable{/// <summary>/// 使變量在不同預(yù)設(shè)值之間切換/// </summary>/// <param name="mode">預(yù)設(shè)模式</param>public void SwitchValue(Enum mode);} }2.2 SwitchableFloat v1
Assets/MeowFramework/Core/Switchable/SwitchableFloat.cs
// ---------------------------------------------- // 作者: 廉價(jià)喵 // 創(chuàng)建于: 22/04/2022 9:01 // 最后一次修改于: 22/04/2022 9:11 // 版權(quán)所有: CheapMeowStudio // 描述: // ----------------------------------------------using System; using System.Collections.Generic; using UnityEngine;namespace MeowFramework.Core.Switchable {/// <summary>/// 可切換浮點(diǎn)/// </summary>public class SwitchableFloat : ISwitchable{/// <summary>/// 當(dāng)前值/// </summary>[Tooltip("當(dāng)前值")]public float Value;/// <summary>/// 預(yù)設(shè)值字典/// </summary>[Tooltip("預(yù)設(shè)值字典")]public Dictionary<Enum, float> TargetValueDict;// 緩存/// <summary>/// 平滑速度/// </summary>private float smoothVelocity;/// <summary>/// 平滑時(shí)間/// </summary>[Tooltip("平滑時(shí)間")]public float SmoothTime = 0.2f;// 實(shí)現(xiàn)接口/// <summary>/// 使變量在不同預(yù)設(shè)值之間切換/// </summary>/// <param name="mode">預(yù)設(shè)模式</param>public void SwitchValue(Enum mode){// SmoothDamp float target = TargetValueDict[mode];Value = Mathf.SmoothDamp(Value, target, ref smoothVelocity, SmoothTime);}} }2.3 TPSCharacterAnimationSetting v1
這是第一版方案,是廢棄了的
拿出來是為了對(duì)比,顯示這樣做有多笨
Assets/MeowFramework/TPSCharacter/Scripts/Struct/TPSCharacterAnimationSetting.cs
// ---------------------------------------------- // 作者: 廉價(jià)喵 // 創(chuàng)建于: 20/04/2022 17:52 // 最后一次修改于: 20/04/2022 17:57 // 版權(quán)所有: CheapMeowStudio // 描述: // ----------------------------------------------namespace MeowFramework.TPSCharacter.Struct {public struct TPSCharacterAnimationSetting{/// <summary>/// 無武器層的動(dòng)畫層級(jí)權(quán)重/// </summary>public float NoWeaponLayerWeight;/// <summary>/// 持槍待機(jī)層的動(dòng)畫層級(jí)權(quán)重/// </summary>public float RifleIdleLayerWeight;/// <summary>/// 持槍瞄準(zhǔn)層的動(dòng)畫層級(jí)權(quán)重/// </summary>public float RifleAimingLayerWeight;/// <summary>/// 無武器層的骨骼綁定權(quán)重/// </summary>public float NoWeaponRigWeight;/// <summary>/// 持槍待機(jī)層的骨骼綁定權(quán)重/// </summary>public float RifleIdleRigWeight;/// <summary>/// 持槍瞄準(zhǔn)層的骨骼綁定權(quán)重/// </summary>public float RifleAimingRigWeight;} }2.4 TPSCharacterAnimationController.Mode v1
這是一個(gè)修改前的半成品……確實(shí)有些地方直接就是跑不通的
拿出來是為了對(duì)比,顯示這樣做有多笨
Assets/MeowFramework/TPSCharacter/Scripts/Controller/TPSCharacterAnimationController.Mode.cs
// ---------------------------------------------- // 作者: 廉價(jià)喵 // 創(chuàng)建于: 12/04/2022 15:55 // 最后一次修改于: 22/04/2022 9:01 // 版權(quán)所有: 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>/// 第三人稱動(dòng)畫狀態(tài)機(jī)控制器/// </summary>public partial class TPSCharacterAnimationController{/// <summary>/// 行動(dòng)模式/// </summary>[BoxGroup("Mode")][ShowInInspector][Sirenix.OdinInspector.ReadOnly][Tooltip("行動(dòng)模式")]private TPSCharacterBehaviourMode mode;/// <summary>/// 無武器層的動(dòng)畫層級(jí)的序號(hào)/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("無武器層的動(dòng)畫層級(jí)的序號(hào)")]private int noWeaponLayerIndex = 0;/// <summary>/// 持步槍待機(jī)層的動(dòng)畫層級(jí)的序號(hào)/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("持步槍待機(jī)層的動(dòng)畫層級(jí)的序號(hào)")]private int rifleIdleLayerIndex = 0;/// <summary>/// 持步槍瞄準(zhǔn)層的動(dòng)畫層級(jí)的序號(hào)/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("持步槍瞄準(zhǔn)層的動(dòng)畫層級(jí)的序號(hào)")]private int rifleAimingLayerIndex = 0;/// <summary>/// 無武器層的動(dòng)畫參數(shù)配置/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("無武器層的動(dòng)畫參數(shù)配置")]private TPSCharacterAnimationSetting noWeaponSetting;/// <summary>/// 持步槍待機(jī)層的動(dòng)畫參數(shù)配置/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("持步槍待機(jī)層的動(dòng)畫參數(shù)配置")]private TPSCharacterAnimationSetting rifleIdleSetting;/// <summary>/// 持步槍瞄準(zhǔn)層的動(dòng)畫參數(shù)配置/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("持步槍瞄準(zhǔn)層的動(dòng)畫參數(shù)配置")]private TPSCharacterAnimationSetting rifleAimingSetting;/// <summary>/// 切換模式的過渡時(shí)間/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("切換模式的過渡時(shí)間")]private float modeTransitionTime = 1f;/// <summary>/// 層級(jí)平滑時(shí)間/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("層級(jí)平滑時(shí)間")]private float layerWeightSmoothTime = 0.2f;// 緩存 - 模式改變/// <summary>/// 平滑切換層級(jí)權(quán)重的協(xié)程/// </summary>private Coroutine smoothSwitchLayerWeightCoroutine;/// <summary>/// 平滑切換層級(jí)權(quán)重/// </summary>/// <param name="fromLayer">舊層級(jí)</param>/// <param name="toLayer">新層級(jí)</param>/// <returns></returns>private IEnumerator SwitchAnimationSetting(TPSCharacterBehaviourMode mode){// 初始化計(jì)時(shí)器var timeLeft = modeTransitionTime;// 舊層級(jí)權(quán)重平滑速度float fromLayerWeightSmoothVelocity = 0f;// 新層級(jí)權(quán)重平滑速度float toLayerWeightSmoothVelocity = 0f;// 舊層級(jí)權(quán)重var fromWeight = Anim.GetLayerWeight(fromLayer);// 新層級(jí)權(quán)重var toWeight = Anim.GetLayerWeight(toLayer);// 在給定時(shí)間內(nèi)平滑// 平滑時(shí)間結(jié)束時(shí),被平滑項(xiàng)接近終點(diǎn)值但不是終點(diǎn)值// 因此最后需要給被平滑項(xiàng)賦終點(diǎn)值,這可能產(chǎn)生一個(gè)抖動(dòng)// 因此平滑時(shí)間需要在保證效果的同時(shí)盡可能小,才能讓最后的抖動(dòng)變小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;}// 賦終點(diǎn)值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>/// 設(shè)置動(dòng)畫模式/// </summary>/// <param name="mode">模式</param>public void SetAnimationMode(TPSCharacterBehaviourMode mode){// 更新模式記錄this.mode = mode;// 如果有正在進(jìn)行的模式切換相關(guān)的協(xié)程,就關(guān)閉這個(gè)協(xié)程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
// ---------------------------------------------- // 作者: 廉價(jià)喵 // 創(chuàng)建于: 12/04/2022 15:55 // 最后一次修改于: 22/04/2022 9:39 // 版權(quán)所有: CheapMeowStudio // 描述: // ----------------------------------------------using System.Collections; using System.Collections.Generic; using MeowFramework.Core.Switchable; using Sirenix.OdinInspector; using UnityEngine;namespace MeowFramework.TPSCharacter {/// <summary>/// 第三人稱動(dòng)畫狀態(tài)機(jī)控制器/// </summary>public partial class TPSCharacterAnimationController{/// <summary>/// 行動(dòng)模式/// </summary>[BoxGroup("Mode")][ShowInInspector][Sirenix.OdinInspector.ReadOnly][Tooltip("行動(dòng)模式")]private TPSCharacterBehaviourMode mode;/// <summary>/// 行動(dòng)模式/// </summary>public TPSCharacterBehaviourMode Mode{get => mode;set{if (mode != value){if (switchValueCoroutine != null)StopCoroutine(switchValueCoroutine);switchValueCoroutine = StartCoroutine(ModeTransition(value));mode = value;}}}/// <summary>/// 切換模式的過渡時(shí)間/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("切換模式的過渡時(shí)間")]private float modeTransitionTime = 1f;/// <summary>/// 動(dòng)畫狀態(tài)機(jī)第 0 層的權(quán)重/// </summary>[BoxGroup("Mode")][Tooltip("動(dòng)畫狀態(tài)機(jī)第 0 層的權(quán)重")]public SwitchableFloat AnimLayer0Weight;/// <summary>/// 動(dòng)畫狀態(tài)機(jī)第 1 層的權(quán)重/// </summary>[BoxGroup("Mode")][Tooltip("動(dòng)畫狀態(tài)機(jī)第 1 層的權(quán)重")]public SwitchableFloat AnimLayer1Weight;/// <summary>/// 動(dòng)畫狀態(tài)機(jī)第 2 層的權(quán)重/// </summary>[BoxGroup("Mode")][Tooltip("動(dòng)畫狀態(tài)機(jī)第 2 層的權(quán)重")]public SwitchableFloat AnimLayer2Weight;/// <summary>/// 可切換變量列表/// </summary>private List<ISwitchable> switchableObjectList = new List<ISwitchable>();// 緩存 - 模式改變/// <summary>/// 切換變量的協(xié)程/// </summary>private Coroutine switchValueCoroutine;/// <summary>/// 初始化可切換變量列表/// </summary>private void InitSwitchableList(){switchableObjectList.Add(AnimLayer0Weight);switchableObjectList.Add(AnimLayer1Weight);switchableObjectList.Add(AnimLayer2Weight);}/// <summary>/// 模式過渡:使變量在不同預(yù)設(shè)值之間切換/// </summary>/// <param name="mode">預(yù)設(shè)模式</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;}} }但是我發(fā)現(xiàn)我還需要做一個(gè)數(shù)據(jù)綁定,把 AnimLayer0Weight, AnimLayer1Weight, AnimLayer2Weight 的值綁定到 Animator
不能在外部替換類里面的屬性,那就只能用委托了
所以 SwitchableFloat 還要改
2.6 SwitchableFloat v1.1
修改后的可切換變量,提供了變量改變時(shí)的鉤子
Assets/MeowFramework/Core/Switchable/SwitchableFloat.cs
// ---------------------------------------------- // 作者: 廉價(jià)喵 // 創(chuàng)建于: 22/04/2022 9:01 // 最后一次修改于: 22/04/2022 10:01 // 版權(quán)所有: CheapMeowStudio // 描述: // ----------------------------------------------using System; using System.Collections.Generic; using Sirenix.OdinInspector; using UnityEngine;namespace MeowFramework.Core.Switchable {/// <summary>/// 可切換浮點(diǎn)/// </summary>public class SwitchableFloat : ISwitchable{/// <summary>/// 當(dāng)前值/// </summary>[ShowInInspector][Tooltip("當(dāng)前值")]private float value;/// <summary>/// 當(dāng)前值/// </summary>public float Value{get => value;set{if (this.value != value){AfterValueChangeAction?.Invoke(this.value,value);this.value = value;}}}/// <summary>/// 值改變后觸發(fā)的委托/// </summary>[HideInInspector]public Action<float, float> AfterValueChangeAction;/// <summary>/// 預(yù)設(shè)值字典/// </summary>[Tooltip("預(yù)設(shè)值字典")]public Dictionary<Enum, float> TargetValueDict = new Dictionary<Enum, float>();// 緩存/// <summary>/// 平滑速度/// </summary>private float smoothVelocity;/// <summary>/// 平滑時(shí)間/// </summary>[Tooltip("平滑時(shí)間")]public float SmoothTime = 0.2f;// 實(shí)現(xiàn)接口/// <summary>/// 使變量在不同預(yù)設(shè)值之間切換/// </summary>/// <param name="mode">預(yù)設(shè)模式</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
添加了數(shù)值綁定
Assets/MeowFramework/TPSCharacter/Scripts/Controller/TPSCharacterAnimationController.Mode.cs
// ---------------------------------------------- // 作者: 廉價(jià)喵 // 創(chuàng)建于: 12/04/2022 15:55 // 最后一次修改于: 22/04/2022 14:40 // 版權(quán)所有: CheapMeowStudio // 描述: // ----------------------------------------------using System.Collections; using System.Collections.Generic; using MeowFramework.Core.Switchable; using Sirenix.OdinInspector; using UnityEngine;namespace MeowFramework.TPSCharacter {/// <summary>/// 第三人稱動(dòng)畫狀態(tài)機(jī)控制器/// </summary>public partial class TPSCharacterAnimationController{/// <summary>/// 行動(dòng)模式/// </summary>[BoxGroup("Mode")][ShowInInspector][Sirenix.OdinInspector.ReadOnly][Tooltip("行動(dòng)模式")]private TPSCharacterBehaviourMode mode;/// <summary>/// 行動(dòng)模式/// </summary>public TPSCharacterBehaviourMode Mode{get => mode;set{if (mode != value){if (switchValueCoroutine != null)StopCoroutine(switchValueCoroutine);switchValueCoroutine = StartCoroutine(ModeTransition(value));mode = value;}}}/// <summary>/// 切換模式的過渡時(shí)間/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("切換模式的過渡時(shí)間")]private float modeTransitionTime = 1f;/// <summary>/// 動(dòng)畫狀態(tài)機(jī)第 0 層的權(quán)重/// </summary>[BoxGroup("Mode")][Tooltip("動(dòng)畫狀態(tài)機(jī)第 0 層的權(quán)重")]public SwitchableFloat AnimLayer0Weight = new SwitchableFloat();/// <summary>/// 動(dòng)畫狀態(tài)機(jī)第 1 層的權(quán)重/// </summary>[BoxGroup("Mode")][Tooltip("動(dòng)畫狀態(tài)機(jī)第 1 層的權(quán)重")]public SwitchableFloat AnimLayer1Weight = new SwitchableFloat();/// <summary>/// 動(dòng)畫狀態(tài)機(jī)第 2 層的權(quán)重/// </summary>[BoxGroup("Mode")][Tooltip("動(dòng)畫狀態(tài)機(jī)第 2 層的權(quán)重")]public SwitchableFloat AnimLayer2Weight = new SwitchableFloat();/// <summary>/// 步槍待機(jī)姿態(tài)所用到的骨骼綁定的權(quán)重/// </summary>[BoxGroup("Mode")][Tooltip("步槍待機(jī)姿態(tài)所用到的骨骼綁定的權(quán)重")]public SwitchableFloat RifleIdleRigWeight = new SwitchableFloat();/// <summary>/// 步槍瞄準(zhǔn)姿態(tài)所用到的骨骼綁定的權(quán)重/// </summary>[BoxGroup("Mode")][Tooltip("步槍瞄準(zhǔn)姿態(tài)所用到的骨骼綁定的權(quán)重")]public SwitchableFloat RifleAimingRigWeight = new SwitchableFloat();/// <summary>/// 可切換變量列表/// </summary>private List<ISwitchable> switchableObjectList = new List<ISwitchable>();// 緩存 - 模式改變/// <summary>/// 切換變量的協(xié)程/// </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>/// 模式過渡:使變量在不同預(yù)設(shè)值之間切換/// </summary>/// <param name="mode">預(yù)設(shè)模式</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
已經(jīng)改造過了 AnimationController,別的就是照葫蘆畫瓢
舊版 LocomotionController
可以說得上是最需要 Switchable 的了
你看這一開始寫了多少變量和 Smooth 語句出來,都是冗余的
Assets/MeowFramework/TPSCharacter/Scripts/Controller/TPSCharacterLocomotionController.Mode.cs
// ---------------------------------------------- // 作者: 廉價(jià)喵 // 創(chuàng)建于: 12/04/2022 15:48 // 最后一次修改于: 20/04/2022 16:41 // 版權(quán)所有: CheapMeowStudio // 描述: // ----------------------------------------------using System.Collections; using System.ComponentModel; using Cinemachine; using Sirenix.OdinInspector; using UnityEngine;namespace MeowFramework.TPSCharacter {/// <summary>/// 第三人稱運(yùn)動(dòng)控制器/// </summary>public partial class TPSCharacterLocomotionController{// 模式/// <summary>/// 行動(dòng)模式/// </summary>[BoxGroup("Mode")][ShowInInspector][Sirenix.OdinInspector.ReadOnly][Tooltip("行動(dòng)模式")]private TPSCharacterBehaviourMode mode;/// <summary>/// 切換模式的過渡時(shí)間/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("切換模式的過渡時(shí)間")]private float modeTransitionTime = 1f;/// <summary>/// 沒有武器時(shí)角色移動(dòng)速度/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("沒有武器時(shí)角色移動(dòng)速度")]private float noWeaponWalkSpeed = 4f;/// <summary>/// 持步槍時(shí)角色移動(dòng)速度/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("持步槍時(shí)角色移動(dòng)速度")]private float rifleWalkSpeed = 2f;/// <summary>/// 沒有武器時(shí)攝像機(jī)的 FOV/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("沒有武器時(shí)攝像機(jī)的 FOV")]private float noWeaponFOV = 40f;/// <summary>/// 持步槍瞄準(zhǔn)時(shí)攝像機(jī)的 FOV/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("持步槍瞄準(zhǔn)時(shí)攝像機(jī)的 FOV")]private float rifleAimingFOV = 30f;/// <summary>/// 攝像機(jī)的 FOV 的平滑時(shí)間/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("攝像機(jī)的目標(biāo) FOV 的平滑時(shí)間")]private float fovSmoothTime = 0.2f;/// <summary>/// 沒有武器時(shí)攝像機(jī)的側(cè)向位置/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("沒有武器時(shí)攝像機(jī)的側(cè)向位置")]private float noWeaponSide = 0.5f;/// <summary>/// 持步槍瞄準(zhǔn)時(shí)攝像機(jī)的側(cè)向位置/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("持步槍瞄準(zhǔn)時(shí)攝像機(jī)的側(cè)向位置")]private float rifleAimingSide = 1f;/// <summary>/// 攝像機(jī)側(cè)向位置的平滑時(shí)間/// </summary>[BoxGroup("Mode")][ShowInInspector][Tooltip("攝像機(jī)側(cè)向位置的平滑時(shí)間")]private float cameraSideSmoothTime = 0.2f;// 緩存// 緩存 - 運(yùn)動(dòng)模式/// <summary>/// 模式改變協(xié)程/// </summary>private Coroutine modeChangeCoroutine;/// <summary>/// 攝像機(jī)的目標(biāo) FOV 平滑速度/// </summary>private float fovSmoothVelocity;/// <summary>/// 攝像機(jī)側(cè)向位置的平滑速度/// </summary>private float cameraSideSmoothVelocity;/// <summary>/// 切換攝像機(jī)配置的協(xié)程函數(shù)/// </summary>/// <param name="targetFOV">目標(biāo) FOV</param>/// <param name="targetSide">目標(biāo)側(cè)向位置</param>/// <returns></returns>private IEnumerator SwitchCameraSetting(float targetFOV, float targetSide){// 攝像機(jī)第三人稱跟隨組件var camera3rdPersonFollow =PlayerFollowCamera.GetCinemachineComponent<Cinemachine3rdPersonFollow>();// 初始化計(jì)時(shí)器var timeLeft = modeTransitionTime;// 在給定時(shí)間內(nèi)平滑// 平滑時(shí)間結(jié)束時(shí),被平滑項(xiàng)接近終點(diǎn)值但不是終點(diǎn)值// 因此最后需要給被平滑項(xiàng)賦終點(diǎn)值,這可能產(chǎn)生一個(gè)抖動(dòng)// 因此平滑時(shí)間需要在保證效果的同時(shí)盡可能小,才能讓最后的抖動(dòng)變小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;}// 攝像機(jī)焦距設(shè)置賦終點(diǎn)值PlayerFollowCamera.m_Lens.FieldOfView = targetFOV;// 攝像機(jī)側(cè)向位置賦終點(diǎn)值camera3rdPersonFollow.CameraSide = targetSide;yield return null;}/// <summary>/// 改變運(yùn)動(dòng)模式/// </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
// ---------------------------------------------- // 作者: 廉價(jià)喵 // 創(chuàng)建于: 12/04/2022 15:48 // 最后一次修改于: 22/04/2022 18:33 // 版權(quán)所有: 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>/// 第三人稱運(yùn)動(dòng)控制器/// </summary>public partial class TPSCharacterLocomotionController{// 模式/// <summary>/// 行動(dòng)模式/// </summary>[BoxGroup("Mode")][ShowInInspector][Sirenix.OdinInspector.ReadOnly][Tooltip("行動(dòng)模式")]private TPSCharacterBehaviourMode mode;/// <summary>/// 行動(dòng)模式/// </summary>public TPSCharacterBehaviourMode Mode{get => mode;set{if (mode != value){if (switchValueCoroutine != null)StopCoroutine(switchValueCoroutine);switchValueCoroutine = StartCoroutine(ModeTransition(value));mode = value;}}}/// <summary>/// 切換模式的過渡時(shí)間/// </summary>[BoxGroup("Mode")][Tooltip("切換模式的過渡時(shí)間")]public float ModeTransitionTime = 1f;/// <summary>/// 行走速度/// </summary>[BoxGroup("Mode")][Tooltip("行走速度")]public SwitchableFloat WalkSpeed = new SwitchableFloat();/// <summary>/// 攝像機(jī) FOV/// </summary>[BoxGroup("Mode")][Tooltip("攝像機(jī) FOV")]public SwitchableFloat CameraFOV = new SwitchableFloat();/// <summary>/// 攝像機(jī)側(cè)向位置/// </summary>[BoxGroup("Mode")][Tooltip("攝像機(jī)側(cè)向位置")]public SwitchableFloat CameraSide = new SwitchableFloat();// 緩存// 緩存 - 運(yùn)動(dòng)模式/// <summary>/// 可切換變量列表/// </summary>private List<ISwitchable> switchableObjectList = new List<ISwitchable>();// 緩存 - 模式改變/// <summary>/// 切換變量的協(xié)程/// </summary>private Coroutine switchValueCoroutine;/// <summary>/// 初始化可切換變量列表/// </summary>private void InitSwitchableList(){// 攝像機(jī)第三人稱跟隨組件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>/// 模式過渡:使變量在不同預(yù)設(shè)值之間切換/// </summary>/// <param name="mode">預(yù)設(shè)模式</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;}} }總結(jié)
以上是生活随笔為你收集整理的[Unity] 战斗系统学习 12:Switchable 1的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在线对弈web服务公司笔试题
- 下一篇: Dubbo异步调用实现