你说得对,但是EmiteInnaACTSystemFramework是一款由EmiteInna自主研发,适用于PC平台的Unity第三人称上帝视角3D动作游戏代码框架。您将在游戏里扮演一个名叫游戏程序的角色,通过跳跃,冲刺和各种各样的丝滑小连招击败你的对手,修复程序中的bug,找回失散多年的代码能力,发掘未知的设计模式和模拟算法,揭开游戏行业无法入行、毕业即失业的真相。
本篇是ACT系列之一,github仓库位于:https://github.com/EmiteInna/EmiteInnaActSystem
简述 动画系统采用了Playable的方案,目前而言我似乎并没有把多个动画blend在一起的需求,所以用了一个简单的两个playableclip互相切的方案,同时做了个表来存clip的映射,一方面避免每次播放动画都要create结构体,一方面避免连续播放同一个动画的时候产生逻辑问题。
动画组件的代码
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 using System.Collections;using System.Collections.Generic;using Unity.VisualScripting;using UnityEngine;using UnityEngine.Animations;using UnityEngine.Playables;namespace EmiteInnaACT { public struct AnimationClipStruct { public AnimationClipPlayable cp0; public AnimationClipPlayable cp1; public int now; } [RequireComponent(typeof(Animator)) ] public class CharacterAnimationController :MonoBehaviour { public Dictionary<int ,AnimationClipStruct> clipDict = new Dictionary<int , AnimationClipStruct>(); public CharacterControllerBase controller; public Animator animator; PlayableGraph graph; AnimationClipPlayable clip1; AnimationClipPlayable clip2; AnimationMixerPlayable rootmixer; bool isFirstPlay = true ; float precentWeight; Coroutine swap; public void OnApplyCharacterEvent (string str ) { controller.OnApplyCharacterEvent(str); } public void Initialize (CharacterControllerBase controller ) { controller.animationController = this ; this .controller = controller; animator = GetComponent<Animator>(); graph = PlayableGraph.Create("Player" ); graph.SetTimeUpdateMode(DirectorUpdateMode.GameTime); rootmixer = AnimationMixerPlayable.Create(graph, 2 ); var playableOutput = AnimationPlayableOutput.Create(graph, "输出" , animator); playableOutput.SetSourcePlayable(rootmixer); } public void Destroy () { clipDict.Clear(); clipDict = null ; graph.Stop(); graph.Destroy(); } public AnimationClipPlayable GetClipPlayable (AnimationClip clip ) { if (clipDict.TryGetValue(clip.GetInstanceID(),out AnimationClipStruct c)) { if (c.now==0 ) { c.now = 1 ; clipDict[clip.GetInstanceID()] = c; return c.cp1; } c.now = 0 ; clipDict[clip.GetInstanceID()] = c; return c.cp0; } else { AnimationClipPlayable cp = AnimationClipPlayable.Create(graph, clip); AnimationClipPlayable cp1 = AnimationClipPlayable.Create(graph, clip); AnimationClipStruct s = new AnimationClipStruct(); s.cp0 = cp; s.cp1 = cp1; s.now = 0 ; clipDict.Add(clip.GetInstanceID(), s); return cp; } } public void PlayAnimation (AnimationClip clip,float transition=0f ,float speed=1f ) { if (isFirstPlay) { precentWeight = 1 ; clip2 = GetClipPlayable(clip); graph.Connect(clip2, 0 , rootmixer, 0 ); rootmixer.SetInputWeight(0 , 1f ); } else { clip1 = clip2; clip2 = GetClipPlayable(clip); clip2.SetTime(0 ); clip2.SetSpeed(speed); graph.Disconnect(rootmixer, 0 ); graph.Disconnect(rootmixer, 1 ); graph.Connect(clip1, 0 , rootmixer, 0 ); graph.Connect(clip2, 0 , rootmixer, 1 ); if (swap != null ) controller.StopCoroutine(swap); swap = controller.StartCoroutine(DoPlayAnimation(transition)); } isFirstPlay = false ; if (graph.IsPlaying() == false ) { graph.Play(); } } IEnumerator DoPlayAnimation (float transition ) { if (transition > 0 ) { float speed = Time.fixedDeltaTime / transition; precentWeight = 1 - precentWeight; while (precentWeight < 1 ) { precentWeight += speed; rootmixer.SetInputWeight(0 , 1 - precentWeight); rootmixer.SetInputWeight(1 , precentWeight); yield return new WaitForFixedUpdate () ; } } precentWeight = 1 ; rootmixer.SetInputWeight(0 , 0 ); rootmixer.SetInputWeight(1 , 1 ); } } }
众所周知现在的配置里其实放了很多资源文件,而这些资源文件其实应该是由AB包管理的,不过现在还没有做AB包而已。
回头想了想打算把AB包管理和不AB包管理分为两个版本分别出一个框架的想法,后来想想果然最后有余力还是全改成资源管理比较好。
留个行为树的坑,打算后期的时候再管(做完3C和技能
最终还是用了rigidbody的物理方案。
踩了两个坑,一个是y有值的时候clamp速度一定要先转换为平面速度。
第二个是动画自己切到自己的时候会出问题。
加了跳跃的状态,细说一下昨天结尾的时候说的状态机的不足吧:
我们会发现动作游戏里玩家的几乎每一个动作都需要一个状态,这是好的,这并不是什么要不得的东西,我们总是希望跳跃时在上升、下降、着陆的过程中都能有自己喜欢的定制表现,因此这些重复是必要的。但是状态之间的转移却会产生代码的不必要重复,举个例子,就拿加了跳跃后的状态机,它是这样的。
当然,我们也可以进一步封装,把连接本身也封装成一个状态,甚至搞点状态间的blend(还是算了吧),但是这个东西本身已经没有一开始看起来那么美好了。
这个时候我们就会想到分层状态机,很显然,Idle,walk和sprint这些东西到jumprising和jumplanding的转移是一样的,那么是否可以看作它们属于一个大节点,而这个大节点存在一个到jumprising和jumplanding的转移,实际上我最后也是这么做的。
如果这样写会发现越来越像行为树了不是吗?
当然,我还是很信任状态机的,状态机也有很多好处,他在维护角色操作这种实际上逻辑并没有那么复杂(相对其它怪物的AI逻辑)的东西上非常舒服,希望在加入技能系统之后它不会变得乱到无法辨识……
Copyright Notice: 此文章版权归EmiteInna所有,多看看吧。