From 3151c18b6c8f76a8d10b1ba217cf46424043bd3d Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Fri, 27 Nov 2020 14:00:57 +0100 Subject: [PATCH] [unity] `SkeletonRenderer` components now provide an additional update mode `Only Event Timelines` at the `Update When Invisible` property. Added methods to AnimationState for proper behaviour during transitions. Closes #1815. --- CHANGELOG.md | 1 + spine-csharp/src/AnimationState.cs | 74 +++++++++++++++++++ .../Components/SkeletonAnimation.cs | 5 +- .../spine-unity/Components/SkeletonGraphic.cs | 9 ++- .../Components/SkeletonRenderer.cs | 4 +- .../Runtime/spine-unity/ISkeletonAnimation.cs | 6 +- 6 files changed, 91 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d11382035..7605c991b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -247,6 +247,7 @@ * Added `BoundingBoxFollowerGraphic` component. This class is a counterpart of `BoundingBoxFollower` that can be used with `SkeletonGraphic`. * Added Inspector context menu functions `SkeletonRenderer - Add all BoundingBoxFollower GameObjects` and `SkeletonGraphic - Add all BoundingBoxFollowerGraphic GameObjects` that automatically generate bounding box follower GameObjects for every `BoundingBoxAttachment` for all skins of a skeleton. * `GetRemappedClone()` now provides an additional parameter `pivotShiftsMeshUVCoords` for `MeshAttachment` to prevent uv shifts at a non-central Sprite pivot. This parameter defaults to `true` to maintain previous behaviour. + * `SkeletonRenderer` components now provide an additional update mode `Only Event Timelines` at the `Update When Invisible` property. This mode saves additional timeline updates compared to update mode `Everything Except Mesh`. * **Changes of default values** * `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`. diff --git a/spine-csharp/src/AnimationState.cs b/spine-csharp/src/AnimationState.cs index e1c132a11..2ded456e1 100644 --- a/spine-csharp/src/AnimationState.cs +++ b/spine-csharp/src/AnimationState.cs @@ -300,6 +300,43 @@ namespace Spine { return applied; } + /// Version of only applying EventTimelines for lightweight off-screen updates. + // Note: This method is not part of the libgdx reference implementation. + public bool ApplyEventTimelinesOnly (Skeleton skeleton) { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + + var events = this.events; + bool applied = false; + var tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) { + TrackEntry current = tracksItems[i]; + if (current == null || current.delay > 0) continue; + applied = true; + + // Apply mixing from entries first. + if (current.mixingFrom != null) + ApplyMixingFromEventTimelinesOnly(current, skeleton); + + // Apply current entry. + float animationLast = current.animationLast, animationTime = current.AnimationTime; + int timelineCount = current.animation.timelines.Count; + var timelines = current.animation.timelines; + var timelinesItems = timelines.Items; + for (int ii = 0; ii < timelineCount; ii++) { + Timeline timeline = timelinesItems[ii]; + if (timeline is EventTimeline) + timeline.Apply(skeleton, animationLast, animationTime, events, 1.0f, MixBlend.Setup, MixDirection.In); + } + QueueEvents(current, animationTime); + events.Clear(false); + current.nextAnimationLast = animationTime; + current.nextTrackLast = current.trackTime; + } + + queue.Drain(); + return applied; + } + private float ApplyMixingFrom (TrackEntry to, Skeleton skeleton, MixBlend blend) { TrackEntry from = to.mixingFrom; if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton, blend); @@ -386,6 +423,43 @@ namespace Spine { return mix; } + /// Version of only applying EventTimelines for lightweight off-screen updates. + // Note: This method is not part of the libgdx reference implementation. + private float ApplyMixingFromEventTimelinesOnly (TrackEntry to, Skeleton skeleton) { + TrackEntry from = to.mixingFrom; + if (from.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(from, skeleton); + + float mix; + if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes. + mix = 1; + } + else { + mix = to.mixTime / to.mixDuration; + if (mix > 1) mix = 1; + } + + var eventBuffer = mix < from.eventThreshold ? this.events : null; + if (eventBuffer == null) + return mix; + + float animationLast = from.animationLast, animationTime = from.AnimationTime; + var timelines = from.animation.timelines; + int timelineCount = timelines.Count; + var timelinesItems = timelines.Items; + for (int i = 0; i < timelineCount; i++) { + var timeline = timelinesItems[i]; + if (timeline is EventTimeline) + timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, 0, MixBlend.Setup, MixDirection.Out); + } + + if (to.mixDuration > 0) QueueEvents(from, animationTime); + this.events.Clear(false); + from.nextAnimationLast = animationTime; + from.nextTrackLast = from.trackTime; + + return mix; + } + /// Applies the attachment timeline and sets . /// False when: 1) the attachment timeline is mixing out, 2) mix < attachmentThreshold, and 3) the timeline /// is not the last timeline to set the slot's attachment. In that case the timeline is applied only so subsequent diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs index 514eaa341..cfc5ddf29 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs @@ -213,7 +213,10 @@ namespace Spine.Unity { if (_BeforeApply != null) _BeforeApply(this); - state.Apply(skeleton); + if (updateMode != UpdateMode.OnlyEventTimelines) + state.Apply(skeleton); + else + state.ApplyEventTimelinesOnly(skeleton); if (_UpdateLocal != null) _UpdateLocal(this); diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs index 429266bf1..457ba3124 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs @@ -62,7 +62,7 @@ namespace Spine.Unity { /// Update mode to optionally limit updates to e.g. only apply animations but not update the mesh. public UpdateMode UpdateMode { get { return updateMode; } set { updateMode = value; } } - [SerializeField] protected UpdateMode updateMode = UpdateMode.FullUpdate; + protected UpdateMode updateMode = UpdateMode.FullUpdate; /// Update mode used when the MeshRenderer becomes invisible /// (when OnBecameInvisible() is called). Update mode is automatically @@ -263,7 +263,10 @@ namespace Spine.Unity { if (BeforeApply != null) BeforeApply(this); - state.Apply(skeleton); + if (updateMode != UpdateMode.OnlyEventTimelines) + state.Apply(skeleton); + else + state.ApplyEventTimelinesOnly(skeleton); if (UpdateLocal != null) UpdateLocal(this); @@ -283,7 +286,7 @@ namespace Spine.Unity { // instantiation can happen from Update() after this component, leading to a missing Update() call. if (!wasUpdatedAfterInit) Update(0); if (freeze) return; - if (updateMode <= UpdateMode.EverythingExceptMesh) return; + if (updateMode != UpdateMode.FullUpdate) return; UpdateMesh(); } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs index 519bf334f..2fea2f7d4 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs @@ -82,7 +82,7 @@ namespace Spine.Unity { /// Update mode to optionally limit updates to e.g. only apply animations but not update the mesh. public UpdateMode UpdateMode { get { return updateMode; } set { updateMode = value; } } - [SerializeField] protected UpdateMode updateMode = UpdateMode.FullUpdate; + protected UpdateMode updateMode = UpdateMode.FullUpdate; /// Update mode used when the MeshRenderer becomes invisible /// (when OnBecameInvisible() is called). Update mode is automatically @@ -381,7 +381,7 @@ namespace Spine.Unity { } #endif - if (updateMode <= UpdateMode.EverythingExceptMesh) return; + if (updateMode != UpdateMode.FullUpdate) return; #if SPINE_OPTIONAL_RENDEROVERRIDE bool doMeshOverride = generateMeshOverride != null; diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/ISkeletonAnimation.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/ISkeletonAnimation.cs index 36e300ec9..90486e381 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/ISkeletonAnimation.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/ISkeletonAnimation.cs @@ -31,8 +31,10 @@ namespace Spine.Unity { public enum UpdateMode { Nothing = 0, OnlyAnimationStatus, - EverythingExceptMesh, - FullUpdate + OnlyEventTimelines = 4, // added as index 4 to keep scene behavior unchanged. + EverythingExceptMesh = 2, + FullUpdate, + //Reserved 4 for OnlyEventTimelines }; public delegate void UpdateBonesDelegate (ISkeletonAnimation animated);