你说得对,但是EmiteInnaACTSystemFramework是一款由EmiteInna自主研发,适用于PC平台的Unity第三人称上帝视角3D动作游戏代码框架。您将在游戏里扮演一个名叫游戏程序的角色,通过跳跃,冲刺和各种各样的丝滑小连招击败你的对手,修复程序中的bug,找回失散多年的代码能力,发掘未知的设计模式和模拟算法,揭开游戏行业无法入行、毕业即失业的真相。
本篇是ACT系列之一,github仓库位于:https://github.com/EmiteInna/EmiteInnaActSystem
简述
因为jkframe的原因,慕名而来看了一下ARPG的3,发现其实他的技能编辑器把我一直以来去做的某件事情明确了,现在我仔细想想这件事情,其实就是技能各个逻辑的分离。
这个也就是我一直在做的事情,为什么要这么做呢,是因为归根结底,在ACT中,技能这个东西,它可以总结为:“我释放某个技能→进入某个动画→在这个动画的某个时间我触发了某个效果”,而这句话中效果这个东西它当然是包含了音效和特效的,那么为什么我要把它和动作分开呢?因为动画是一个持续的过程,而效果是一个瞬间的过程(哪怕是协程类的持续性效果,它也是瞬间发生的),那么仔细想想瞬间的过程和持续的过程它的主要区别在哪呢?主要区别在于是否需要判断打断的逻辑,现在我们已经通过分层状态机把一些琐碎的判断去优化掉了,那么到底怎么样才能算是一个完整的技能,答案就只是加入了几句,变为了在如此的状态下-我释放某个技能-在技能的时长的某个时间中我播放某个动画-在技能时长的某个时间中我产生了什么效果-在技能时长的某个时间中我被打断了,这个打断使我进入了一个怎样的后续-在技能时长结束之后我进入下一个状态-或是释放下一个技能。
回到一开始,一切皆技能这个说法真的对吗?需要选择技能目标的技能难道不可以看成是本身进入了一个状态,然后选定完目标之后我进入下一个技能,这的确不需要制作一个新的类型来处理。但是走路呢?走路也可以是技能,因为没有人说在技能的过程中玩家就不能进行操作,走路完全可以是一个持续施法类型的技能。
那么技能需要的真的是可视化编辑吗?不,它其实不需要可视化编辑,它只是需要可视化而已。
那么我们能把走路也变成技能吗?可以,但没必要。
那么又到了最喜欢的类设环节(bushi
这个雏形当然后面可能有所更改,但是它满足了我对技能的理解之一:同类型业务的分离。
再次强调一遍,为什么我要这么单独地对待技能编辑器,还是在已经理解了“一切皆技能”这个感觉的前提下,为了效率,我希望能够以最快的速度去制作一个技能,让它能够很好地运行,并且我能够很好地去测试它。
在这个设计中,我把动画、逻辑、音效、特效(甚至以后还会有其它东西)分离开来,用结构体的方法进行管理,然后在Spell的Use方法中,我用一堆迭代器来推理它们,然后通过事件字符串的方法和Character的Controller类进行沟通,实现它们各自应有的逻辑,完成整个技能的生命周期,这看起来和单纯写个协程相比反而更加困难。
但是这些结构体都可以被可视化,用技能编辑器或者Gizmos的方式来debug,而且现成的组件越多,构建一个新的技能就越容易。
至少直到我后悔这么干之前是这样的,就像我后悔在gamejam上给一个单例写1500行代码来控制整个游戏的进程一样。
贴一个Spell的代码
代码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 160
| using System.Collections; using System.Collections.Generic; using UnityEngine;
namespace EmiteInnaACT { public class SpellBase { public float currentCD; public bool active; public Stack<Coroutine> stack = new Stack<Coroutine>(); public SpellConfigureBase config; public SpellBase(SpellConfigureBase config) { this.config = config; currentCD = 0; active = false; } public void SetActive(bool value) { active = value; } public virtual void OnSpellFixedUpdate() { if (currentCD > 0) currentCD -= Time.fixedDeltaTime; } public virtual void OnSpellUpdate() {
} public void EnterCooldown() { currentCD = config.SpellCoolDown; } public virtual void OnSpellUse(CharacterControllerBase ch) { if (!active) return; if (currentCD > 0) return; ClearCoroutineStack(); StartInterrupatbleCoroutine(ch,(DoSpellUse(ch))); } public virtual void OnSpellCancelWithCommandName(CharacterControllerBase ch,string commandName){ if (config.cancelEvents.TryGetValue(commandName, out string eventName)) { OnSpellCancel(ch, eventName); } } public virtual void OnSpellCancel(CharacterControllerBase ch,string cancelCommand = "Stun") { if (ConfigureInstance.GetValue<EmiteInnaBool>("uniform", "SpellDebug").Value) Debug.Log("打断技能" + config.SpellName); while (stack.Count > 0) { Coroutine co=stack.Peek(); ch.StopCoroutine(co); stack.Pop(); } ch.OnApplyCharacterEvent(cancelCommand); } public void ClearCoroutineStack() { while (stack.Count > 0) stack.Pop(); } public void StartInterrupatbleCoroutine(CharacterControllerBase ch, IEnumerator c) { Coroutine co= ch.StartCoroutine(c); stack.Push(co); } IEnumerator DoSpellUse(CharacterControllerBase ch) { float timer = 0; int idx_Animation = 0; int idx_Script = 0; int idx_Audio = 0; if (ConfigureInstance.GetValue<EmiteInnaBool>("uniform", "SpellDebug").Value) Debug.Log("使用技能"+config.SpellName); while (timer <= config.duration*config.timeMultiplier) { while (idx_Animation < config.animationEvent.Count) { if (timer >= config.animationEvent[idx_Animation].happenTime * config.timeMultiplier) { ch.PlayAnimation(config.animationEvent[idx_Animation].clip); idx_Animation++; } } while (idx_Script < config.scriptEvents.Count) { if (timer >= config.scriptEvents[idx_Script].happenTime * config.timeMultiplier) { ch.OnApplyCharacterEvent(config.scriptEvents[idx_Script].eventName); idx_Script++; } } while (idx_Audio < config.audioEvents.Count) { if (timer >= config.audioEvents[idx_Audio].happenTime * config.timeMultiplier) { ESoundInstance.PlaySFX(config.audioEvents[idx_Audio].clip, ch.transform.position + config.audioEvents[idx_Audio].offset, config.audioEvents[idx_Audio].clip.length, config.audioEvents[idx_Audio].playVolume, config.audioEvents[idx_Audio].playPitch/config.timeMultiplier); idx_Audio++; } } timer += Time.fixedDeltaTime; yield return new WaitForFixedUpdate(); } } } }
|
Copyright Notice: 此文章版权归EmiteInna所有,多看看吧。