编写一个KeyCodeListener Playables
实现主要细节(使用到插件odin)
1.针对不同按键操作有不同应对方式:比如正常按下,长按,组合按键
2.将动画片段帧分为可输入帧范围和执行成功输入的可执行的帧范围
3.利用编辑器通过枚举控制编辑器属性显示隐藏。
[Serializable] public class KeyState { public KeyCode keyCode; [LabelText("切换的动画状态")] public AnimState animState; [HideInInspector] public bool canHandle; [LabelText("按键操作类型")] public KeyExecuteType keyExecuteType; [LabelText("以当前Clip为Frame范围为标准")] public Vector2Int acceptInputFrameRange; public Vector2Int executeOperationFrameRange; [LabelText("后续按键")] public KeyCode nextKeyCode; [LabelText("按下帧时长/在多少帧内按下连续按键")] public int downframe; } public enum KeyExecuteType { Normal, LongPress, ContinuousPress }
PlayableBehaviour 部分代码
通过info.frameId可以精确获得帧的序号,帧序号在TimeLine上是逐帧增加的,可以在OnBehaviourPlay中记录开始帧序号,当前clips运行的帧数=info.frameId-在OnBehaviourPlay中记录的
开始帧序号。
using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Playables; using UnityEngine.Timeline; using Sirenix.OdinInspector; [Serializable] public class InputKeyCodeListenerBehaviour : PlayableBehaviour { [LabelText("操作列表,优先级从前到后")] public List<KeyState> keyStates = new List<KeyState>(); [NonSerialized] public PlayerController playerController; bool isIn=false; ulong initframe=0; bool beginpress; int beginPressFrame; public override void OnPlayableCreate (Playable playable) { isIn = false; beginpress = false; } public override void OnBehaviourPlay(Playable playable, FrameData info) { isIn = true; initframe = info.frameId; beginPressFrame = 0; beginpress = false; for (int i = 0; i < keyStates.Count; i++) { keyStates[i].canHandle = false; } } public override void OnBehaviourPause(Playable playable, FrameData info) { isIn = false; } public override void ProcessFrame(Playable playable, FrameData info, object playerData) { if (!isIn) return; for (int i = 0; i < keyStates.Count; i++) { int currFrame = (int)(info.frameId - initframe); switch (keyStates[i].keyExecuteType) { case KeyExecuteType.Normal: if (currFrame >= keyStates[i].acceptInputFrameRange.x && currFrame <= keyStates[i].acceptInputFrameRange.y) { keyStates[i].canHandle |= Input.GetKeyDown(keyStates[i].keyCode); } break; case KeyExecuteType.LongPress: if (currFrame >= keyStates[i].acceptInputFrameRange.x && currFrame <= keyStates[i].acceptInputFrameRange.y) { if (Input.GetKey(keyStates[i].keyCode)&& !beginpress) { beginpress = true; beginPressFrame = currFrame; } if (beginpress) { if (Input.GetKeyUp(keyStates[i].keyCode)){ beginpress = false; beginPressFrame = 0; } if (currFrame- beginPressFrame>=keyStates[i].downframe) { keyStates[i].canHandle = true; } } } break; case KeyExecuteType.ContinuousPress: if (currFrame >= keyStates[i].acceptInputFrameRange.x && currFrame <= keyStates[i].acceptInputFrameRange.y) { if (currFrame - beginPressFrame < keyStates[i].downframe - 1 && currFrame > 0&& beginpress) { keyStates[i].canHandle |= Input.GetKeyDown(keyStates[i].nextKeyCode); break; } if (Input.GetKey(keyStates[i].keyCode) && !beginpress) { beginpress = true; beginPressFrame = currFrame; } } break; } if (currFrame > keyStates[i].executeOperationFrameRange.x && currFrame < keyStates[i].executeOperationFrameRange.y) { if (keyStates[i].canHandle) { playerController.GetSkillController.StopPlay(); playerController.GetSkillController.Play(keyStates[i].animState); } } } } }
对KeyState编辑的Editor类,通过按键不同操作显示相应的属性
using UnityEditor; using UnityEngine; using UnityEngine.Playables; [CustomPropertyDrawer(typeof(KeyState))] public class InputKeyCodeListenerEditor :PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { SerializedProperty keyExecuteType = property.FindPropertyRelative("keyExecuteType"); SerializedProperty nextKeyCode = property.FindPropertyRelative("nextKeyCode"); SerializedProperty downframe = property.FindPropertyRelative("downframe"); EditorGUILayout.PropertyField(property.FindPropertyRelative("keyCode")); EditorGUILayout.PropertyField(property.FindPropertyRelative("animState")); EditorGUILayout.PropertyField(property.FindPropertyRelative("acceptInputFrameRange")); EditorGUILayout.PropertyField(property.FindPropertyRelative("executeOperationFrameRange")); EditorGUILayout.PropertyField(keyExecuteType); switch (keyExecuteType.enumValueIndex) { case 1: EditorGUILayout.PropertyField(downframe); break; case 2: EditorGUILayout.PropertyField(nextKeyCode); break; } property.serializedObject.ApplyModifiedProperties(); } }
PlayableAsset代码
using System; using UnityEngine; using UnityEngine.Playables; using UnityEngine.Timeline; [Serializable] public class InputKeyCodeListenerClip : PlayableAsset, ITimelineClipAsset { public InputKeyCodeListenerBehaviour template = new InputKeyCodeListenerBehaviour (); public ExposedReference<PlayerController> playerController; public ClipCaps clipCaps { get { return ClipCaps.None; } } public override Playable CreatePlayable (PlayableGraph graph, GameObject owner) { var playable = ScriptPlayable<InputKeyCodeListenerBehaviour>.Create (graph, template); InputKeyCodeListenerBehaviour clone = playable.GetBehaviour (); clone.playerController = playerController.Resolve (graph.GetResolver ()); return playable; } }
PlayableBehaviourMixer,暂时不打算处理混合逻辑没用到,可以删掉
using System; using UnityEngine; using UnityEngine.Playables; using UnityEngine.Timeline; public class InputKeyCodeListenerMixerBehaviour : PlayableBehaviour { // NOTE: This function is called at runtime and edit time. Keep that in mind when setting the values of properties. public override void ProcessFrame(Playable playable, FrameData info, object playerData) { int inputCount = playable.GetInputCount (); for (int i = 0; i < inputCount; i++) { float inputWeight = playable.GetInputWeight(i); ScriptPlayable<InputKeyCodeListenerBehaviour> inputPlayable = (ScriptPlayable<InputKeyCodeListenerBehaviour>)playable.GetInput(i); InputKeyCodeListenerBehaviour input = inputPlayable.GetBehaviour (); // Use the above variables to process each frame of this playable. } } }
TrackAsset没有修改
using UnityEngine; using UnityEngine.Playables; using UnityEngine.Timeline; [TrackColor(0.855f, 0.8623f, 0.87f)] [TrackClipType(typeof(InputKeyCodeListenerClip))] public class InputKeyCodeListenerTrack : TrackAsset { public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount) { return ScriptPlayable<InputKeyCodeListenerMixerBehaviour>.Create (graph, inputCount); } }