├── _config.yml ├── README.md.meta ├── Editor.meta ├── Scripts.meta ├── AnimationPlayer.cs.meta ├── AnimationPlayerSimple.cs.meta ├── Editor ├── AnimationPlayerEditor.cs.meta ├── AnimationPlayerSimpleEditor.cs.meta ├── AnimationPlayerSimpleEditor.cs └── AnimationPlayerEditor.cs ├── Scripts ├── AnimationPlayerBase.cs.meta ├── AnimationPlayer_State.cs.meta ├── AnimationPlayerBase_Internal.cs.meta ├── AnimationPlayerBase_Playable.cs.meta ├── AnimationPlayer_Internal.cs.meta ├── AnimationPlayerBase.cs ├── AnimationPlayerBase_Playable.cs ├── AnimationPlayerBase_Internal.cs ├── AnimationPlayer_Internal.cs └── AnimationPlayer_State.cs ├── AnimationPlayerSimple.cs ├── LICENSE ├── README.md └── AnimationPlayer.cs /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e94b553c271e5c74d82e68be9c6647f7 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7efc3f80465412148977f2033a324338 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4696735d6b23e1940a3784558498cea8 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /AnimationPlayer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9c2e1a8faf5f9e0458befabd485f93be 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /AnimationPlayerSimple.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0709d5ebf1b472f4484a3a4360443e85 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/AnimationPlayerEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2fbd8f6f80fb3d3459c484f665964a66 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/AnimationPlayerBase.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 200367f5adecd524e8f5b1f9e9c37792 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/AnimationPlayer_State.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 972f9c05a718063418bb42c3ba2dfcac 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/AnimationPlayerSimpleEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6b2ccf15381ae714085a5cbedccca618 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/AnimationPlayerBase_Internal.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c50bf3b20106627429bdfee75456ba19 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/AnimationPlayerBase_Playable.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6fb50f7eb0d92a048b06f81c0ddab5cb 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/AnimationPlayer_Internal.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9eb1469c8cadc2a44866d283098f38b1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /AnimationPlayerSimple.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.Animations; 5 | using UnityEngine.Playables; 6 | 7 | public class AnimationPlayerSimple : AnimationPlayerBase 8 | { 9 | [SerializeField] 10 | AnimationClip animClip; 11 | AnimationClipPlayable clipPlayable; 12 | 13 | public override void Init() 14 | { 15 | if (_initialized || animClip == null) 16 | return; 17 | 18 | base.Init(); 19 | 20 | clipPlayable = AnimationClipPlayable.Create(_playable.Graph, animClip); 21 | 22 | _playable.ConnectInput(0, clipPlayable); 23 | _playable.SetInputWeight(0, 1.0f); 24 | 25 | _initialized = true; 26 | } 27 | 28 | void Update() 29 | { 30 | if (!_initialized || !_graph.IsValid()) 31 | return; 32 | 33 | if (DeactivateOnEnd) { 34 | if (clipPlayable.GetTime() > animClip.length) { 35 | gameObject.SetActive(false); 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 8izips 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Scripts/AnimationPlayerBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.Playables; 5 | 6 | [RequireComponent(typeof(Animator))] 7 | public abstract partial class AnimationPlayerBase : MonoBehaviour 8 | { 9 | public bool PlayOnAwake = true; 10 | public bool DeactivateOnEnd = false; 11 | 12 | public bool IsPlaying() 13 | { 14 | if (!_graph.IsValid()) 15 | return false; 16 | return _graph.IsPlaying(); 17 | } 18 | 19 | bool _graphConnected = false; 20 | public void Play() 21 | { 22 | if (!_graphConnected) { 23 | AnimationPlayableUtilities.Play(Animator, _playable.Playable, _playable.Graph); 24 | _graphConnected = true; 25 | } 26 | else { 27 | _graph.Play(); 28 | } 29 | } 30 | 31 | public void Stop() 32 | { 33 | if (_graph.IsValid()) 34 | _graph.Stop(); 35 | } 36 | 37 | public void Pause() 38 | { 39 | if (_graph.IsPlaying()) 40 | _graph.Stop(); 41 | } 42 | 43 | public void Resume() 44 | { 45 | if (!_graph.IsPlaying()) 46 | _graph.Play(); 47 | } 48 | 49 | public void Evaluate() 50 | { 51 | if (_graph.IsValid()) 52 | _graph.Evaluate(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Scripts/AnimationPlayerBase_Playable.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.Playables; 5 | using UnityEngine.Animations; 6 | 7 | public abstract partial class AnimationPlayerBase : MonoBehaviour 8 | { 9 | public class AnimationPlayable : PlayableBehaviour 10 | { 11 | public Playable Playable { get; private set; } 12 | public PlayableGraph Graph { get { return Playable.GetGraph(); } } 13 | public AnimationMixerPlayable Mixer { get { return _mixer; } } 14 | AnimationMixerPlayable _mixer; 15 | 16 | public override void OnPlayableCreate(Playable playable) 17 | { 18 | Playable = playable; 19 | _mixer = AnimationMixerPlayable.Create(Graph, 1, true); 20 | 21 | Playable.SetInputCount(1); 22 | Playable.SetInputWeight(0, 1.0f); 23 | Graph.Connect(_mixer, 0, Playable, 0); 24 | } 25 | 26 | public void SetInputCount(int count) 27 | { 28 | _mixer.SetInputCount(count); 29 | } 30 | 31 | public Playable GetInput(int index) 32 | { 33 | if (index >= _mixer.GetInputCount()) 34 | return Playable.Null; 35 | 36 | return _mixer.GetInput(index); 37 | } 38 | 39 | public void ConnectInput(int index, Playable playable) 40 | { 41 | Graph.Connect(playable, 0, _mixer, index); 42 | } 43 | 44 | public void DisconnectInput(int index) 45 | { 46 | Graph.Disconnect(_mixer, index); 47 | } 48 | 49 | public void DisconnectInputs() 50 | { 51 | for (int i = 0; i < _mixer.GetInputCount(); i++) 52 | DisconnectInput(i); 53 | } 54 | 55 | public void SetInputWeight(int index, float weight) 56 | { 57 | _mixer.SetInputWeight(index, weight); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AnimationPlayer 2 | Simple Animation Player for Unity 3 | 4 | ![AnimationPlayer](https://docs.google.com/uc?id=1l45FF-6tX077jELec0Yz1RluG1u0PgxI) 5 | 6 | ## About AnimationPlayer 7 | 8 | > AnimationPlayerは[SimpleAnimation](https://github.com/Unity-Technologies/SimpleAnimation)がアップデートされなくなったため 9 | 作った簡単なアニメーションプレイヤーです。 10 | 基本的にSimpleAnimationをベースにしましたが、設計から作り直したものです。 11 | SimpleAnimationはPlayableクラスの中にStateが存在して、外からの変更をそのStateに同期させる構造ですが、 12 | それはスクリプト上で内部のプレイ状態を得るたびにGCが発生するので、Stateを共通にした仕組みに変わりました。 13 | AnimationPlayer is a simple animation player, I made this since Simpleanimation is no longer updating. 14 | AnimationPlayer is based on [SimpleAnimation](https://github.com/Unity-Technologies/SimpleAnimation), but I rebuild from the design. 15 | 16 | ## Installing AnimationPlayer 17 | 18 | > Pathに依存しないのでどこでもいいですが、個人的にはこのパスを使います。 19 | It doesn`t depend on path, so anywhere is fine, but I use next path personally 20 | 21 | Assets/Plugins/AnimationPlayer 22 | 23 | ## Using AnimationPlayer 24 | 25 | > Animatorが既に設定されている場合はAnimatorのControllerをNoneに設定します。 26 | 持っていない場合はAnimationPlayerコンポーネントを追加したら自動で追加されます。 27 | プレイヤーは2種類あります。 28 | if Animator is added already, Set none for controller, 29 | otherwise adding this component will automatically add Animator component. 30 | There are 2 available components. 31 | 32 | * AnimationPlayerSimple 33 | 34 | 単一アニメーションのみ再生する場合使います。 35 | State管理しない分ちょっとだけ軽くなります。 36 | Use this component for single animation clip. 37 | 38 | * AnimationPlayer 39 | 40 | 一つ以上のアニメーションを設定するかプレイ中追加するならこのコンポーネントを使います。 41 | ランタイム中のPlay、CrossFade、Blendなどの制御が可能で各アニメーションに 42 | 終了コールバックを設定することもできます。 43 | Use this component for more than one animation clip. 44 | Can controll Play, CrossFade, Blend, registering end callback by script on runtime. 45 | 46 | ** PlayableAPIを使って実装したのでUnity 2017.1以上で動作します。 47 | [PlayableGraph Visualizer](https://github.com/Unity-Technologies/graph-visualizer)も一緒に使うとデバッグが楽になります。 48 | -------------------------------------------------------------------------------- /Scripts/AnimationPlayerBase_Internal.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.Playables; 5 | 6 | [RequireComponent(typeof(Animator))] 7 | public abstract partial class AnimationPlayerBase : MonoBehaviour 8 | { 9 | #region Animator Property 10 | public Animator Animator { 11 | get { 12 | if (_animator == null) 13 | _animator = GetComponent(); 14 | return _animator; 15 | } 16 | } 17 | 18 | public Avatar Avatar { 19 | get { return Animator.avatar; } 20 | set { Animator.avatar = value; } 21 | } 22 | public bool ApplyRootMotion { 23 | get { return Animator.applyRootMotion; } 24 | set { Animator.applyRootMotion = value; } 25 | } 26 | 27 | public AnimatorUpdateMode UpdateMode { 28 | get { return Animator.updateMode; } 29 | set { Animator.updateMode = value; } 30 | } 31 | 32 | public AnimatorCullingMode CullingMode { 33 | get { return Animator.cullingMode; } 34 | set { Animator.cullingMode = value; } 35 | } 36 | Animator _animator; 37 | #endregion 38 | 39 | protected PlayableGraph _graph; 40 | protected AnimationPlayable _playable; 41 | 42 | void Awake() 43 | { 44 | Init(); 45 | } 46 | 47 | void OnEnable() 48 | { 49 | Init(); 50 | 51 | if (PlayOnAwake) 52 | Play(); 53 | } 54 | 55 | void OnDisable() 56 | { 57 | if (_initialized) { 58 | Stop(); 59 | //_graph.Stop(); 60 | } 61 | } 62 | 63 | void OnDestroy() 64 | { 65 | if (_graph.IsValid()) 66 | _graph.Destroy(); 67 | } 68 | 69 | protected bool _initialized = false; 70 | public virtual void Init() 71 | { 72 | if (_initialized) 73 | return; 74 | 75 | _animator = GetComponent(); 76 | _graph = PlayableGraph.Create(); 77 | _graph.SetTimeUpdateMode(DirectorUpdateMode.GameTime); 78 | _graphConnected = false; 79 | 80 | AnimationPlayable template = new AnimationPlayable(); 81 | var playable = ScriptPlayable.Create(_graph, template, 1); 82 | _playable = playable.GetBehaviour(); 83 | 84 | _initialized = true; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Scripts/AnimationPlayer_Internal.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.Playables; 5 | 6 | public partial class AnimationPlayer : AnimationPlayerBase 7 | { 8 | public override void Init() 9 | { 10 | if (_initialized) 11 | return; 12 | 13 | base.Init(); 14 | _InitStates(); 15 | 16 | _initialized = true; 17 | } 18 | 19 | void OnValidate() 20 | { 21 | if (Application.isPlaying) 22 | return; 23 | 24 | if (_states == null || _states.Length == 0) { 25 | _states = new AnimationState[1]; 26 | } 27 | if (_states[0] == null) { 28 | _states[0] = new AnimationState() { name = "Default" }; 29 | } 30 | } 31 | 32 | void Update() 33 | { 34 | if (!_graph.IsValid()) 35 | return; 36 | 37 | float elapsedTime = Time.deltaTime; 38 | 39 | for (int i = 0; i < _states.Length; i++) { 40 | AnimationState state = _states[i]; 41 | 42 | if (state == null) 43 | continue; 44 | 45 | if (state.fading) { 46 | float weight = Mathf.MoveTowards(state.weight, state.targetWeight, state.fadeSpeed * elapsedTime); 47 | state.SetWeight(weight); 48 | if (state.weight == state.targetWeight) { 49 | state.ResetFade(); 50 | 51 | if (state.weight == 0.0f) { 52 | state.SetEnable(false); 53 | state.SetStateTime(0.0f); 54 | } 55 | } 56 | } 57 | 58 | if (state.enableDirty) { 59 | if (state.enable) 60 | state.clipPlayable.Play(); 61 | else 62 | state.clipPlayable.Pause(); 63 | state.enableDirty = false; 64 | } 65 | 66 | if (state.weightDirty) { 67 | _playable.SetInputWeight(i, state.weight); 68 | state.weightDirty = false; 69 | } 70 | 71 | if (state.speedDirty) { 72 | state.clipPlayable.SetSpeed(state.speed); 73 | state.speedDirty = false; 74 | } 75 | 76 | if (state.enable) { 77 | state.time = (float)state.clipPlayable.GetTime(); 78 | 79 | if (state.time >= state.duration && !state.isLooping) { 80 | if (DeactivateOnEnd) 81 | gameObject.SetActive(false); 82 | 83 | state.endCallback?.Invoke(); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Editor/AnimationPlayerSimpleEditor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEditor; 5 | using UnityEditorInternal; 6 | 7 | [CustomEditor(typeof(AnimationPlayerSimple))] 8 | public class AnimationPlayerSimpleEditor : Editor 9 | { 10 | static class Styles 11 | { 12 | } 13 | 14 | AnimationPlayerSimple _instance; 15 | Animator animator; 16 | SerializedProperty animClip; 17 | void OnEnable() 18 | { 19 | _instance = (AnimationPlayerSimple)target; 20 | _instance.Init(); 21 | 22 | animator = _instance.Animator; 23 | animClip = serializedObject.FindProperty("animClip"); 24 | } 25 | 26 | public override void OnInspectorGUI() 27 | { 28 | serializedObject.Update(); 29 | 30 | // Animator Property 31 | animator = (Animator)EditorGUILayout.ObjectField("Animator", animator, typeof(Animator), true); 32 | EditorGUILayout.BeginVertical("Box"); 33 | _instance.Avatar = (Avatar)EditorGUILayout.ObjectField("Avatar", animator.avatar, typeof(Avatar), true); 34 | _instance.ApplyRootMotion = EditorGUILayout.Toggle("Apply Root Motion", _instance.ApplyRootMotion); 35 | _instance.UpdateMode = (AnimatorUpdateMode)EditorGUILayout.EnumPopup("Update Mode", _instance.UpdateMode); 36 | _instance.CullingMode = (AnimatorCullingMode)EditorGUILayout.EnumPopup("Culling Mode", _instance.CullingMode); 37 | EditorGUILayout.EndVertical(); 38 | 39 | // State Property 40 | EditorGUILayout.BeginVertical("Box"); 41 | 42 | bool isDirty = false; 43 | var playOnAwake = EditorGUILayout.Toggle("Play On Awake", _instance.PlayOnAwake); 44 | if (playOnAwake != _instance.PlayOnAwake) { 45 | _instance.PlayOnAwake = playOnAwake; 46 | isDirty = true; 47 | } 48 | var deactivateOnEnd = EditorGUILayout.Toggle("Deactivate On End", _instance.DeactivateOnEnd); 49 | if (deactivateOnEnd != _instance.DeactivateOnEnd) { 50 | _instance.DeactivateOnEnd = deactivateOnEnd; 51 | isDirty = true; 52 | } 53 | if (isDirty) 54 | EditorUtility.SetDirty(_instance); 55 | 56 | if (!Application.isPlaying) { 57 | ClipOnEditor(); 58 | } 59 | else { 60 | ClipOnPlay(); 61 | } 62 | EditorGUILayout.EndVertical(); 63 | 64 | // Controller 65 | EditorGUILayout.BeginVertical("Box"); 66 | EditorGUILayout.BeginHorizontal(); 67 | if (GUILayout.Button("Play")) { 68 | _instance.Play(); 69 | } 70 | if (GUILayout.Button("Stop")) { 71 | _instance.Stop(); 72 | } 73 | EditorGUILayout.EndHorizontal(); 74 | EditorGUILayout.EndVertical(); 75 | 76 | serializedObject.ApplyModifiedProperties(); 77 | } 78 | 79 | void ClipOnEditor() 80 | { 81 | EditorGUILayout.LabelField("Editor Mode", EditorStyles.boldLabel); 82 | EditorGUI.BeginChangeCheck(); 83 | 84 | EditorGUILayout.PropertyField(animClip); 85 | 86 | if (EditorGUI.EndChangeCheck()) { 87 | serializedObject.ApplyModifiedProperties(); 88 | } 89 | } 90 | 91 | void ClipOnPlay() 92 | { 93 | EditorGUILayout.LabelField("Play Mode", EditorStyles.boldLabel); 94 | 95 | EditorGUILayout.PropertyField(animClip); 96 | } 97 | } -------------------------------------------------------------------------------- /AnimationPlayer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.Playables; 5 | 6 | public partial class AnimationPlayer : AnimationPlayerBase 7 | { 8 | public AnimationState GetState(string stateName) 9 | { 10 | return _states[_GetStateIndex(stateName)]; 11 | } 12 | 13 | public AnimationState GetState(int index) 14 | { 15 | if (!IsStateIndexValid(index)) 16 | return null; 17 | return _states[index]; 18 | } 19 | 20 | public int AddState(string stateName, AnimationClip animClip) 21 | { 22 | return _AddState(stateName, animClip); 23 | } 24 | 25 | public void RemoveState(string stateName) 26 | { 27 | _RemoveState(_GetStateIndex(stateName)); 28 | } 29 | 30 | public void RemoveState(int index) 31 | { 32 | _RemoveState(index); 33 | } 34 | 35 | public void Play(string stateName) 36 | { 37 | Play(_GetStateIndex(stateName)); 38 | } 39 | 40 | public void Play(int index) 41 | { 42 | if (!IsStateIndexValid(index)) 43 | return; 44 | if (!_graph.IsPlaying()) 45 | Play(); 46 | 47 | CurStateIndex = index; 48 | for (int i = 0; i < _states.Length; i++) { 49 | AnimationState state = _states[i]; 50 | if (state == null) 51 | continue; 52 | 53 | state.SetEnable(i == index ? true : false); 54 | state.SetWeight(i == index ? 1.0f : 0.0f); 55 | state.ResetFade(); 56 | } 57 | } 58 | 59 | public void CrossFade(string stateName, float fadeTime) 60 | { 61 | CrossFade(_GetStateIndex(stateName), fadeTime); 62 | } 63 | 64 | public void CrossFade(int index, float fadeTime) 65 | { 66 | if (!IsStateIndexValid(index)) 67 | return; 68 | if (!_graph.IsPlaying()) 69 | Play(); 70 | 71 | CurStateIndex = index; 72 | _states[index].SetEnable(true); 73 | 74 | for (int i = 0; i < _states.Length; i++) { 75 | AnimationState state = _states[i]; 76 | if (state == null) 77 | continue; 78 | if (state.enable == false) 79 | continue; 80 | 81 | float targetWeight = i == index ? 1.0f : 0.0f; 82 | state.SetFade(targetWeight, fadeTime); 83 | } 84 | } 85 | 86 | public void Blend(string stateName, float targetWeight, float fadeTime) 87 | { 88 | Blend(_GetStateIndex(stateName), targetWeight, fadeTime); 89 | } 90 | 91 | public void Blend(int index, float targetWeight, float fadeTime) 92 | { 93 | if (!IsStateIndexValid(index)) 94 | return; 95 | 96 | AnimationState state = _states[index]; 97 | 98 | if (!state.enable) 99 | state.SetEnable(true); 100 | 101 | state.SetFade(targetWeight, fadeTime); 102 | } 103 | 104 | public void Stop(string stateName) 105 | { 106 | Stop(_GetStateIndex(stateName)); 107 | } 108 | 109 | public void Stop(int index) 110 | { 111 | if (!IsStateIndexValid(index)) 112 | return; 113 | 114 | AnimationState state = _states[index]; 115 | state.SetEnable(false); 116 | state.SetWeight(0.0f); 117 | state.ResetFade(); 118 | } 119 | 120 | public void SetTime(float time) 121 | { 122 | for (int i = 0; i < _states.Length; i++) 123 | _states[i]?.SetStateTime(time); 124 | } 125 | 126 | public void Rewind() 127 | { 128 | for (int i = 0; i < _states.Length; i++) 129 | _states[i]?.SetStateTime(0.0f); 130 | } 131 | 132 | public void Rewind(string stateName) 133 | { 134 | Rewind(_GetStateIndex(stateName)); 135 | } 136 | 137 | public void Rewind(int stateIndex) 138 | { 139 | if (IsStateIndexValid(stateIndex)) 140 | return; 141 | 142 | _states[stateIndex].SetStateTime(0.0f); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /Scripts/AnimationPlayer_State.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.Playables; 5 | using UnityEngine.Animations; 6 | 7 | public partial class AnimationPlayer : AnimationPlayerBase 8 | { 9 | [System.Serializable] 10 | public class AnimationState 11 | { 12 | public string name; 13 | public AnimationClip clip; 14 | public float time = 0.0f; 15 | public float duration = 0.0f; 16 | public float normalizedTime { 17 | get { 18 | if (duration != 0.0f) 19 | return time / duration; 20 | return 0.0f; 21 | } 22 | } 23 | 24 | public void SetStateTime(float stateTime) 25 | { 26 | time = stateTime; 27 | clipPlayable.SetTime(stateTime); 28 | } 29 | 30 | public AnimationClipPlayable clipPlayable { get; private set; } 31 | public void Init(PlayableGraph graph, bool enable, float weight) 32 | { 33 | if (clip == null) 34 | return; 35 | 36 | if (graph.IsValid()) { 37 | clipPlayable = AnimationClipPlayable.Create(graph, clip); 38 | if (!clip.isLooping) 39 | clipPlayable.SetDuration(clip.length); 40 | clipPlayable.SetApplyFootIK(applyFootIK); 41 | clipPlayable.SetApplyPlayableIK(applyPlayableIK); 42 | } 43 | duration = clip.length; 44 | 45 | this.enable = enable; 46 | this.enableDirty = false; 47 | this.weight = weight; 48 | this.weightDirty = false; 49 | this.fading = false; 50 | this.fadeSpeed = 0.0f; 51 | this.isLooping = clip.isLooping; 52 | } 53 | 54 | public bool enable { get; private set; } = false; 55 | public bool enableDirty { get; set; } = false; 56 | public void SetEnable(bool enable, bool enableDirty = true) 57 | { 58 | this.enable = enable; 59 | this.enableDirty = enableDirty; 60 | } 61 | 62 | public float weight { get; private set; } = 0.0f; 63 | public bool weightDirty { get; set; } = false; 64 | public void SetWeight(float weight, bool weightDirty = true) 65 | { 66 | this.weight = weight; 67 | this.weightDirty = weightDirty; 68 | } 69 | 70 | public bool fading { get; private set; } = false; 71 | public float fadeSpeed { get; private set; } = 0.0f; 72 | public float targetWeight { get; private set; } = 0.0f; 73 | public void ResetFade() 74 | { 75 | this.fading = false; 76 | this.fadeSpeed = 0.0f; 77 | } 78 | public void SetFade(float targetWeight, float fadeTime) 79 | { 80 | float diff = Mathf.Abs(this.weight - targetWeight); 81 | this.fading = diff > 0f; 82 | if (fadeTime == 0.0f) { 83 | this.fading = false; 84 | this.weight = targetWeight; 85 | } 86 | else { 87 | this.fadeSpeed = diff / fadeTime; 88 | } 89 | 90 | this.targetWeight = targetWeight; 91 | } 92 | 93 | [SerializeField] 94 | float _speed = 1.0f; 95 | public float speed { 96 | get { return _speed; } 97 | set { 98 | _speed = value; 99 | speedDirty = true; 100 | } 101 | } 102 | 103 | public bool speedDirty = false; 104 | public bool applyFootIK = false; 105 | public bool applyPlayableIK = false; 106 | public bool isLooping { get; private set; } = false; 107 | 108 | public System.Action endCallback { get; private set; } 109 | public void SetEndCallback(System.Action callback) 110 | { 111 | endCallback = callback; 112 | } 113 | 114 | public void ClearEndCallback() 115 | { 116 | endCallback = null; 117 | } 118 | } 119 | 120 | [SerializeField] 121 | AnimationState[] _states = new AnimationState[1]; 122 | public AnimationState[] States { get { return _states; } set { _states = value; } } 123 | public int StateCount { get; private set; } 124 | public int CurStateIndex { get; private set; } = 0; 125 | public AnimationState CurState { get { return _states[CurStateIndex]; } } 126 | 127 | bool IsStateIndexValid(int index) 128 | { 129 | return (index >= 0 && index < _states.Length); 130 | } 131 | 132 | int _GetStateIndex(string stateName) 133 | { 134 | for (int i = 0; i < _states.Length; i++) { 135 | if (_states[i].name == stateName) 136 | return i; 137 | } 138 | 139 | return -1; 140 | } 141 | 142 | void _InitStates() 143 | { 144 | if (_states == null) 145 | return; 146 | 147 | _playable.SetInputCount(_states.Length); 148 | for (int i = 0; i < _states.Length; i++) { 149 | AnimationState state = _states[i]; 150 | if (state == null) 151 | continue; 152 | 153 | state.Init(_graph, i == 0 ? true : false, i == 0 ? 1.0f : 0.0f); 154 | 155 | _playable.ConnectInput(i, state.clipPlayable); 156 | _playable.SetInputWeight(i, state.weight); 157 | } 158 | } 159 | 160 | AnimationState _GetState(int index) 161 | { 162 | if (!IsStateIndexValid(index)) 163 | return null; 164 | 165 | return _states[index]; 166 | } 167 | 168 | int _AddState(string stateName, AnimationClip animClip, int index = -1) 169 | { 170 | AnimationState newState = new AnimationState(); 171 | newState.name = stateName; 172 | newState.clip = animClip; 173 | 174 | return _AddState(newState, index); 175 | } 176 | 177 | int _AddState(AnimationState newState, int index = -1) 178 | { 179 | if (!_graph.IsValid()) 180 | return -1; 181 | 182 | newState.Init(_graph, false, 0.0f); 183 | newState.clipPlayable.Pause(); 184 | 185 | _playable.DisconnectInputs(); 186 | _playable.SetInputCount(_states.Length + 1); 187 | 188 | AnimationState[] newStates = new AnimationState[_states.Length + 1]; 189 | // append to last 190 | if (index == -1) { 191 | index = _states.Length; 192 | } 193 | 194 | newStates[index] = newState; 195 | for (int i = 0; i < newStates.Length; i++) { 196 | if (i < index) 197 | newStates[i] = _states[i]; 198 | else if (i > index) 199 | newStates[i + 1] = _states[i]; 200 | 201 | _playable.ConnectInput(i, newStates[i].clipPlayable); 202 | _playable.SetInputWeight(i, newStates[i].weight); 203 | } 204 | 205 | _states = newStates; 206 | 207 | return index; 208 | } 209 | 210 | void _RemoveState(int index) 211 | { 212 | if (!IsStateIndexValid(index)) 213 | return; 214 | 215 | _playable.DisconnectInput(index); 216 | 217 | AnimationState removedState = _states[index]; 218 | _states[index] = null; 219 | 220 | _graph.DestroyPlayable(removedState.clipPlayable); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /Editor/AnimationPlayerEditor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEditor; 5 | using UnityEditorInternal; 6 | 7 | [CustomEditor(typeof(AnimationPlayer))] 8 | public class AnimationPlayerEditor : Editor 9 | { 10 | AnimationPlayer _instance; 11 | Animator animator; 12 | SerializedProperty stateProperty; 13 | ReorderableList states; 14 | void OnEnable() 15 | { 16 | _instance = (AnimationPlayer)target; 17 | _instance.Init(); 18 | 19 | animator = _instance.Animator; 20 | stateProperty = serializedObject.FindProperty("_states"); 21 | states = new ReorderableList(serializedObject, stateProperty); 22 | states.drawHeaderCallback = (rect) => 23 | { 24 | EditorGUI.LabelField(rect, "Animation State"); 25 | }; 26 | states.drawElementCallback = (rect, index, isActive, isFocused) => 27 | { 28 | var element = stateProperty.GetArrayElementAtIndex(index); 29 | float rectWidth = rect.width * 0.5f; 30 | rect.width = rectWidth - 2; 31 | rect.height -= 4; 32 | rect.y += 1; 33 | 34 | _instance.States[index].name = EditorGUI.TextField(rect, _instance.States[index].name); 35 | 36 | rect.x += rectWidth; 37 | var curClip = (AnimationClip)EditorGUI.ObjectField(rect, _instance.States[index].clip, typeof(AnimationClip), true); 38 | if (curClip != _instance.States[index].clip) 39 | EditorUtility.SetDirty(_instance); 40 | 41 | _instance.States[index].clip = curClip; 42 | }; 43 | } 44 | 45 | public override void OnInspectorGUI() 46 | { 47 | serializedObject.Update(); 48 | 49 | // Animator Property 50 | animator = (Animator)EditorGUILayout.ObjectField("Animator", animator, typeof(Animator), true); 51 | EditorGUILayout.BeginVertical("Box"); 52 | _instance.Avatar = (Avatar)EditorGUILayout.ObjectField("Avatar", animator.avatar, typeof(Avatar), true); 53 | _instance.ApplyRootMotion = EditorGUILayout.Toggle("Apply Root Motion", _instance.ApplyRootMotion); 54 | _instance.UpdateMode = (AnimatorUpdateMode)EditorGUILayout.EnumPopup("Update Mode", _instance.UpdateMode); 55 | _instance.CullingMode = (AnimatorCullingMode)EditorGUILayout.EnumPopup("Culling Mode", _instance.CullingMode); 56 | EditorGUILayout.EndVertical(); 57 | 58 | // State Property 59 | EditorGUILayout.BeginVertical("Box"); 60 | 61 | bool isDirty = false; 62 | var playOnAwake = EditorGUILayout.Toggle("Play On Awake", _instance.PlayOnAwake); 63 | if (playOnAwake != _instance.PlayOnAwake) { 64 | _instance.PlayOnAwake = playOnAwake; 65 | isDirty = true; 66 | } 67 | var deactivateOnEnd = EditorGUILayout.Toggle("Deactivate On End", _instance.DeactivateOnEnd); 68 | if (deactivateOnEnd != _instance.DeactivateOnEnd) { 69 | _instance.DeactivateOnEnd = deactivateOnEnd; 70 | isDirty = true; 71 | } 72 | if (isDirty) 73 | EditorUtility.SetDirty(_instance); 74 | 75 | if (!Application.isPlaying) { 76 | StateOnEditor(); 77 | } 78 | else { 79 | StateOnPlay(); 80 | } 81 | EditorGUILayout.EndVertical(); 82 | serializedObject.ApplyModifiedProperties(); 83 | } 84 | 85 | void StateOnEditor() 86 | { 87 | EditorGUILayout.LabelField("Editor Mode", EditorStyles.boldLabel); 88 | 89 | states.DoLayoutList(); 90 | StateDetailOnEditor(); 91 | } 92 | 93 | void StateDetailOnEditor() 94 | { 95 | if (states.index == -1 || states.index >= _instance.States.Length) 96 | return; 97 | 98 | AnimationPlayer.AnimationState state = _instance.States[states.index]; 99 | if (state == null) 100 | return; 101 | 102 | EditorGUILayout.BeginVertical("Box"); 103 | EditorGUILayout.LabelField(state.name, EditorStyles.boldLabel); 104 | EditorGUI.indentLevel++; 105 | 106 | bool isDirty = false; 107 | var speed = EditorGUILayout.DelayedFloatField("Speed", state.speed); 108 | if (speed != state.speed) { 109 | state.speed = speed; 110 | isDirty = true; 111 | } 112 | 113 | var applyFootIK = EditorGUILayout.Toggle("Apply Foot IK", state.applyFootIK); 114 | if (applyFootIK != state.applyFootIK) { 115 | state.applyFootIK = applyFootIK; 116 | isDirty = true; 117 | } 118 | 119 | var applyPlayableIK = EditorGUILayout.Toggle("Apply Playable IK", state.applyPlayableIK); 120 | if (applyPlayableIK != state.applyPlayableIK) { 121 | state.applyPlayableIK = applyPlayableIK; 122 | isDirty = true; 123 | } 124 | 125 | if (isDirty) 126 | EditorUtility.SetDirty(_instance); 127 | 128 | EditorGUI.indentLevel--; 129 | EditorGUILayout.EndVertical(); 130 | } 131 | 132 | bool playDetailFoldOpened = false; 133 | void StateOnPlay() 134 | { 135 | EditorGUILayout.LabelField("Play Mode", EditorStyles.boldLabel); 136 | 137 | // Controller 138 | EditorGUILayout.BeginHorizontal(); 139 | if (GUILayout.Button("Play")) 140 | _instance.Play(); 141 | if (GUILayout.Button("Stop")) 142 | _instance.Stop(); 143 | 144 | EditorGUILayout.EndHorizontal(); 145 | 146 | // State Information 147 | Color weightZeroColor = new Color(0.18f, 0.3f, 0.15f, 0.7f); 148 | Color weightOneColor = new Color(0.68f, 0.825f, 0.65f, 0.3f); 149 | EditorGUILayout.BeginVertical("Box"); 150 | EditorGUI.indentLevel++; 151 | 152 | playDetailFoldOpened = EditorGUILayout.Foldout(playDetailFoldOpened, playDetailFoldOpened ? "Detail" : "Simple"); 153 | for (int i = 0; i < _instance.States.Length; i++) { 154 | AnimationPlayer.AnimationState state = _instance.States[i]; 155 | if (state == null) 156 | continue; 157 | 158 | if (i == _instance.CurStateIndex) 159 | EditorGUILayout.LabelField(state.name, EditorStyles.boldLabel); 160 | else 161 | EditorGUILayout.LabelField(state.name); 162 | 163 | EditorGUILayout.ObjectField(state.clip, typeof(AnimationClip), true); 164 | Rect controlRect = GUILayoutUtility.GetLastRect(); 165 | Rect timeRect = controlRect; 166 | timeRect.x += 16; 167 | timeRect.y += 1; 168 | timeRect.width -= 35; 169 | timeRect.height -= 2; 170 | timeRect.width *= (state.normalizedTime - (int)state.normalizedTime); 171 | Color weightColor = Color.Lerp(weightZeroColor, weightOneColor, state.weight); 172 | EditorGUI.DrawRect(timeRect, weightColor); 173 | 174 | if (playDetailFoldOpened) { 175 | EditorGUILayout.DelayedFloatField("Time", state.time); 176 | EditorGUILayout.DelayedFloatField("NormalizedTime", state.normalizedTime); 177 | EditorGUILayout.DelayedFloatField("Weight", state.weight); 178 | EditorGUILayout.DelayedFloatField("FadeSpeed", state.fadeSpeed); 179 | } 180 | } 181 | 182 | EditorGUI.indentLevel--; 183 | EditorGUILayout.EndVertical(); 184 | } 185 | } --------------------------------------------------------------------------------