From f4d755dee69c87cba4c905d035a33bffa0bdcbd9 Mon Sep 17 00:00:00 2001 From: AkiKurisu <2683987717@qq.com> Date: Mon, 7 Oct 2024 20:09:30 +0800 Subject: [PATCH 1/8] Update SoftAssetReferenceEditorUtils.cs Ensure address is included in build --- Modules/Resource/Editor/SoftAssetReferenceEditorUtils.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Modules/Resource/Editor/SoftAssetReferenceEditorUtils.cs b/Modules/Resource/Editor/SoftAssetReferenceEditorUtils.cs index 6d00f49..03bb659 100644 --- a/Modules/Resource/Editor/SoftAssetReferenceEditorUtils.cs +++ b/Modules/Resource/Editor/SoftAssetReferenceEditorUtils.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using UObject = UnityEngine.Object; using Kurisu.Framework.Serialization; +using UnityEditor.AddressableAssets.Settings.GroupSchemas; namespace Kurisu.Framework.Resource.Editor { public static class SoftAssetReferenceEditorUtils @@ -130,7 +131,11 @@ public static AddressableAssetGroup GetOrCreateAssetGroup(string groupName) { var group = AddressableAssetSettingsDefaultObject.Settings.groups.FirstOrDefault(x => x.name == groupName); if (group != null) return group; - return AddressableAssetSettingsDefaultObject.Settings.CreateGroup(groupName, false, false, true, AddressableAssetSettingsDefaultObject.Settings.DefaultGroup.Schemas); + group = AddressableAssetSettingsDefaultObject.Settings.CreateGroup(groupName, false, false, true, AddressableAssetSettingsDefaultObject.Settings.DefaultGroup.Schemas); + // Ensure address is included in build + BundledAssetGroupSchema infoSchema = group.GetSchema(); + infoSchema.IncludeAddressInCatalog = true; + return group; } public static AddressableAssetEntry AddAsset(this AddressableAssetGroup group, UObject asset, params string[] labels) { From e3a757521e3955826445b7804670a7dd0f5f4d12 Mon Sep 17 00:00:00 2001 From: AkiKurisu <2683987717@qq.com> Date: Thu, 10 Oct 2024 22:52:10 +0800 Subject: [PATCH 2/8] Add EventDispatchingListener --- Runtime/Core/Events/EventSystem.cs | 1 - .../Events/Interfaces/IEventCoordinator.cs | 5 ++++ .../Interfaces/IEventDispatchingStrategy.cs | 5 ++++ Runtime/Core/Events/Models/EventBase.cs | 6 ++--- Runtime/Core/Events/Models/EventDispatcher.cs | 26 +++++++++++++++++-- 5 files changed, 37 insertions(+), 6 deletions(-) diff --git a/Runtime/Core/Events/EventSystem.cs b/Runtime/Core/Events/EventSystem.cs index 35be6ff..f647b0b 100644 --- a/Runtime/Core/Events/EventSystem.cs +++ b/Runtime/Core/Events/EventSystem.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using UnityEngine; namespace Kurisu.Framework.Events { diff --git a/Runtime/Core/Events/Interfaces/IEventCoordinator.cs b/Runtime/Core/Events/Interfaces/IEventCoordinator.cs index 6d497d7..8b46e1b 100644 --- a/Runtime/Core/Events/Interfaces/IEventCoordinator.cs +++ b/Runtime/Core/Events/Interfaces/IEventCoordinator.cs @@ -10,5 +10,10 @@ public interface IEventCoordinator /// /// CallbackEventHandler GetCallbackEventHandler(); + /// + /// Get coordinator's dispatcher + /// + /// + EventDispatcher EventDispatcher { get; } } } diff --git a/Runtime/Core/Events/Interfaces/IEventDispatchingStrategy.cs b/Runtime/Core/Events/Interfaces/IEventDispatchingStrategy.cs index 672f6c7..08e2afc 100644 --- a/Runtime/Core/Events/Interfaces/IEventDispatchingStrategy.cs +++ b/Runtime/Core/Events/Interfaces/IEventDispatchingStrategy.cs @@ -61,6 +61,11 @@ public interface IEventDispatchingStrategy bool CanDispatchEvent(EventBase evt); void DispatchEvent(EventBase evt, IEventCoordinator coordinator); } + public interface IEventDispatchingListener + { + void OnPushDispatcherContext(); + void OnPopDispatcherContext(); + } internal static class EventDispatchUtilities { public static void PropagateEvent(EventBase evt) diff --git a/Runtime/Core/Events/Models/EventBase.cs b/Runtime/Core/Events/Models/EventBase.cs index 1c81f3d..5fbf074 100644 --- a/Runtime/Core/Events/Models/EventBase.cs +++ b/Runtime/Core/Events/Models/EventBase.cs @@ -353,7 +353,7 @@ private set } - internal bool StopDispatch + public bool StopDispatch { get { return (Status & LifeCycleStatus.StopDispatch) != LifeCycleStatus.None; } set @@ -447,7 +447,7 @@ protected bool Pooled } } - internal abstract void Acquire(); + public abstract void Acquire(); /// /// Implementation of . /// @@ -554,7 +554,7 @@ static void ReleasePooled(T evt) } } - internal override void Acquire() + public override void Acquire() { m_RefCount++; } diff --git a/Runtime/Core/Events/Models/EventDispatcher.cs b/Runtime/Core/Events/Models/EventDispatcher.cs index 76c288c..1e18ab3 100644 --- a/Runtime/Core/Events/Models/EventDispatcher.cs +++ b/Runtime/Core/Events/Models/EventDispatcher.cs @@ -84,10 +84,11 @@ private struct DispatchContext public uint m_GateCount; public Queue m_Queue; } - + private readonly List dispatchingListeners; private readonly Stack m_DispatchContexts = new(); public EventDispatcher(IList strategies) { + dispatchingListeners = new List(); m_DispatchingStrategies = new List(); #if UNITY_EDITOR m_DebuggerEventDispatchingStrategy = new DebuggerEventDispatchingStrategy(); @@ -136,6 +137,11 @@ public void Dispatch(EventBase evt, IEventCoordinator coordinator, DispatchMode public void PushDispatcherContext() { + foreach (var listener in dispatchingListeners) + { + listener.OnPushDispatcherContext(); + } + // Drain the event queue before pushing a new context. ProcessEventQueue(); @@ -154,6 +160,11 @@ public void PopDispatcherContext() m_GateCount = m_DispatchContexts.Peek().m_GateCount; m_Queue = m_DispatchContexts.Peek().m_Queue; m_DispatchContexts.Pop(); + + foreach (var listener in dispatchingListeners) + { + listener.OnPopDispatcherContext(); + } } internal void CloseGate() @@ -281,6 +292,17 @@ private void ApplyDispatchingStrategies(EventBase evt, IEventCoordinator coordin } } } - + public TListener GetEventDispatchingListener() where TListener : IEventDispatchingListener + { + foreach (var listener in dispatchingListeners) + { + if (listener is TListener tlistener) return tlistener; + } + return default; + } + public void AddEventDispatchingListener(IEventDispatchingListener listener) + { + dispatchingListeners.Add(listener); + } } } \ No newline at end of file From 81d73f02bdbad0baef53e933ba654d4125d289aa Mon Sep 17 00:00:00 2001 From: AkiKurisu <2683987717@qq.com> Date: Mon, 14 Oct 2024 22:35:22 +0800 Subject: [PATCH 3/8] Add InvalidResourceRequestException summary --- Modules/Resource/Runtime/ResourceCache.cs | 16 ++++++++++------ Modules/Resource/Runtime/ResourceSystem.cs | 13 +++++++++++-- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Modules/Resource/Runtime/ResourceCache.cs b/Modules/Resource/Runtime/ResourceCache.cs index a8b9daf..03ae452 100644 --- a/Modules/Resource/Runtime/ResourceCache.cs +++ b/Modules/Resource/Runtime/ResourceCache.cs @@ -5,12 +5,6 @@ using Cysharp.Threading.Tasks; namespace Kurisu.Framework.Resource { - public class InvalidResourceRequestException : Exception - { - public string InvalidAddress { get; } - public InvalidResourceRequestException() : base() { } - public InvalidResourceRequestException(string address, string message) : base(message) { InvalidAddress = address; } - } /// /// Loading and cache specific asset as a group and release them by control version /// @@ -20,15 +14,25 @@ public class ResourceCache : IDisposable, IReadOnlyDictionary> internalHandles = new(); private readonly Dictionary cacheMap = new(); private readonly Dictionary versionMap = new(); + /// /// Validate asset location before loading, throw if not exist /// /// public bool AddressSafeCheck { get; set; } = false; + + /// + /// Current cache version + /// + /// public int Version { get; private set; } = 0; + public IEnumerable Keys => cacheMap.Keys; + public IEnumerable Values => cacheMap.Values; + public int Count => cacheMap.Count; + public TAsset this[string key] => cacheMap[key]; /// diff --git a/Modules/Resource/Runtime/ResourceSystem.cs b/Modules/Resource/Runtime/ResourceSystem.cs index 4106861..7a47494 100644 --- a/Modules/Resource/Runtime/ResourceSystem.cs +++ b/Modules/Resource/Runtime/ResourceSystem.cs @@ -7,6 +7,15 @@ using Cysharp.Threading.Tasks; namespace Kurisu.Framework.Resource { + /// + /// Exception thrown when request resource address is invalid + /// + public class InvalidResourceRequestException : Exception + { + public string InvalidAddress { get; } + public InvalidResourceRequestException() : base() { } + public InvalidResourceRequestException(string address, string message) : base(message) { InvalidAddress = address; } + } /// /// Resource system that loads resource by address and label based on Addressables. /// @@ -99,7 +108,7 @@ public static async UniTask SafeCheckAsync(object key) if (location.Status != AsyncOperationStatus.Succeeded || location.Result.Count == 0) { string stringValue; - if (key is IEnumerable list) stringValue = $"[{string.Join(",", list)}]"; + if (key is IEnumerable list) stringValue = $"[{string.Join(',', list)}]"; else stringValue = key.ToString(); throw new InvalidResourceRequestException(stringValue, $"Address {stringValue} not valid for loading {typeof(TAsset)} asset"); } @@ -118,7 +127,7 @@ public static async UniTask SafeCheckAsync(IEnumerable key, MergeMode me if (location.Status != AsyncOperationStatus.Succeeded || location.Result.Count == 0) { string stringValue; - if (key is IEnumerable list) stringValue = $"[{string.Join(",", list)}]"; + if (key is IEnumerable list) stringValue = $"[{string.Join(',', list)}]"; else stringValue = key.ToString(); throw new InvalidResourceRequestException(stringValue, $"Address {stringValue} not valid for loading {typeof(TAsset)} asset"); } From e5262454b054eec9d1e766c23a0d6a21f4c521a6 Mon Sep 17 00:00:00 2001 From: AkiKurisu <2683987717@qq.com> Date: Mon, 14 Oct 2024 22:35:47 +0800 Subject: [PATCH 4/8] Rename PlayableAnimator to AnimationProxy --- .../{Playables.meta => Animations.meta} | 0 .../AnimationProxy.cs} | 112 ++++++++++-------- .../AnimationProxy.cs.meta} | 0 .../AnimationSequenceBuilder.cs | 2 +- .../AnimationSequenceBuilder.cs.meta | 0 5 files changed, 64 insertions(+), 50 deletions(-) rename Runtime/GamePlay/{Playables.meta => Animations.meta} (100%) rename Runtime/GamePlay/{Playables/PlayableAnimator.cs => Animations/AnimationProxy.cs} (82%) rename Runtime/GamePlay/{Playables/PlayableAnimator.cs.meta => Animations/AnimationProxy.cs.meta} (100%) rename Runtime/GamePlay/{Playables => Animations}/AnimationSequenceBuilder.cs (99%) rename Runtime/GamePlay/{Playables => Animations}/AnimationSequenceBuilder.cs.meta (100%) diff --git a/Runtime/GamePlay/Playables.meta b/Runtime/GamePlay/Animations.meta similarity index 100% rename from Runtime/GamePlay/Playables.meta rename to Runtime/GamePlay/Animations.meta diff --git a/Runtime/GamePlay/Playables/PlayableAnimator.cs b/Runtime/GamePlay/Animations/AnimationProxy.cs similarity index 82% rename from Runtime/GamePlay/Playables/PlayableAnimator.cs rename to Runtime/GamePlay/Animations/AnimationProxy.cs index 565f40b..35caa2c 100644 --- a/Runtime/GamePlay/Playables/PlayableAnimator.cs +++ b/Runtime/GamePlay/Animations/AnimationProxy.cs @@ -4,59 +4,66 @@ using UnityEngine; using UnityEngine.Animations; using UnityEngine.Playables; -namespace Kurisu.Framework.Playables +namespace Kurisu.Framework.Animations { + // TODO: Refactor: Release animator already blend out + /// - /// Playable animator that can cross fade multi + /// Animation proxy that can cross fade multi /// /// /// Useful to override default animation in cutscene and dialogue. /// - public class PlayableAnimator : IDisposable + public class AnimationProxy : IDisposable { /// /// Bind animator /// /// public Animator Animator { get; } - private RuntimeAnimatorController sourceController; - private PlayableGraph playableGraph; - private AnimationPlayableOutput playableOutput; - private Playable mixerPointer; - private Playable rootMixer; - private AnimatorControllerPlayable playablePointer; - public AnimatorControllerPlayable CurrentPlayable => playablePointer; - public RuntimeAnimatorController currentController; - public bool IsPlaying - { - get - { - return playableGraph.IsValid() && playableGraph.IsPlaying(); - } - } + protected RuntimeAnimatorController sourceController; + protected PlayableGraph playableGraph; + protected AnimationPlayableOutput playableOutput; + protected Playable mixerPointer; + protected Playable rootMixer; + protected AnimatorControllerPlayable playablePointer; + protected RuntimeAnimatorController currentController; /// /// Handle for root blending task /// - private SchedulerHandle rootHandle; + protected SchedulerHandle rootHandle; /// /// Handle for subTree blending task /// /// - private readonly Dictionary subHandleMap = new(); - private bool isFadeOut; - public PlayableAnimator(Animator animator) + protected readonly Dictionary subHandleMap = new(); + protected bool isFadeOut; + public bool IsPlaying + { + get + { + return playableGraph.IsValid() && playableGraph.IsPlaying(); + } + } + public AnimationProxy(Animator animator) { Animator = animator; - CreateNewGraph(); + } + public virtual void Dispose() + { + currentController = null; + sourceController = null; + if (playableGraph.IsValid()) + playableGraph.Destroy(); } private void CreateNewGraph() { - playableGraph = PlayableGraph.Create($"{Animator.gameObject.name}_VirtualAnimator"); + playableGraph = PlayableGraph.Create($"{Animator.gameObject.name}_Playable"); playableOutput = AnimationPlayableOutput.Create(playableGraph, "Animation", Animator); mixerPointer = rootMixer = AnimationMixerPlayable.Create(playableGraph, 2); playableOutput.SetSourcePlayable(rootMixer); } - public void Play(RuntimeAnimatorController animatorController, float fadeInTime = 0.25f) + public void LoadAnimation(RuntimeAnimatorController animatorController, float fadeInTime = 0.25f) { if (isFadeOut) { @@ -64,12 +71,12 @@ public void Play(RuntimeAnimatorController animatorController, float fadeInTime SetOutGraph(); } if (IsPlaying && currentController == animatorController) return; - //If graph has root controller, destroy it + // If graph has root controller, destroy it if (playableGraph.IsValid()) { if (mixerPointer.GetInput(1).IsNull()) { - PlayInternal(animatorController, fadeInTime); + LoadAnimation_Imp(animatorController, fadeInTime); return; } else @@ -79,9 +86,9 @@ public void Play(RuntimeAnimatorController animatorController, float fadeInTime } } CreateNewGraph(); - PlayInternal(animatorController, fadeInTime); + LoadAnimation_Imp(animatorController, fadeInTime); } - private void PlayInternal(RuntimeAnimatorController animatorController, float fadeInTime = 0.25f) + protected void LoadAnimation_Imp(RuntimeAnimatorController animatorController, float fadeInTime = 0.25f) { sourceController = Animator.runtimeAnimatorController; playablePointer = AnimatorControllerPlayable.Create(playableGraph, currentController = animatorController); @@ -91,16 +98,21 @@ private void PlayInternal(RuntimeAnimatorController animatorController, float fa rootMixer.SetInputWeight(1, 0); rootHandle.Cancel(); if (fadeInTime > 0) - rootHandle = Scheduler.Delay(fadeInTime, SetInGraph, x => FadeIn(rootMixer, x / fadeInTime)); + rootHandle = Scheduler.Delay(fadeInTime, SetInGraph, x => Blend(rootMixer, x / fadeInTime)); else SetInGraph(); if (!IsPlaying) playableGraph.Play(); } private void SetInGraph() { - FadeIn(rootMixer, 1); + Blend(rootMixer, 1); Animator.runtimeAnimatorController = null; } + /// + /// Cross fade to a new animator controller + /// + /// + /// public void CrossFade(RuntimeAnimatorController animatorController, float fadeInTime = 0.25f) { if (isFadeOut) @@ -113,13 +125,13 @@ public void CrossFade(RuntimeAnimatorController animatorController, float fadeIn if (!playableGraph.IsValid()) { CreateNewGraph(); - PlayInternal(animatorController, fadeInTime); + LoadAnimation_Imp(animatorController, fadeInTime); return; } //If has no animator controller, use play instead if (mixerPointer.GetInput(1).IsNull()) { - PlayInternal(animatorController, fadeInTime); + LoadAnimation_Imp(animatorController, fadeInTime); } else { @@ -132,15 +144,15 @@ private void CrossFadeInternal(RuntimeAnimatorController animatorController, flo // Layout as a binary tree var newMixer = AnimationMixerPlayable.Create(playableGraph, 2); var right = mixerPointer.GetInput(1); - //Disconnect leaf + // Disconnect leaf playableGraph.Disconnect(mixerPointer, 1); - //Right=>left + // Right=>left playableGraph.Connect(right, 0, newMixer, 0); - //New right leaf + // New right leaf playableGraph.Connect(playablePointer, 0, newMixer, 1); - //Connect to parent + // Connect to parent playableGraph.Connect(newMixer, 0, mixerPointer, 1); - //Update pointer + // Update pointer mixerPointer = newMixer; mixerPointer.SetInputWeight(0, 1); mixerPointer.SetInputWeight(1, 0); @@ -149,11 +161,16 @@ private void CrossFadeInternal(RuntimeAnimatorController animatorController, flo handle.Cancel(); } if (fadeInTime > 0) - subHandleMap[animatorController] = Scheduler.Delay(fadeInTime, () => FadeIn(mixerPointer, 1), x => FadeIn(mixerPointer, x / fadeInTime)); + subHandleMap[animatorController] = Scheduler.Delay(fadeInTime, () => Blend(mixerPointer, 1), x => Blend(mixerPointer, x / fadeInTime)); else - FadeIn(mixerPointer, 1); + Blend(mixerPointer, 1); } - private void FadeIn(Playable playable, float weight) + /// + /// Utils function for blend playable with tow childs + /// + /// + /// + public static void Blend(Playable playable, float weight) { playable.SetInputWeight(0, 1 - weight); playable.SetInputWeight(1, weight); @@ -174,22 +191,19 @@ public void Stop(float fadeOutTime = 0.25f) return; } isFadeOut = true; - rootHandle = Scheduler.Delay(fadeOutTime, SetOutGraph, x => FadeIn(rootMixer, 1 - x / fadeOutTime)); + rootHandle = Scheduler.Delay(fadeOutTime, SetOutGraph, x => Blend(rootMixer, 1 - x / fadeOutTime)); } - private void SetOutGraph() + protected virtual void SetOutGraph() { - FadeIn(rootMixer, 0); + Blend(rootMixer, 0); isFadeOut = false; playableGraph.Stop(); playableGraph.Destroy(); currentController = null; } - public void Dispose() + public bool IsCurrent(RuntimeAnimatorController runtimeAnimatorController) { - currentController = null; - sourceController = null; - if (playableGraph.IsValid()) - playableGraph.Destroy(); + return currentController == runtimeAnimatorController; } #region Wrap public float GetFloat(string name) diff --git a/Runtime/GamePlay/Playables/PlayableAnimator.cs.meta b/Runtime/GamePlay/Animations/AnimationProxy.cs.meta similarity index 100% rename from Runtime/GamePlay/Playables/PlayableAnimator.cs.meta rename to Runtime/GamePlay/Animations/AnimationProxy.cs.meta diff --git a/Runtime/GamePlay/Playables/AnimationSequenceBuilder.cs b/Runtime/GamePlay/Animations/AnimationSequenceBuilder.cs similarity index 99% rename from Runtime/GamePlay/Playables/AnimationSequenceBuilder.cs rename to Runtime/GamePlay/Animations/AnimationSequenceBuilder.cs index 9ad9efd..adb1da6 100644 --- a/Runtime/GamePlay/Playables/AnimationSequenceBuilder.cs +++ b/Runtime/GamePlay/Animations/AnimationSequenceBuilder.cs @@ -5,7 +5,7 @@ using UnityEngine.Animations; using UnityEngine.Playables; using UnityEngine.Pool; -namespace Kurisu.Framework.Playables.Tasks +namespace Kurisu.Framework.Animations { /// /// Builder for creating sequence on animation clips, using UnityEngine.Playables API. diff --git a/Runtime/GamePlay/Playables/AnimationSequenceBuilder.cs.meta b/Runtime/GamePlay/Animations/AnimationSequenceBuilder.cs.meta similarity index 100% rename from Runtime/GamePlay/Playables/AnimationSequenceBuilder.cs.meta rename to Runtime/GamePlay/Animations/AnimationSequenceBuilder.cs.meta From 220816124d5d15b03e538b0d9d3ba657a2a2a0ce Mon Sep 17 00:00:00 2001 From: AkiKurisu <2683987717@qq.com> Date: Tue, 15 Oct 2024 22:01:08 +0800 Subject: [PATCH 5/8] Update AnimationProxy.cs --- Runtime/GamePlay/Animations/AnimationProxy.cs | 66 ++++++------------- 1 file changed, 20 insertions(+), 46 deletions(-) diff --git a/Runtime/GamePlay/Animations/AnimationProxy.cs b/Runtime/GamePlay/Animations/AnimationProxy.cs index 35caa2c..af2c326 100644 --- a/Runtime/GamePlay/Animations/AnimationProxy.cs +++ b/Runtime/GamePlay/Animations/AnimationProxy.cs @@ -70,29 +70,33 @@ public void LoadAnimation(RuntimeAnimatorController animatorController, float fa rootHandle.Cancel(); SetOutGraph(); } + LoadAnimation_Imp(animatorController, fadeInTime); + } + protected void LoadAnimation_Imp(RuntimeAnimatorController animatorController, float fadeInTime = 0.25f) + { if (IsPlaying && currentController == animatorController) return; - // If graph has root controller, destroy it - if (playableGraph.IsValid()) + // If Graph is not created or already destroyed, create a new one and use play api + if (!playableGraph.IsValid()) { - if (mixerPointer.GetInput(1).IsNull()) - { - LoadAnimation_Imp(animatorController, fadeInTime); - return; - } - else - { - playableGraph.Stop(); - playableGraph.Destroy(); - } + CreateNewGraph(); + PlayInternal(animatorController, fadeInTime); + return; + } + // If has no animator controller, use play instead + if (mixerPointer.GetInput(1).IsNull()) + { + PlayInternal(animatorController, fadeInTime); + } + else + { + CrossFadeInternal(animatorController, fadeInTime); } - CreateNewGraph(); - LoadAnimation_Imp(animatorController, fadeInTime); } - protected void LoadAnimation_Imp(RuntimeAnimatorController animatorController, float fadeInTime = 0.25f) + private void PlayInternal(RuntimeAnimatorController animatorController, float fadeInTime = 0.25f) { sourceController = Animator.runtimeAnimatorController; playablePointer = AnimatorControllerPlayable.Create(playableGraph, currentController = animatorController); - //Connect to second input of mixer + // Connect to second input of mixer playableGraph.Connect(playablePointer, 0, rootMixer, 1); rootMixer.SetInputWeight(0, 1); rootMixer.SetInputWeight(1, 0); @@ -108,36 +112,6 @@ private void SetInGraph() Blend(rootMixer, 1); Animator.runtimeAnimatorController = null; } - /// - /// Cross fade to a new animator controller - /// - /// - /// - public void CrossFade(RuntimeAnimatorController animatorController, float fadeInTime = 0.25f) - { - if (isFadeOut) - { - rootHandle.Cancel(); - SetOutGraph(); - } - if (IsPlaying && currentController == animatorController) return; - //Graph is destroyed, create new graph and play instead - if (!playableGraph.IsValid()) - { - CreateNewGraph(); - LoadAnimation_Imp(animatorController, fadeInTime); - return; - } - //If has no animator controller, use play instead - if (mixerPointer.GetInput(1).IsNull()) - { - LoadAnimation_Imp(animatorController, fadeInTime); - } - else - { - CrossFadeInternal(animatorController, fadeInTime); - } - } private void CrossFadeInternal(RuntimeAnimatorController animatorController, float fadeInTime = 0.25f) { playablePointer = AnimatorControllerPlayable.Create(playableGraph, currentController = animatorController); From 65f8f5e28d65a3585447ae74b515718e1e39ab50 Mon Sep 17 00:00:00 2001 From: AkiKurisu <2683987717@qq.com> Date: Sat, 19 Oct 2024 16:12:36 +0800 Subject: [PATCH 6/8] Update AnimationProxy AnimationProxy can now both blend animation clip and animatorcontroller. Refactor AnimationProxy internal playable logic. --- Runtime/Core/Tasks/Models/TaskBase.cs | 5 +- .../AnimationProxy.AnimationMontage.cs | 370 ++++++++++++ ...> AnimationProxy.AnimationMontage.cs.meta} | 2 +- .../AnimationProxy.AnimationSequence.cs | 260 ++++++++ .../AnimationProxy.AnimationSequence.cs.meta | 11 + Runtime/GamePlay/Animations/AnimationProxy.cs | 564 ++++++++---------- .../Animations/AnimationSequenceBuilder.cs | 361 ----------- 7 files changed, 902 insertions(+), 671 deletions(-) create mode 100644 Runtime/GamePlay/Animations/AnimationProxy.AnimationMontage.cs rename Runtime/GamePlay/Animations/{AnimationSequenceBuilder.cs.meta => AnimationProxy.AnimationMontage.cs.meta} (83%) create mode 100644 Runtime/GamePlay/Animations/AnimationProxy.AnimationSequence.cs create mode 100644 Runtime/GamePlay/Animations/AnimationProxy.AnimationSequence.cs.meta delete mode 100644 Runtime/GamePlay/Animations/AnimationSequenceBuilder.cs diff --git a/Runtime/Core/Tasks/Models/TaskBase.cs b/Runtime/Core/Tasks/Models/TaskBase.cs index c2a747b..7d469d8 100644 --- a/Runtime/Core/Tasks/Models/TaskBase.cs +++ b/Runtime/Core/Tasks/Models/TaskBase.cs @@ -80,7 +80,6 @@ protected virtual void Init() { mStatus = TaskStatus.Stopped; } - public virtual void Stop() { mStatus = TaskStatus.Stopped; @@ -97,6 +96,10 @@ public virtual void Pause() public virtual void Tick() { + } + protected void CompleteTask() + { + mStatus = TaskStatus.Completed; } protected virtual void Reset() { diff --git a/Runtime/GamePlay/Animations/AnimationProxy.AnimationMontage.cs b/Runtime/GamePlay/Animations/AnimationProxy.AnimationMontage.cs new file mode 100644 index 0000000..64e6545 --- /dev/null +++ b/Runtime/GamePlay/Animations/AnimationProxy.AnimationMontage.cs @@ -0,0 +1,370 @@ +using System; +using System.Collections.Generic; +using Kurisu.Framework.Schedulers; +using UnityEngine; +using UnityEngine.Animations; +using UnityEngine.Playables; +namespace Kurisu.Framework.Animations +{ + public partial class AnimationProxy + { + public class AnimationPlayableNode : IDisposable + { + public PlayableGraph Graph { get; } + public Playable Playable { get; protected set; } + public AnimationPlayableNode(Playable playable) + { + Graph = playable.GetGraph(); + Playable = playable; + } + public static AnimationMontageNode operator |(AnimationMontageNode left, AnimationPlayableNode right) + { + return AnimationMontageNode.CreateMontage(left, right); + } + public static implicit operator AnimationPlayableNode(Playable playable) + { + return new AnimationPlayableNode(playable); + } + /// + /// Destroy playable recursively + /// + public void Destroy() + { + Playable playable = Playable; + while (playable.IsValid()) + { + var input = playable.GetInput(0); + playable.Destroy(); + playable = input; + } + } + public virtual void Dispose() + { + + } + } + public class AnimationMontageNode : AnimationPlayableNode + { + public AnimationMixerPlayable Montage => (AnimationMixerPlayable)Playable; + public AnimationMontageNode Parent; + public AnimationPlayableNode Child; /* Can be montage or normal playable node */ + public SchedulerHandle BlendHandle; + public float BlendWeight => Playable.GetInputWeight(1); + public AnimationMontageNode(AnimationMixerPlayable playable) : base(playable) + { + } + + /// + /// Whether node is composite root, which should have no left child + /// + /// + public bool IsRootMontage() + { + return Parent == null; + } + public override void Dispose() + { + BlendHandle.Dispose(); + Child?.Dispose(); + Child = null; + Parent = null; + } + public static AnimationMontageNode CreateRootMontage(Playable sourcePlayable) + { + var graph = sourcePlayable.GetGraph(); + var newMixer = AnimationMixerPlayable.Create(graph, 2); + + // Only has right child + graph.Connect(sourcePlayable, 0, newMixer, 1); + + // Set weight + newMixer.SetInputWeight(0, 1); + newMixer.SetInputWeight(1, 0); + + return new AnimationMontageNode(newMixer) + { + Parent = null, + Child = new AnimationPlayableNode(sourcePlayable) + }; + } + public static AnimationMontageNode CreateMontage(AnimationMontageNode parent, AnimationPlayableNode source) + { + var playablePtr = source.Playable; + var graph = parent.Graph; + var leafMontage = parent.Montage; + var leafNode = parent.Child; + + // Layout as a binary tree + var newMontage = AnimationMixerPlayable.Create(graph, 2); + + // Disconnect right leaf from leaf montage + graph.Disconnect(leafMontage, 1); + // Current right leaf => New left leaf + graph.Connect(leafNode.Playable, 0, newMontage, 0); + // New right leaf + graph.Connect(playablePtr, 0, newMontage, 1); + // Connect to parent + graph.Connect(newMontage, 0, leafMontage, 1); + // Set weight + newMontage.SetInputWeight(0, 1); + newMontage.SetInputWeight(1, 0); + + var newMontageNode = new AnimationMontageNode(newMontage) + { + Parent = parent, + Child = source + }; + // Link child + parent.Child = newMontageNode; + + return newMontageNode; + } + /// + /// Blend internal playable weight + /// + /// + public void Blend(float weight) + { + Playable.SetInputWeight(0, 1 - weight); + Playable.SetInputWeight(1, weight); + } + /// + /// Shrink link list + /// + /// Current leaf node + public AnimationMontageNode Shrink() + { + if (BlendWeight != 1 || Parent == null) + { + return this; + } + // Disconnect child output first + Graph.Disconnect(Montage, 1); + Parent.SetChild(Child); + var parent = Parent; + // Release playable + Child = null; + Parent = null; + Destroy(); + return parent.Shrink(); + } + private void SetChild(AnimationPlayableNode newChild) + { + Child = newChild; + Graph.Disconnect(Montage, 1); + Graph.Connect(newChild.Playable, 0, Montage, 1); + } + public void ScheduleBlendIn(float duration, Action callBack = null) + { + Scheduler.Delay(ref BlendHandle, duration, () => { Blend(1); callBack?.Invoke(); }, x => Blend(x / duration)); + } + public void ScheduleBlendOut(float duration, Action callBack = null) + { + Scheduler.Delay(ref BlendHandle, duration, () => { Blend(0); callBack?.Invoke(); }, x => Blend(1 - x / duration)); + } + } + #region Wrapper + public float GetFloat(string name) + { + return LeafAnimatorPlayable.GetFloat(name); + } + + public float GetFloat(int id) + { + return LeafAnimatorPlayable.GetFloat(id); + } + public void SetFloat(string name, float value) + { + LeafAnimatorPlayable.SetFloat(name, value); + } + public void SetFloat(int id, float value) + { + LeafAnimatorPlayable.SetFloat(id, value); + } + public bool GetBool(string name) + { + return LeafAnimatorPlayable.GetBool(name); + } + public bool GetBool(int id) + { + return LeafAnimatorPlayable.GetBool(id); + } + public void SetBool(string name, bool value) + { + LeafAnimatorPlayable.SetBool(name, value); + } + + public void SetBool(int id, bool value) + { + LeafAnimatorPlayable.SetBool(id, value); + } + + public int GetInteger(string name) + { + return LeafAnimatorPlayable.GetInteger(name); + } + public int GetInteger(int id) + { + return LeafAnimatorPlayable.GetInteger(id); + } + public void SetInteger(string name, int value) + { + LeafAnimatorPlayable.SetInteger(name, value); + } + + public void SetInteger(int id, int value) + { + LeafAnimatorPlayable.SetInteger(id, value); + } + public void SetTrigger(string name) + { + LeafAnimatorPlayable.SetTrigger(name); + } + + public void SetTrigger(int id) + { + LeafAnimatorPlayable.SetTrigger(id); + } + + public void ResetTrigger(string name) + { + LeafAnimatorPlayable.ResetTrigger(name); + } + public void ResetTrigger(int id) + { + LeafAnimatorPlayable.ResetTrigger(id); + } + public bool IsParameterControlledByCurve(string name) + { + return LeafAnimatorPlayable.IsParameterControlledByCurve(name); + } + public bool IsParameterControlledByCurve(int id) + { + return LeafAnimatorPlayable.IsParameterControlledByCurve(id); + } + + public int GetLayerCount() + { + return LeafAnimatorPlayable.GetLayerCount(); + } + + public string GetLayerName(int layerIndex) + { + return LeafAnimatorPlayable.GetLayerName(layerIndex); + } + + public int GetLayerIndex(string layerName) + { + return LeafAnimatorPlayable.GetLayerIndex(layerName); + } + public float GetLayerWeight(int layerIndex) + { + return LeafAnimatorPlayable.GetLayerWeight(layerIndex); + } + public void SetLayerWeight(int layerIndex, float weight) + { + LeafAnimatorPlayable.SetLayerWeight(layerIndex, weight); + } + + public AnimatorStateInfo GetCurrentAnimatorStateInfo(int layerIndex) + { + return LeafAnimatorPlayable.GetCurrentAnimatorStateInfo(layerIndex); + } + + public AnimatorStateInfo GetNextAnimatorStateInfo(int layerIndex) + { + return LeafAnimatorPlayable.GetNextAnimatorStateInfo(layerIndex); + } + + public AnimatorTransitionInfo GetAnimatorTransitionInfo(int layerIndex) + { + return LeafAnimatorPlayable.GetAnimatorTransitionInfo(layerIndex); + } + + public AnimatorClipInfo[] GetCurrentAnimatorClipInfo(int layerIndex) + { + return LeafAnimatorPlayable.GetCurrentAnimatorClipInfo(layerIndex); + } + + public void GetCurrentAnimatorClipInfo(int layerIndex, List clips) + { + LeafAnimatorPlayable.GetCurrentAnimatorClipInfo(layerIndex, clips); + } + + public void GetNextAnimatorClipInfo(int layerIndex, List clips) + { + LeafAnimatorPlayable.GetNextAnimatorClipInfo(layerIndex, clips); + } + public int GetCurrentAnimatorClipInfoCount(int layerIndex) + { + return LeafAnimatorPlayable.GetCurrentAnimatorClipInfoCount(layerIndex); + } + public int GetNextAnimatorClipInfoCount(int layerIndex) + { + return LeafAnimatorPlayable.GetNextAnimatorClipInfoCount(layerIndex); + } + + public AnimatorClipInfo[] GetNextAnimatorClipInfo(int layerIndex) + { + return LeafAnimatorPlayable.GetNextAnimatorClipInfo(layerIndex); + } + public bool IsInTransition(int layerIndex) + { + return LeafAnimatorPlayable.IsInTransition(layerIndex); + } + + public int GetParameterCount() + { + return LeafAnimatorPlayable.GetParameterCount(); + } + + public AnimatorControllerParameter GetParameter(int index) + { + return LeafAnimatorPlayable.GetParameter(index); + } + + public void CrossFadeInFixedTime(string stateName, float transitionDuration, int layer = -1, float fixedTime = 0f) + { + LeafAnimatorPlayable.CrossFadeInFixedTime(stateName, transitionDuration, layer, fixedTime); + } + public void CrossFadeInFixedTime(int stateNameHash, float transitionDuration, int layer = -1, float fixedTime = 0.0f) + { + LeafAnimatorPlayable.CrossFadeInFixedTime(stateNameHash, transitionDuration, layer, fixedTime); + } + public void CrossFade(string stateName, float transitionDuration, int layer = -1, float normalizedTime = float.NegativeInfinity) + { + LeafAnimatorPlayable.CrossFade(stateName, transitionDuration, layer, normalizedTime); + } + + public void CrossFade(int stateNameHash, float transitionDuration, int layer = -1, float normalizedTime = float.NegativeInfinity) + { + LeafAnimatorPlayable.CrossFade(stateNameHash, transitionDuration, layer, normalizedTime); + } + public void PlayInFixedTime(string stateName, int layer = -1, float fixedTime = float.NegativeInfinity) + { + LeafAnimatorPlayable.PlayInFixedTime(stateName, layer, fixedTime); + } + + public void PlayInFixedTime(int stateNameHash, int layer = -1, float fixedTime = float.NegativeInfinity) + { + LeafAnimatorPlayable.PlayInFixedTime(stateNameHash, layer, fixedTime); + } + + public void Play(string stateName, int layer = -1, float normalizedTime = float.NegativeInfinity) + { + LeafAnimatorPlayable.Play(stateName, layer, normalizedTime); + } + + + public void Play(int stateNameHash, int layer = -1, float normalizedTime = float.NegativeInfinity) + { + LeafAnimatorPlayable.Play(stateNameHash, layer, normalizedTime); + } + + public bool HasState(int layerIndex, int stateID) + { + return LeafAnimatorPlayable.HasState(layerIndex, stateID); + } + #endregion Public API + } +} diff --git a/Runtime/GamePlay/Animations/AnimationSequenceBuilder.cs.meta b/Runtime/GamePlay/Animations/AnimationProxy.AnimationMontage.cs.meta similarity index 83% rename from Runtime/GamePlay/Animations/AnimationSequenceBuilder.cs.meta rename to Runtime/GamePlay/Animations/AnimationProxy.AnimationMontage.cs.meta index 2960529..8ac2dc5 100644 --- a/Runtime/GamePlay/Animations/AnimationSequenceBuilder.cs.meta +++ b/Runtime/GamePlay/Animations/AnimationProxy.AnimationMontage.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: cadf53f8203ba834eb80ce431156dbbc +guid: a345f87ac716ab44f8c79a5242a56ae2 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/GamePlay/Animations/AnimationProxy.AnimationSequence.cs b/Runtime/GamePlay/Animations/AnimationProxy.AnimationSequence.cs new file mode 100644 index 0000000..8a5dc66 --- /dev/null +++ b/Runtime/GamePlay/Animations/AnimationProxy.AnimationSequence.cs @@ -0,0 +1,260 @@ +using System; +using System.Collections.Generic; +using Kurisu.Framework.Tasks; +using UnityEngine; +using UnityEngine.Pool; +namespace Kurisu.Framework.Animations +{ + public partial class AnimationProxy + { + public delegate void AnimationProxyDelegate(AnimationProxy animationProxy); + /// + /// Builder for creating dynamic animation sequence. + /// + public struct AnimationSequenceBuilder : IDisposable + { + private List taskBuffer; + private SequenceTask sequence; + private float blendOutTime; + private bool isDisposed; + private AnimationProxy proxy; + internal AnimationSequenceBuilder(AnimationProxy proxy) + { + this.proxy = proxy; + blendOutTime = 0f; + sequence = null; + isDisposed = false; + taskBuffer = ListPool.Get(); + } + /// + /// Append an animation clip + /// + /// Clip to play + /// FadeIn time + /// + public readonly AnimationSequenceBuilder Append(AnimationClip animationClip, float blendInDuration) + { + return Append(animationClip, animationClip.length, blendInDuration); + } + /// + /// Append an animation clip + /// + /// Clip to play + /// Duration can be infinity as loop + /// FadeIn time + /// + public readonly AnimationSequenceBuilder Append(AnimationClip animationClip, float duration, float blendInDuration) + { + if (!IsValid()) + { + Debug.LogWarning("Builder is invalid but try to access it"); + return this; + } + taskBuffer.Add(LoadAnimationClipTask.GetPooled(proxy, animationClip, duration, blendInDuration)); + return this; + } + /// + /// Append an animatior controller + /// + /// Clip to play + /// Duration can be infinity as loop + /// FadeIn time + /// + public readonly AnimationSequenceBuilder Append(RuntimeAnimatorController animatorController, float duration, float blendInDuration) + { + if (!IsValid()) + { + Debug.LogWarning("Builder is invalid but try to access it"); + return this; + } + taskBuffer.Add(LoadAnimatorTask.GetPooled(proxy, animatorController, duration, blendInDuration)); + return this; + } + /// + /// Append a proxy call back after current last action in the sequence + /// + /// + /// + public readonly AnimationSequenceBuilder AppendCallBack(AnimationProxyDelegate callBack) + { + taskBuffer.Add(AnimationProxyCallBackTask.GetPooled(proxy, callBack)); + return this; + } + /// + /// Set animation sequence blend out time, default is 0 + /// + /// + /// + public AnimationSequenceBuilder SetBlendOut(float blendOutTime) + { + if (!IsValid()) + { + Debug.LogWarning("Builder is invalid but try to access it"); + return this; + } + this.blendOutTime = blendOutTime; + return this; + } + /// + /// Build an animation sequence + /// + public SequenceTask Build() + { + if (!IsValid()) + { + Debug.LogWarning("Builder is invalid, rebuild is not allowed"); + return sequence; + } + return BuildInternal(SequenceTask.GetPooled(Dispose)); + } + /// + /// Append animation sequence after an existed sequence + /// + /// + public void Build(SequenceTask sequenceTask) + { + if (!IsValid()) + { + Debug.LogWarning("Builder is invalid, rebuild is not allowed"); + return; + } + BuildInternal(sequenceTask); + sequenceTask.AppendCallBack(Dispose); + } + private SequenceTask BuildInternal(SequenceTask sequenceTask) + { + foreach (var task in taskBuffer) + { + sequenceTask.Append(task); + } + float time = blendOutTime; + AnimationProxy animProxy = proxy; + sequenceTask.AppendCallBack(() => animProxy.Stop(time)); + sequence = sequenceTask; + taskBuffer.Clear(); + sequence.Acquire(); + return sequence; + } + /// + /// Whether builder is valid + /// + /// + public readonly bool IsValid() + { + return sequence == null && !isDisposed; + } + /// + /// Dispose internal playable graph + /// + public void Dispose() + { + if (isDisposed) + { + return; + } + isDisposed = true; + proxy = null; + sequence = null; + ListPool.Release(taskBuffer); + taskBuffer = null; + } + } + private class LoadAnimationClipTask : PooledTaskBase + { + private AnimationProxy proxy; + private AnimationClip animationClip; + private float blendInTime; + private float duration; + private double startTimestamp; + public static LoadAnimationClipTask GetPooled(AnimationProxy proxy, AnimationClip animationClip, float duration, float blendInTime) + { + var task = GetPooled(); + task.proxy = proxy; + task.animationClip = animationClip; + task.duration = duration; + task.blendInTime = blendInTime; + return task; + } + public override void Tick() + { + if (Time.timeSinceLevelLoadAsDouble - startTimestamp >= duration) + { + CompleteTask(); + } + } + public override void Start() + { + base.Start(); + startTimestamp = Time.timeSinceLevelLoadAsDouble; + proxy.LoadAnimationClip(animationClip, blendInTime); + } + } + private class LoadAnimatorTask : PooledTaskBase + { + private AnimationProxy proxy; + private RuntimeAnimatorController animatorController; + private float blendInTime; + private float duration; + private double startTimestamp; + public static LoadAnimatorTask GetPooled(AnimationProxy proxy, RuntimeAnimatorController animatorController, float duration, float blendInTime) + { + var task = GetPooled(); + task.proxy = proxy; + task.animatorController = animatorController; + task.duration = duration; + task.blendInTime = blendInTime; + return task; + } + public override void Tick() + { + if (Time.timeSinceLevelLoadAsDouble - startTimestamp >= duration) + { + CompleteTask(); + } + } + public override void Start() + { + base.Start(); + startTimestamp = Time.timeSinceLevelLoadAsDouble; + proxy.LoadAnimator(animatorController, blendInTime); + } + protected override void Reset() + { + base.Reset(); + proxy = null; + animatorController = null; + } + } + private class AnimationProxyCallBackTask : PooledTaskBase + { + private AnimationProxy proxy; + private AnimationProxyDelegate callBack; + public static AnimationProxyCallBackTask GetPooled(AnimationProxy proxy, AnimationProxyDelegate callBack) + { + var task = GetPooled(); + task.callBack = callBack; + task.proxy = proxy; + return task; + } + public override void Tick() + { + callBack?.Invoke(proxy); + CompleteTask(); + } + protected override void Reset() + { + base.Reset(); + proxy = null; + callBack = null; + } + } + /// + /// Create an from this proxy + /// + /// + public AnimationSequenceBuilder CreateSequenceBuilder() + { + return new AnimationSequenceBuilder(this); + } + } +} diff --git a/Runtime/GamePlay/Animations/AnimationProxy.AnimationSequence.cs.meta b/Runtime/GamePlay/Animations/AnimationProxy.AnimationSequence.cs.meta new file mode 100644 index 0000000..5d7db3e --- /dev/null +++ b/Runtime/GamePlay/Animations/AnimationProxy.AnimationSequence.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 659c8d2d234c5db4b8f845ebdcea40c4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/GamePlay/Animations/AnimationProxy.cs b/Runtime/GamePlay/Animations/AnimationProxy.cs index af2c326..d432d8c 100644 --- a/Runtime/GamePlay/Animations/AnimationProxy.cs +++ b/Runtime/GamePlay/Animations/AnimationProxy.cs @@ -1,386 +1,334 @@ using System; -using System.Collections.Generic; -using Kurisu.Framework.Schedulers; using UnityEngine; using UnityEngine.Animations; using UnityEngine.Playables; namespace Kurisu.Framework.Animations { - // TODO: Refactor: Release animator already blend out - /// /// Animation proxy that can cross fade multi /// /// /// Useful to override default animation in cutscene and dialogue. /// - public class AnimationProxy : IDisposable + public partial class AnimationProxy : IDisposable { /// - /// Bind animator + /// Get bound /// /// public Animator Animator { get; } - protected RuntimeAnimatorController sourceController; - protected PlayableGraph playableGraph; - protected AnimationPlayableOutput playableOutput; - protected Playable mixerPointer; - protected Playable rootMixer; - protected AnimatorControllerPlayable playablePointer; - protected RuntimeAnimatorController currentController; /// - /// Handle for root blending task + /// Cached of /// - protected SchedulerHandle rootHandle; + /// + protected RuntimeAnimatorController SourceController { get; private set; } /// - /// Handle for subTree blending task + /// Cached input of /// - /// - protected readonly Dictionary subHandleMap = new(); - protected bool isFadeOut; + /// + protected RuntimeAnimatorController CurrentAnimatorController { get; private set; } + /// + /// Cached input of + /// + /// + protected AnimationClip CurrentAnimationClip { get; private set; } + /// + /// Get playing + /// + /// + protected PlayableGraph Graph { get; private set; } + /// + /// Get root montage node + /// + /// + protected AnimationMontageNode RootMontage { get; private set; } + /// + /// Get leaf montage node + /// + /// + protected AnimationMontageNode LeafMontage { get; private set; } + /// + /// Get leaf + /// + /// + protected Playable LeafPlayable { get; private set; } + /// + /// Get leaf if type matched + /// + /// + protected AnimatorControllerPlayable LeafAnimatorPlayable + { + get + { + if (LeafPlayable.IsPlayableOfType()) + { + return (AnimatorControllerPlayable)LeafPlayable; + } + return AnimatorControllerPlayable.Null; + } + } + /// + /// Get leaf if type matched + /// + /// + protected AnimationClipPlayable LeafAnimationClipPlayable + { + get + { + if (LeafPlayable.IsPlayableOfType()) + { + return (AnimationClipPlayable)LeafPlayable; + } + return default; + } + } + /// + /// Is proxy blendout + /// + /// + protected bool IsBlendIn { get; private set; } + /// + /// Is proxy blendout + /// + /// + protected bool IsBlendOut { get; private set; } + /// + /// Is proxy playing + /// + /// public bool IsPlaying { get { - return playableGraph.IsValid() && playableGraph.IsPlaying(); + return Graph.IsValid() && Graph.IsPlaying(); } } public AnimationProxy(Animator animator) { Animator = animator; } - public virtual void Dispose() + /// + /// Load animator to the graph + /// + /// + /// + protected virtual void LoadAnimator_Implementation(RuntimeAnimatorController animatorController, float blendInDuration = 0.25f) { - currentController = null; - sourceController = null; - if (playableGraph.IsValid()) - playableGraph.Destroy(); + if (IsPlaying && CurrentAnimatorController == animatorController) return; + // If Graph is not created or already destroyed, create a new one and use play api + if (!Graph.IsValid()) + { + PlayAnimatorInternal(animatorController, blendInDuration); + return; + } + BlendAnimatorInternal(animatorController, blendInDuration); } - private void CreateNewGraph() - { - playableGraph = PlayableGraph.Create($"{Animator.gameObject.name}_Playable"); - playableOutput = AnimationPlayableOutput.Create(playableGraph, "Animation", Animator); - mixerPointer = rootMixer = AnimationMixerPlayable.Create(playableGraph, 2); - playableOutput.SetSourcePlayable(rootMixer); + /// + /// Load animator to the graph in play mode + /// + /// + /// + protected void PlayAnimatorInternal(RuntimeAnimatorController animatorController, float blendInDuration = 0.25f) + { + // Create new graph + SourceController = Animator.runtimeAnimatorController; + Graph = PlayableGraph.Create($"{Animator.gameObject.name}_Playable"); + var playableOutput = AnimationPlayableOutput.Create(Graph, "Animation", Animator); + LeafPlayable = AnimatorControllerPlayable.Create(Graph, CurrentAnimatorController = animatorController); + LeafMontage = RootMontage = AnimationMontageNode.CreateRootMontage(LeafPlayable); + playableOutput.SetSourcePlayable(RootMontage.Montage); + + // Start play graph + PlayInternal(blendInDuration); } - public void LoadAnimation(RuntimeAnimatorController animatorController, float fadeInTime = 0.25f) + /// + /// Load animator to the graph in blend mode + /// + /// + /// + protected void BlendAnimatorInternal(RuntimeAnimatorController animatorController, float blendInDuration = 0.25f) { - if (isFadeOut) + LeafPlayable = AnimatorControllerPlayable.Create(Graph, CurrentAnimatorController = animatorController); + LeafMontage |= new AnimationPlayableNode(LeafPlayable); + if (blendInDuration > 0) { - rootHandle.Cancel(); - SetOutGraph(); + LeafMontage.ScheduleBlendIn(blendInDuration, () => Shrink(LeafMontage)); + } + else + { + LeafMontage.Blend(1); + Shrink(LeafMontage); } - LoadAnimation_Imp(animatorController, fadeInTime); } - protected void LoadAnimation_Imp(RuntimeAnimatorController animatorController, float fadeInTime = 0.25f) + /// + /// Load animation clip to the graph + /// + /// + /// + protected virtual void LoadAnimationClip_Implementation(AnimationClip animationClip, float blendInDuration = 0.25f) { - if (IsPlaying && currentController == animatorController) return; + if (IsPlaying && CurrentAnimationClip == animationClip) return; // If Graph is not created or already destroyed, create a new one and use play api - if (!playableGraph.IsValid()) + if (!Graph.IsValid()) { - CreateNewGraph(); - PlayInternal(animatorController, fadeInTime); + PlayAnimationClipInternal(animationClip, blendInDuration); return; } - // If has no animator controller, use play instead - if (mixerPointer.GetInput(1).IsNull()) + BlendAnimationClipInternal(animationClip, blendInDuration); + } + /// + /// Load animation clip to the graph in play mode + /// + /// + /// + protected void PlayAnimationClipInternal(AnimationClip animationClip, float blendInDuration = 0.25f) + { + // Create new graph + SourceController = Animator.runtimeAnimatorController; + Graph = PlayableGraph.Create($"{Animator.gameObject.name}_Playable"); + var playableOutput = AnimationPlayableOutput.Create(Graph, "Animation", Animator); + LeafPlayable = AnimationClipPlayable.Create(Graph, CurrentAnimationClip = animationClip); + LeafMontage = RootMontage = AnimationMontageNode.CreateRootMontage(LeafPlayable); + playableOutput.SetSourcePlayable(RootMontage.Montage); + + // Start play graph + PlayInternal(blendInDuration); + } + /// + /// Load animation clip to the graph in blend mode + /// + /// + /// + protected void BlendAnimationClipInternal(AnimationClip animationClip, float blendInDuration = 0.25f) + { + LeafPlayable = AnimationClipPlayable.Create(Graph, CurrentAnimationClip = animationClip); + LeafMontage |= new AnimationPlayableNode(LeafPlayable); + if (blendInDuration > 0) { - PlayInternal(animatorController, fadeInTime); + LeafMontage.ScheduleBlendIn(blendInDuration, () => Shrink(LeafMontage)); } else { - CrossFadeInternal(animatorController, fadeInTime); + LeafMontage.Blend(1); + Shrink(LeafMontage); } } - private void PlayInternal(RuntimeAnimatorController animatorController, float fadeInTime = 0.25f) + /// + /// Start play graph and montage + /// + /// + protected void PlayInternal(float blendInDuration) { - sourceController = Animator.runtimeAnimatorController; - playablePointer = AnimatorControllerPlayable.Create(playableGraph, currentController = animatorController); - // Connect to second input of mixer - playableGraph.Connect(playablePointer, 0, rootMixer, 1); - rootMixer.SetInputWeight(0, 1); - rootMixer.SetInputWeight(1, 0); - rootHandle.Cancel(); - if (fadeInTime > 0) - rootHandle = Scheduler.Delay(fadeInTime, SetInGraph, x => Blend(rootMixer, x / fadeInTime)); + IsBlendIn = true; + if (blendInDuration > 0) + { + RootMontage.ScheduleBlendIn(blendInDuration, SetInGraph); + } else + { + RootMontage.Blend(1); SetInGraph(); - if (!IsPlaying) playableGraph.Play(); + } + if (!IsPlaying) Graph.Play(); } - private void SetInGraph() + private void Shrink(AnimationMontageNode node) { - Blend(rootMixer, 1); + if (LeafMontage != node) return; /* Has new montage in blend */ + if (node.BlendWeight != 1) + { + Debug.LogWarning("[AnimationProxy] Montage is in use but try to release it."); + return; + } + LeafMontage = node.Shrink(); + } + /// + /// Call this function after animation proxy completly blend in + /// + protected virtual void SetInGraph() + { + IsBlendIn = false; Animator.runtimeAnimatorController = null; } - private void CrossFadeInternal(RuntimeAnimatorController animatorController, float fadeInTime = 0.25f) + /// + /// Call this function after animation proxy completly blend out + /// + protected virtual void SetOutGraph() + { + IsBlendOut = false; + Graph.Stop(); + Graph.Destroy(); + CurrentAnimatorController = null; + } + #region Public API + /// + /// Start playing animation from new + /// and blend in if greater than 0 + /// + /// + /// + public void LoadAnimator(RuntimeAnimatorController animatorController, float blendInDuration = 0.25f) { - playablePointer = AnimatorControllerPlayable.Create(playableGraph, currentController = animatorController); - // Layout as a binary tree - var newMixer = AnimationMixerPlayable.Create(playableGraph, 2); - var right = mixerPointer.GetInput(1); - // Disconnect leaf - playableGraph.Disconnect(mixerPointer, 1); - // Right=>left - playableGraph.Connect(right, 0, newMixer, 0); - // New right leaf - playableGraph.Connect(playablePointer, 0, newMixer, 1); - // Connect to parent - playableGraph.Connect(newMixer, 0, mixerPointer, 1); - // Update pointer - mixerPointer = newMixer; - mixerPointer.SetInputWeight(0, 1); - mixerPointer.SetInputWeight(1, 0); - if (subHandleMap.TryGetValue(animatorController, out var handle)) + // Ensure old graph is destroyed + if (IsBlendOut) { - handle.Cancel(); + RootMontage.BlendHandle.Cancel(); + SetOutGraph(); } - if (fadeInTime > 0) - subHandleMap[animatorController] = Scheduler.Delay(fadeInTime, () => Blend(mixerPointer, 1), x => Blend(mixerPointer, x / fadeInTime)); - else - Blend(mixerPointer, 1); + LoadAnimator_Implementation(animatorController, blendInDuration); } /// - /// Utils function for blend playable with tow childs + /// Start playing animation from new + /// and blend in if greater than 0 /// - /// - /// - public static void Blend(Playable playable, float weight) + /// + /// + public void LoadAnimationClip(AnimationClip animationClip, float blendInDuration = 0.25f) { - playable.SetInputWeight(0, 1 - weight); - playable.SetInputWeight(1, weight); + // Ensure old graph is destroyed + if (IsBlendOut) + { + RootMontage.BlendHandle.Cancel(); + SetOutGraph(); + } + LoadAnimationClip_Implementation(animationClip, blendInDuration); } - public void Stop(float fadeOutTime = 0.25f) + /// + /// Stop animation proxy montage and blend out if greater than 0 + /// + /// + public void Stop(float blendOutDuration = 0.25f) { if (!IsPlaying) return; - foreach (var handle in subHandleMap.Values) - { - handle.Cancel(); - } - subHandleMap.Clear(); - rootHandle.Cancel(); - Animator.runtimeAnimatorController = sourceController; - if (fadeOutTime < 0) + RootMontage.Dispose(); + Animator.runtimeAnimatorController = SourceController; + IsBlendOut = true; + if (blendOutDuration <= 0) { + RootMontage.Blend(0); SetOutGraph(); return; } - isFadeOut = true; - rootHandle = Scheduler.Delay(fadeOutTime, SetOutGraph, x => Blend(rootMixer, 1 - x / fadeOutTime)); + RootMontage.ScheduleBlendOut(blendOutDuration, SetOutGraph); } - protected virtual void SetOutGraph() + /// + /// Release animation proxy + /// + public virtual void Dispose() { - Blend(rootMixer, 0); - isFadeOut = false; - playableGraph.Stop(); - playableGraph.Destroy(); - currentController = null; + CurrentAnimatorController = null; + SourceController = null; + if (Graph.IsValid()) + Graph.Destroy(); } + /// + /// Check if use this + /// + /// + /// public bool IsCurrent(RuntimeAnimatorController runtimeAnimatorController) { - return currentController == runtimeAnimatorController; - } - #region Wrap - public float GetFloat(string name) - { - return playablePointer.GetFloat(name); - } - - public float GetFloat(int id) - { - return playablePointer.GetFloat(id); - } - public void SetFloat(string name, float value) - { - playablePointer.SetFloat(name, value); - } - public void SetFloat(int id, float value) - { - playablePointer.SetFloat(id, value); - } - public bool GetBool(string name) - { - return playablePointer.GetBool(name); - } - public bool GetBool(int id) - { - return playablePointer.GetBool(id); - } - public void SetBool(string name, bool value) - { - playablePointer.SetBool(name, value); - } - - public void SetBool(int id, bool value) - { - playablePointer.SetBool(id, value); - } - - public int GetInteger(string name) - { - return playablePointer.GetInteger(name); - } - public int GetInteger(int id) - { - return playablePointer.GetInteger(id); - } - public void SetInteger(string name, int value) - { - playablePointer.SetInteger(name, value); - } - - public void SetInteger(int id, int value) - { - playablePointer.SetInteger(id, value); - } - public void SetTrigger(string name) - { - playablePointer.SetTrigger(name); - } - - public void SetTrigger(int id) - { - playablePointer.SetTrigger(id); - } - - public void ResetTrigger(string name) - { - playablePointer.ResetTrigger(name); - } - public void ResetTrigger(int id) - { - playablePointer.ResetTrigger(id); - } - public bool IsParameterControlledByCurve(string name) - { - return playablePointer.IsParameterControlledByCurve(name); - } - public bool IsParameterControlledByCurve(int id) - { - return playablePointer.IsParameterControlledByCurve(id); - } - - public int GetLayerCount() - { - return playablePointer.GetLayerCount(); - } - - public string GetLayerName(int layerIndex) - { - return playablePointer.GetLayerName(layerIndex); - } - - public int GetLayerIndex(string layerName) - { - return playablePointer.GetLayerIndex(layerName); - } - public float GetLayerWeight(int layerIndex) - { - return playablePointer.GetLayerWeight(layerIndex); - } - public void SetLayerWeight(int layerIndex, float weight) - { - playablePointer.SetLayerWeight(layerIndex, weight); - } - - public AnimatorStateInfo GetCurrentAnimatorStateInfo(int layerIndex) - { - return playablePointer.GetCurrentAnimatorStateInfo(layerIndex); - } - - public AnimatorStateInfo GetNextAnimatorStateInfo(int layerIndex) - { - return playablePointer.GetNextAnimatorStateInfo(layerIndex); - } - - public AnimatorTransitionInfo GetAnimatorTransitionInfo(int layerIndex) - { - return playablePointer.GetAnimatorTransitionInfo(layerIndex); - } - - public AnimatorClipInfo[] GetCurrentAnimatorClipInfo(int layerIndex) - { - return playablePointer.GetCurrentAnimatorClipInfo(layerIndex); - } - - public void GetCurrentAnimatorClipInfo(int layerIndex, List clips) - { - playablePointer.GetCurrentAnimatorClipInfo(layerIndex, clips); - } - - public void GetNextAnimatorClipInfo(int layerIndex, List clips) - { - playablePointer.GetNextAnimatorClipInfo(layerIndex, clips); - } - public int GetCurrentAnimatorClipInfoCount(int layerIndex) - { - return playablePointer.GetCurrentAnimatorClipInfoCount(layerIndex); - } - public int GetNextAnimatorClipInfoCount(int layerIndex) - { - return playablePointer.GetNextAnimatorClipInfoCount(layerIndex); - } - - public AnimatorClipInfo[] GetNextAnimatorClipInfo(int layerIndex) - { - return playablePointer.GetNextAnimatorClipInfo(layerIndex); - } - public bool IsInTransition(int layerIndex) - { - return playablePointer.IsInTransition(layerIndex); - } - - public int GetParameterCount() - { - return playablePointer.GetParameterCount(); - } - - public AnimatorControllerParameter GetParameter(int index) - { - return playablePointer.GetParameter(index); - } - - public void CrossFadeInFixedTime(string stateName, float transitionDuration, int layer = -1, float fixedTime = 0f) - { - playablePointer.CrossFadeInFixedTime(stateName, transitionDuration, layer, fixedTime); - } - public void CrossFadeInFixedTime(int stateNameHash, float transitionDuration, int layer = -1, float fixedTime = 0.0f) - { - playablePointer.CrossFadeInFixedTime(stateNameHash, transitionDuration, layer, fixedTime); - } - public void CrossFade(string stateName, float transitionDuration, int layer = -1, float normalizedTime = float.NegativeInfinity) - { - playablePointer.CrossFade(stateName, transitionDuration, layer, normalizedTime); - } - - public void CrossFade(int stateNameHash, float transitionDuration, int layer = -1, float normalizedTime = float.NegativeInfinity) - { - playablePointer.CrossFade(stateNameHash, transitionDuration, layer, normalizedTime); - } - public void PlayInFixedTime(string stateName, int layer = -1, float fixedTime = float.NegativeInfinity) - { - playablePointer.PlayInFixedTime(stateName, layer, fixedTime); - } - - public void PlayInFixedTime(int stateNameHash, int layer = -1, float fixedTime = float.NegativeInfinity) - { - playablePointer.PlayInFixedTime(stateNameHash, layer, fixedTime); - } - - public void Play(string stateName, int layer = -1, float normalizedTime = float.NegativeInfinity) - { - playablePointer.Play(stateName, layer, normalizedTime); - } - - - public void Play(int stateNameHash, int layer = -1, float normalizedTime = float.NegativeInfinity) - { - playablePointer.Play(stateNameHash, layer, normalizedTime); - } - - public bool HasState(int layerIndex, int stateID) - { - return playablePointer.HasState(layerIndex, stateID); + return CurrentAnimatorController == runtimeAnimatorController; } - #endregion + #endregion Public API } } diff --git a/Runtime/GamePlay/Animations/AnimationSequenceBuilder.cs b/Runtime/GamePlay/Animations/AnimationSequenceBuilder.cs deleted file mode 100644 index adb1da6..0000000 --- a/Runtime/GamePlay/Animations/AnimationSequenceBuilder.cs +++ /dev/null @@ -1,361 +0,0 @@ -using System; -using System.Collections.Generic; -using Kurisu.Framework.Tasks; -using UnityEngine; -using UnityEngine.Animations; -using UnityEngine.Playables; -using UnityEngine.Pool; -namespace Kurisu.Framework.Animations -{ - /// - /// Builder for creating sequence on animation clips, using UnityEngine.Playables API. - /// - /// - /// Useful to perform dynamic animation for character without . - /// - public struct AnimationSequenceBuilder : IDisposable - { - private readonly PlayableGraph playableGraph; - private AnimationPlayableOutput playableOutput; - // TODO: Use complete event to create a link list instead of using an additional containers? - private List taskBuffer; - private Playable rootMixer; - private Playable mixerPointer; - private SequenceTask sequence; - private float fadeOutTime; - private bool isDisposed; - public AnimationSequenceBuilder(Animator animator) - { - playableGraph = PlayableGraph.Create($"{animator.name}_AnimationSequence_{animator.GetHashCode()}"); - playableOutput = AnimationPlayableOutput.Create(playableGraph, nameof(AnimationSequenceBuilder), animator); - mixerPointer = rootMixer = AnimationMixerPlayable.Create(playableGraph, 2); - playableOutput.SetSourcePlayable(mixerPointer); - fadeOutTime = 0f; - sequence = null; - isDisposed = false; - taskBuffer = ListPool.Get(); - } - /// - /// Append an animation clip - /// - /// Clip to play - /// FadeIn time - /// - public AnimationSequenceBuilder Append(AnimationClip animationClip, float fadeIn) - { - return Append(animationClip, animationClip.length, fadeIn); - } - /// - /// Append an animation clip - /// - /// Clip to play - /// Duration can be infinity as loop - /// FadeIn time - /// - public AnimationSequenceBuilder Append(AnimationClip animationClip, float duration, float fadeIn) - { - if (IsBuilt()) return this; - if (!IsValid()) return this; - var clipPlayable = AnimationClipPlayable.Create(playableGraph, animationClip); - clipPlayable.SetDuration(duration); - clipPlayable.SetSpeed(0d); - return AppendInternal(clipPlayable, fadeIn); - } - private AnimationSequenceBuilder AppendInternal(Playable clipPlayable, float fadeIn) - { - if (mixerPointer.GetInput(1).IsNull()) - { - playableGraph.Connect(clipPlayable, 0, mixerPointer, 1); - } - else - { - // Layout as a binary tree - var newMixer = AnimationMixerPlayable.Create(playableGraph, 2); - var right = mixerPointer.GetInput(1); - taskBuffer.Add(WaitPlayableTask.GetPooled(right, right.GetDuration() - fadeIn)); - //Disconnect leaf - playableGraph.Disconnect(mixerPointer, 1); - //Right=>left - playableGraph.Connect(right, 0, newMixer, 0); - //New right leaf - playableGraph.Connect(clipPlayable, 0, newMixer, 1); - //Connect to parent - playableGraph.Connect(newMixer, 0, mixerPointer, 1); - //Update pointer - mixerPointer = newMixer; - } - mixerPointer.SetInputWeight(0, 1); - mixerPointer.SetInputWeight(1, 0); - taskBuffer.Add(FadeInPlayableTask.GetPooled(mixerPointer, clipPlayable, fadeIn)); - return this; - } - /// - /// Set last playable duration - /// - /// - /// - public readonly AnimationSequenceBuilder SetDuration(double duration) - { - mixerPointer.GetInput(1).SetDuration(duration); - return this; - } - /// - /// Build an animation sequence - /// - public SequenceTask Build() - { - if (IsBuilt()) - { - Debug.LogWarning("Graph is already built, rebuild is not allowed"); - return sequence; - } - if (!IsValid()) - { - Debug.LogWarning("Graph is already destroyed before build"); - return sequence; - } - return BuildInternal(SequenceTask.GetPooled(Dispose)); - } - /// - /// Append animation sequence after an existed sequence - /// - /// - public void Build(SequenceTask sequenceTask) - { - if (IsBuilt()) - { - Debug.LogWarning("Graph is already built, rebuild is not allowed"); - return; - } - if (!IsValid()) - { - Debug.LogWarning("Graph is already destroyed before build"); - return; - } - BuildInternal(sequenceTask); - sequenceTask.AppendCallBack(Dispose); - } - private SequenceTask BuildInternal(SequenceTask sequenceTask) - { - if (!playableGraph.IsPlaying()) - { - playableGraph.SetTimeUpdateMode(DirectorUpdateMode.GameTime); - playableGraph.Play(); - } - foreach (var task in taskBuffer) - sequenceTask.Append(task); - var right = (AnimationClipPlayable)mixerPointer.GetInput(1); - sequenceTask.Append(WaitPlayableTask.GetPooled(right, right.GetAnimationClip().length - fadeOutTime)); - if (fadeOutTime > 0) - { - sequenceTask.Append(FadeOutPlayableTask.GetPooled(rootMixer, right, fadeOutTime)); - } - sequence = sequenceTask; - taskBuffer.Clear(); - sequence.Acquire(); - return sequence; - } - /// - /// Build an animation sequence to force fade out the playable, useful when your clip is loop and you want to crossfade it - /// - /// - /// - public readonly SequenceTask BuildFadeOut(Action callBack) - { - return SequenceTask.GetPooled(AbsolutelyFadeOutPlayableTask.GetPooled(rootMixer, fadeOutTime), callBack); - } - /// - /// Set animation sequence fadeOut time, default is 0 - /// - /// - /// - public AnimationSequenceBuilder SetFadeOut(float fadeOut) - { - if (IsBuilt()) return this; - fadeOutTime = fadeOut; - return this; - } - /// - /// Whether animation sequence is already built - /// - /// - public readonly bool IsBuilt() - { - return sequence != null; - } - /// - /// Whether animation sequence is valid - /// - /// - public readonly bool IsValid() - { - return playableGraph.IsValid(); - } - /// - /// Dispose internal playable graph - /// - public void Dispose() - { - if (isDisposed) - { - return; - } - isDisposed = true; - sequence?.Dispose(); - sequence = null; - if (playableGraph.IsValid()) - { - playableGraph.Destroy(); - } - ListPool.Release(taskBuffer); - taskBuffer = null; - } - #region Playable Tasks - /// - /// A task to fade in playable according to playable's duration - /// - private class FadeInPlayableTask : PooledTaskBase - { - private Playable clipPlayable; - private Playable mixerPlayable; - private float fadeInTime; - public static FadeInPlayableTask GetPooled(Playable mixerPlayable, Playable clipPlayable, float fadeInTime) - { - var task = GetPooled(); - task.clipPlayable = clipPlayable; - task.mixerPlayable = mixerPlayable; - task.fadeInTime = fadeInTime; - return task; - } - public override void Tick() - { - if (!mixerPlayable.IsValid()) - { - Debug.LogWarning("Playable is already destroyed"); - mStatus = TaskStatus.Completed; - return; - } - clipPlayable.SetSpeed(1d); - double current = clipPlayable.GetTime(); - if (current >= fadeInTime) - { - mixerPlayable.SetInputWeight(0, 0); - mixerPlayable.SetInputWeight(1, 1); - mStatus = TaskStatus.Completed; - } - else - { - float weight = (float)(current / fadeInTime); - mixerPlayable.SetInputWeight(0, Mathf.Lerp(1, 0, weight)); - mixerPlayable.SetInputWeight(1, Mathf.Lerp(0, 1, weight)); - } - } - } - /// - /// A task to fade out playable according to playable's duration - /// - private class FadeOutPlayableTask : PooledTaskBase - { - private Playable clipPlayable; - private Playable mixerPlayable; - private float fadeOutTime; - private double duration; - public static FadeOutPlayableTask GetPooled(Playable mixerPlayable, Playable clipPlayable, float fadeOutTime) - { - var task = GetPooled(); - task.clipPlayable = clipPlayable; - task.mixerPlayable = mixerPlayable; - task.fadeOutTime = fadeOutTime; - task.duration = clipPlayable.GetDuration(); - return task; - } - public override void Tick() - { - if (!mixerPlayable.IsValid()) - { - Debug.LogWarning("Playable is already destroyed"); - mStatus = TaskStatus.Completed; - return; - } - double current = clipPlayable.GetTime(); - if (current >= duration) - { - mixerPlayable.SetInputWeight(0, 1); - mixerPlayable.SetInputWeight(1, 0); - mStatus = TaskStatus.Completed; - } - else - { - float weight = 1 - (float)((duration - current) / fadeOutTime); - mixerPlayable.SetInputWeight(0, Mathf.Lerp(0, 1, weight)); - mixerPlayable.SetInputWeight(1, Mathf.Lerp(1, 0, weight)); - } - } - } - /// - /// A task to fade out playable according to fadeOutTime - /// - private class AbsolutelyFadeOutPlayableTask : PooledTaskBase - { - private Playable mixerPlayable; - private float fadeOutTime; - private float timer; - public static AbsolutelyFadeOutPlayableTask GetPooled(Playable mixerPlayable, float fadeOutTime) - { - var task = GetPooled(); - task.mixerPlayable = mixerPlayable; - task.fadeOutTime = fadeOutTime; - task.timer = 0; - return task; - } - public override void Tick() - { - if (!mixerPlayable.IsValid()) - { - Debug.LogWarning("Playable is already destroyed"); - mStatus = TaskStatus.Completed; - return; - } - timer += Time.deltaTime; - if (timer >= fadeOutTime) - { - mixerPlayable.SetInputWeight(0, 1); - mixerPlayable.SetInputWeight(1, 0); - mStatus = TaskStatus.Completed; - } - else - { - float weight = timer / fadeOutTime; - mixerPlayable.SetInputWeight(0, Mathf.Lerp(0, 1, weight)); - mixerPlayable.SetInputWeight(1, Mathf.Lerp(1, 0, weight)); - } - } - } - private class WaitPlayableTask : PooledTaskBase - { - private Playable clipPlayable; - private double waitTime; - public static WaitPlayableTask GetPooled(Playable clipPlayable, double waitTime) - { - var task = GetPooled(); - task.clipPlayable = clipPlayable; - task.waitTime = waitTime; - return task; - } - public override void Tick() - { - if (!clipPlayable.IsValid()) - { - Debug.LogWarning("Playable is already destroyed"); - mStatus = TaskStatus.Completed; - return; - } - if (clipPlayable.GetTime() >= waitTime) - { - mStatus = TaskStatus.Completed; - } - } - } - #endregion - } -} From 4b252468653d67e708ee9996e1d85eeb489874ac Mon Sep 17 00:00:00 2001 From: AkiKurisu <2683987717@qq.com> Date: Sat, 19 Oct 2024 16:31:31 +0800 Subject: [PATCH 7/8] Update doc --- Docs/Animations.md | 50 +++++++++++++++++++ Docs/Animations.md.meta | 7 +++ Docs/{Scheduler.md => Schedulers.md} | 2 +- .../{Scheduler.md.meta => Schedulers.md.meta} | 0 README.md | 5 +- .../AnimationProxy.AnimationSequence.cs | 3 +- 6 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 Docs/Animations.md create mode 100644 Docs/Animations.md.meta rename Docs/{Scheduler.md => Schedulers.md} (99%) rename Docs/{Scheduler.md.meta => Schedulers.md.meta} (100%) diff --git a/Docs/Animations.md b/Docs/Animations.md new file mode 100644 index 0000000..ca34491 --- /dev/null +++ b/Docs/Animations.md @@ -0,0 +1,50 @@ +# Animations + +Create dynamic animation sequence and cutscene from script based on Playables. + +## Features + +- Crossfade multi `RuntimeAnimatorController` and `AnimationClip`. + +## AnimationProxy Example + +```C# +public class MontageExample : MonoBehaviour +{ + public Animator animator; + private AnimationProxy animationProxy; + public RuntimeAnimatorController controllerA; + public RuntimeAnimatorController controllerB; + private IEnumerator Start() + { + animationProxy = new AnimationProxy(animator); + animationProxy.LoadAnimator(controllerA, 0.5f); /* Crossfade animator to controllerA in 0.5s */ + yield return new WaitForSeconds(1f); + animationProxy.LoadAnimator(controllerB, 0.5f); /* Crossfade controllerA to controllerB in 0.5s */ + yield return new WaitForSeconds(1f); + chaAnimationProxy.Stop(0.5f); /* Crossfade controllerB to animator in 0.5s */ + } +} +``` + +## SequenceBuilder Example + +```C# +public class SequenceExample : MonoBehaviour +{ + public Animator animator; + private AnimationProxy animationProxy; + public AnimationClip[] clips; + private void Start() + { + animationProxy = new AnimationProxy(animator); + using var builder = chaAnimationProxy.CreateSequenceBuilder(); + foreach (var clip in clips) + { + builder.Append(clip, clip.length * 3 /* Play 3 loop */, 0.25f /* BlendIn duration */); + } + builder.SetBlendOut(0.5f); + builder.Build().Run(); + } +} +``` \ No newline at end of file diff --git a/Docs/Animations.md.meta b/Docs/Animations.md.meta new file mode 100644 index 0000000..01afaea --- /dev/null +++ b/Docs/Animations.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 791d117e464c65540b355de886d144ae +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Docs/Scheduler.md b/Docs/Schedulers.md similarity index 99% rename from Docs/Scheduler.md rename to Docs/Schedulers.md index 2f443f7..fb475ef 100644 --- a/Docs/Scheduler.md +++ b/Docs/Schedulers.md @@ -1,4 +1,4 @@ -# Scheduler +# Schedulers Zero allocation timer/frame counter. diff --git a/Docs/Scheduler.md.meta b/Docs/Schedulers.md.meta similarity index 100% rename from Docs/Scheduler.md.meta rename to Docs/Schedulers.md.meta diff --git a/README.md b/README.md index db6118c..c8c41db 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ I supposed these code are useful but also not-must-be-included, which is my pers [Pool](./Docs/Pool.md) > Zero allocation GameObject/Component pooling. -[Scheduler](./Docs/Scheduler.md) +[Schedulers](./Docs/Schedulers.md) > Zero allocation timer/frame counter. [Serialization](./Docs/Serialization.md) @@ -25,6 +25,9 @@ I supposed these code are useful but also not-must-be-included, which is my pers [Data Driven](./Docs/DataDriven.md) >Use Unreal-like DataTable workflow in Unity. +[Animations](./Docs/Animations.md) +>Create dynamic animation sequence and cutscene from script based on Playables. + ## Modules Modules are based on core features. diff --git a/Runtime/GamePlay/Animations/AnimationProxy.AnimationSequence.cs b/Runtime/GamePlay/Animations/AnimationProxy.AnimationSequence.cs index 8a5dc66..f072265 100644 --- a/Runtime/GamePlay/Animations/AnimationProxy.AnimationSequence.cs +++ b/Runtime/GamePlay/Animations/AnimationProxy.AnimationSequence.cs @@ -105,7 +105,7 @@ public SequenceTask Build() Debug.LogWarning("Builder is invalid, rebuild is not allowed"); return sequence; } - return BuildInternal(SequenceTask.GetPooled(Dispose)); + return BuildInternal(SequenceTask.GetPooled()); } /// /// Append animation sequence after an existed sequence @@ -119,7 +119,6 @@ public void Build(SequenceTask sequenceTask) return; } BuildInternal(sequenceTask); - sequenceTask.AppendCallBack(Dispose); } private SequenceTask BuildInternal(SequenceTask sequenceTask) { From e7ff12cd472eea552de243a704a781d82b24a9ed Mon Sep 17 00:00:00 2001 From: AkiKurisu <2683987717@qq.com> Date: Sat, 19 Oct 2024 16:32:00 +0800 Subject: [PATCH 8/8] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 89de0d4..7935096 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "com.kurisu.akiframework", "displayName": "AkiFramework", - "version": "1.1.0", + "version": "1.1.1", "unity": "2021.3", "description": "Personal game framework and code collection.", "keywords": [