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-csharp/src/Atlas.cs b/spine-csharp/src/Atlas.cs index 3ef27c5d1..aeaf1050a 100644 --- a/spine-csharp/src/Atlas.cs +++ b/spine-csharp/src/Atlas.cs @@ -58,6 +58,9 @@ namespace Spine { } #endregion + public List Regions { get { return regions; } } + public List Pages { get { return pages; } } + #if !(IS_UNITY) #if WINDOWS_STOREAPP private async Task ReadFile(string path, TextureLoader textureLoader) { 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); diff --git a/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/Include/Spine-Sprite-ForwardPass-URP.hlsl b/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/Include/Spine-Sprite-ForwardPass-URP.hlsl index d4f7ccd7a..452389590 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/Include/Spine-Sprite-ForwardPass-URP.hlsl +++ b/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/Include/Spine-Sprite-ForwardPass-URP.hlsl @@ -5,6 +5,10 @@ #include "SpineCoreShaders/SpriteLighting.cginc" +#if defined(_RIM_LIGHTING) || defined(_ADDITIONAL_LIGHTS) || defined(MAIN_LIGHT_CALCULATE_SHADOWS) + #define NEEDS_POSITION_WS +#endif + //////////////////////////////////////// // Vertex output struct // @@ -26,10 +30,10 @@ struct VertexOutputLWRP #else half3 normalWorld : TEXCOORD4; #endif -#if defined(_MAIN_LIGHT_SHADOWS) && !defined(_RECEIVE_SHADOWS_OFF) +#if (defined(_MAIN_LIGHT_SHADOWS) || defined(MAIN_LIGHT_CALCULATE_SHADOWS)) && !defined(_RECEIVE_SHADOWS_OFF) float4 shadowCoord : TEXCOORD7; #endif -#if defined(_RIM_LIGHTING) || defined(_ADDITIONAL_LIGHTS) +#if defined(NEEDS_POSITION_WS) float4 positionWS : TEXCOORD8; #endif UNITY_VERTEX_OUTPUT_STEREO @@ -80,7 +84,7 @@ half4 LightweightFragmentPBRSimplified(InputData inputData, half4 texAlbedoAlpha brdfData.specular *= albedo.a; #ifndef _MAIN_LIGHT_VERTEX -#if defined(_MAIN_LIGHT_SHADOWS) && !defined(_RECEIVE_SHADOWS_OFF) +#if (defined(_MAIN_LIGHT_SHADOWS) || defined(MAIN_LIGHT_CALCULATE_SHADOWS)) && !defined(_RECEIVE_SHADOWS_OFF) Light mainLight = GetMainLight(inputData.shadowCoord); #else Light mainLight = GetMainLight(); @@ -115,7 +119,7 @@ half4 LightweightFragmentBlinnPhongSimplified(InputData inputData, half4 texDiff half4 diffuse = texDiffuseAlpha * vertexColor; #ifndef _MAIN_LIGHT_VERTEX -#if defined(_MAIN_LIGHT_SHADOWS) && !defined(_RECEIVE_SHADOWS_OFF) +#if (defined(_MAIN_LIGHT_SHADOWS) || defined(MAIN_LIGHT_CALCULATE_SHADOWS)) && !defined(_RECEIVE_SHADOWS_OFF) Light mainLight = GetMainLight(inputData.shadowCoord); #else Light mainLight = GetMainLight(); @@ -170,12 +174,12 @@ VertexOutputLWRP ForwardPassVertexSprite(VertexInput input) backFaceSign = calculateBackfacingSign(positionWS.xyz); #endif output.viewDirectionWS = GetCameraPositionWS() - positionWS; +#if defined(NEEDS_POSITION_WS) + output.positionWS = float4(positionWS, 1); +#endif #if defined(PER_PIXEL_LIGHTING) -#if defined(_RIM_LIGHTING) || defined(_ADDITIONAL_LIGHTS) - output.positionWS = float4(positionWS, 1); -#endif half3 normalWS = calculateSpriteWorldNormal(input, -backFaceSign); output.normalWorld.xyz = normalWS; @@ -191,7 +195,8 @@ VertexOutputLWRP ForwardPassVertexSprite(VertexInput input) #endif // !PER_PIXEL_LIGHTING output.fogFactorAndVertexLight.yzw = LightweightLightVertexSimplified(positionWS, normalWS); -#if defined(_MAIN_LIGHT_SHADOWS) && !defined(_RECEIVE_SHADOWS_OFF) + +#if (defined(_MAIN_LIGHT_SHADOWS) || defined(MAIN_LIGHT_CALCULATE_SHADOWS)) && !defined(_RECEIVE_SHADOWS_OFF) VertexPositionInputs vertexInput; vertexInput.positionWS = positionWS; vertexInput.positionCS = output.pos; @@ -216,8 +221,16 @@ half4 ForwardPassFragmentSprite(VertexOutputLWRP input) : SV_Target // fill out InputData struct InputData inputData; -#if defined(_MAIN_LIGHT_SHADOWS) && !defined(_RECEIVE_SHADOWS_OFF) - inputData.shadowCoord = input.shadowCoord; +#if !defined(_RECEIVE_SHADOWS_OFF) + #if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR) + inputData.shadowCoord = input.shadowCoord; + #elif defined(MAIN_LIGHT_CALCULATE_SHADOWS) + inputData.shadowCoord = TransformWorldToShadowCoord(input.positionWS); + #elif defined(_MAIN_LIGHT_SHADOWS) + inputData.shadowCoord = input.shadowCoord; + #else + inputData.shadowCoord = float4(0, 0, 0, 0); + #endif #endif inputData.viewDirectionWS = input.viewDirectionWS; diff --git a/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/Spine-Sprite-URP.shader b/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/Spine-Sprite-URP.shader index 41c3f7aeb..f40e5098b 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/Spine-Sprite-URP.shader +++ b/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/Spine-Sprite-URP.shader @@ -100,6 +100,8 @@ Shader "Universal Render Pipeline/Spine/Sprite" // ------------------------------------- // Universal Pipeline keywords #pragma multi_compile _ _MAIN_LIGHT_SHADOWS + #pragma multi_compile _ MAIN_LIGHT_CALCULATE_SHADOWS + #pragma multi_compile _ REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE #pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS #pragma multi_compile _ _ADDITIONAL_LIGHT_SHADOWS