Files
2026-07-10 10:24:29 +08:00

536 lines
19 KiB
C#

/******************************************************************************
* Spine Runtimes License Agreement
* Last updated September 24, 2021. Replaces all prior versions.
*
* Copyright (c) 2013-2021, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
#define NEW_PREFAB_SYSTEM
#endif
#if UNITY_2018_1_OR_NEWER
#define PER_MATERIAL_PROPERTY_BLOCKS
#endif
#if UNITY_2019_3_OR_NEWER
#define CONFIGURABLE_ENTER_PLAY_MODE
#endif
#if !SPINE_DISABLE_THREADING
#define USE_THREADED_ANIMATION_UPDATE
#endif
#if !SPINE_AUTO_UPGRADE_COMPONENTS_OFF
#define AUTO_UPGRADE_TO_43_COMPONENTS
#endif
using System.Collections.Generic;
using UnityEngine;
namespace Spine.Unity {
#if NEW_PREFAB_SYSTEM
[ExecuteAlways]
#else
[ExecuteInEditMode]
#endif
[System.Serializable]
public abstract class SkeletonAnimationBase : MonoBehaviour,
ISkeletonAnimation, ISkeletonComponent, IHasSkeletonRenderer,
ISkeletonRendererEvents, IHasModifyableSkeletonDataAsset, IUpgradable {
[SerializeField] protected UpdateTiming updateTiming = UpdateTiming.InUpdate;
protected int frameOfLastUpdate = -1;
protected ISkeletonRenderer skeletonRenderer;
protected bool skipUpdate = false;
public UpdateTiming UpdateTiming {
get { return updateTiming; }
set {
if (updateTiming == value) return;
#if USE_THREADED_ANIMATION_UPDATE
if (Application.isPlaying && UsesThreadedAnimation && isUpdatedExternally) {
SkeletonUpdateSystem system = SkeletonUpdateSystem.Instance;
if (system) {
// unregister from old update timing mode, register for new one.
system.UnregisterFromUpdate(updateTiming, this);
system.RegisterForUpdate(value, this);
}
}
#endif
updateTiming = value;
}
}
#if UNITY_EDITOR
protected bool requiresEditorUpdate = false;
#endif
#if USE_THREADED_ANIMATION_UPDATE
#region Threaded update system
protected static int mainThreadID = -1;
protected static float externalDeltaTime = 0f;
protected static float unscaledDeltaTime = 0f;
[SerializeField] protected SettingsTriState threadedAnimation = SettingsTriState.UseGlobalSetting;
protected bool isUpdatedExternally = false;
public static float ExternalDeltaTime {
get { return externalDeltaTime; }
set { externalDeltaTime = value; }
}
public static float ExternalUnscaledDeltaTime {
get { return unscaledDeltaTime; }
set { unscaledDeltaTime = value; }
}
public virtual float UsedExternalDeltaTime { get { return ExternalDeltaTime; } }
public bool IsUpdatedExternally {
get { return isUpdatedExternally; }
set { isUpdatedExternally = value; }
}
protected bool UsesThreadedAnimation {
get { return threadedAnimation == SettingsTriState.Enable || RuntimeSettings.UseThreadedAnimation; }
}
public SettingsTriState ThreadedAnimation {
get { return threadedAnimation; }
set {
if (threadedAnimation == value) return;
threadedAnimation = value;
SkeletonUpdateSystem system = SkeletonUpdateSystem.Instance;
if (system) {
if (threadedAnimation == SettingsTriState.Enable || RuntimeSettings.UseThreadedAnimation)
system.RegisterForUpdate(updateTiming, this);
else
system.UnregisterFromUpdate(updateTiming, this);
}
}
}
#endregion
#if UNITY_EDITOR
static bool applicationIsPlaying = false;
// ApplicationIsPlaying for threaded access. Unfortunately Application.isPlaying throws
// when called from worker thread.
public static bool ApplicationIsPlaying {
get { return applicationIsPlaying; }
set { applicationIsPlaying = value; }
}
#endif
#else
protected bool UseThreading {
get { return false; }
set { }
}
#if UNITY_EDITOR
public static bool ApplicationIsPlaying {
get { return Application.isPlaying; }
set { }
}
#endif
#endif
#region Threading Asserts
[System.Diagnostics.Conditional("UNITY_EDITOR")]
protected void InitializeMainThreadID () {
#if USE_THREADED_ANIMATION_UPDATE
if (mainThreadID == -1)
mainThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId;
#endif
}
[System.Diagnostics.Conditional("UNITY_EDITOR")]
private void AssertIsMainThread () {
#if USE_THREADED_ANIMATION_UPDATE
if (System.Threading.Thread.CurrentThread.ManagedThreadId != mainThreadID)
Debug.LogError("AssertIsMainThread failed: worker thread calling main thread code. Thread ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
#endif
}
[System.Diagnostics.Conditional("UNITY_EDITOR")]
private void AssertIsWorkerThread () {
#if USE_THREADED_ANIMATION_UPDATE
if (System.Threading.Thread.CurrentThread.ManagedThreadId == mainThreadID)
Debug.LogError("AssertIsWorkerThread failed: main thread calling worker thread code! Thread ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
#endif
}
#endregion
#region Interface Implementation
public UnityEngine.MonoBehaviour Component { get { return this; } }
public ISkeletonRenderer Renderer {
get {
if (skeletonRenderer == null) {
skeletonRenderer = this.GetComponent<ISkeletonRenderer>();
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
if (skeletonRenderer == null) {
UpgradeTo43();
skeletonRenderer = this.GetComponent<ISkeletonRenderer>();
}
#endif
}
return skeletonRenderer;
}
}
public Skeleton Skeleton {
get { return Renderer.Skeleton; }
set { Renderer.Skeleton = value; }
}
public Skeleton skeleton {
get { return Renderer.Skeleton; }
set { Renderer.Skeleton = value; }
}
public SkeletonDataAsset SkeletonDataAsset {
get { return Renderer.SkeletonDataAsset; }
set { Renderer.SkeletonDataAsset = value; }
}
public SkeletonDataAsset skeletonDataAsset {
get { return Renderer.SkeletonDataAsset; }
set { Renderer.SkeletonDataAsset = value; }
}
protected event SkeletonAnimationDelegate _BeforeUpdate, _BeforeApply, _OnAnimationRebuild;
/// <summary>OnAnimationRebuild is raised after the SkeletonAnimation component is successfully initialized.</summary>
public event SkeletonAnimationDelegate OnAnimationRebuild { add { _OnAnimationRebuild += value; } remove { _OnAnimationRebuild -= value; } }
/// <summary>
/// Occurs before the animation state is updated.
/// Use this callback when you want to change the skeleton state before animation state is updated.
/// </summary>
public event SkeletonAnimationDelegate BeforeUpdate { add { _BeforeUpdate += value; } remove { _BeforeUpdate -= value; } }
/// <summary>
/// Occurs before the animations are applied.
/// Use this callback when you want to change the skeleton state before animations are applied on top.
/// </summary>
public event SkeletonAnimationDelegate BeforeApply { add { _BeforeApply += value; } remove { _BeforeApply -= value; } }
/// <summary>A compatibility wrapper for <see cref="SkeletonRenderer.UpdateLocal"/></summary>
public event SkeletonRendererDelegate UpdateLocal { add { Renderer.UpdateLocal += value; } remove { Renderer.UpdateLocal -= value; } }
/// <summary>A compatibility wrapper for <see cref="Renderer.UpdateWorld"/></summary>
public event SkeletonRendererDelegate UpdateWorld { add { Renderer.UpdateWorld += value; } remove { Renderer.UpdateWorld -= value; } }
/// <summary>A compatibility wrapper for <see cref="Renderer.UpdateComplete"/></summary>
public event SkeletonRendererDelegate UpdateComplete { add { Renderer.UpdateComplete += value; } remove { Renderer.UpdateComplete -= value; } }
/// <summary>A compatibility wrapper for <see cref="Renderer.OnRebuild"/></summary>
public event SkeletonRendererDelegate OnRebuild { add { Renderer.OnRebuild += value; } remove { Renderer.OnRebuild -= value; } }
/// <summary>A compatibility wrapper for <see cref="Renderer.OnMeshAndMaterialsUpdated"/></summary>
public event SkeletonRendererDelegate OnMeshAndMaterialsUpdated { add { Renderer.OnMeshAndMaterialsUpdated += value; } remove { Renderer.OnMeshAndMaterialsUpdated -= value; } }
/// <summary>Update mode to optionally limit updates to e.g. only apply animations but not update the mesh.</summary>
public UpdateMode UpdateMode { get { return Renderer.UpdateMode; } set { Renderer.UpdateMode = value; } }
public abstract bool IsValid { get; }
#endregion
public virtual void Awake () {
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
UpgradeTo43();
#endif
InitializeMainThreadID();
#if UNITY_EDITOR
SkeletonAnimationBase.ApplicationIsPlaying = Application.isPlaying;
#endif
EnsureRendererEventsSubscribed();
}
public void EnsureRendererEventsSubscribed () {
Renderer.OnRebuild -= OnRendererRebuild;
Renderer.OnRebuild += OnRendererRebuild;
}
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
// compatibility layer between 4.1 and 4.2, automatically transfer serialized attributes.
public abstract void UpgradeTo43 ();
#endif
/// <summary>
/// Initialize the associated renderer component and subsequently this animation component.
/// Creates the internal Spine objects and buffers.</summary>
/// <param name="overwrite">If set to <c>true</c>, force overwrite an already initialized object.</param>
public virtual void Initialize (bool overwrite, bool quiet = false, bool calledFromRendererCallback = false) {
#if UNITY_EDITOR
if (BuildUtilities.IsInSkeletonAssetBuildPreProcessing)
return;
#endif
if (!calledFromRendererCallback) {
if (skeletonRenderer == null)
skeletonRenderer = this.GetComponent<ISkeletonRenderer>();
if (!skeletonRenderer.IsValid || !this.IsValid || overwrite)
skeletonRenderer.Initialize(overwrite, quiet);
}
if (this.IsValid && !overwrite)
return;
InitializeAnimationComponent();
if (_OnAnimationRebuild != null)
_OnAnimationRebuild(this);
}
/// <summary>
/// Manually initializes just this animation component without initializing the associated renderer component.
/// The renderer component has to be initialized before calling this method, which happens automatically via
/// renderer component Awake, or when needed earlier by calling <see cref="Initialize(bool, bool, bool)"/> on
/// this animation component which initializes both components in the correct order.
/// </summary>
public virtual void InitializeAnimationComponent () {
if (skeletonRenderer == null)
skeletonRenderer = this.GetComponent<ISkeletonRenderer>();
#if UNITY_EDITOR
if (requiresEditorUpdate && skeletonRenderer != null && skeletonRenderer.Skeleton != null)
skeletonRenderer.Skeleton.SetupPose();
requiresEditorUpdate = false;
#endif
}
/// <summary>
/// Clears the previously generated mesh, resets the skeleton's pose, and clears all previously active animations.</summary>
public void ClearState () {
skeletonRenderer.ClearSkeletonState();
ClearAnimationState();
}
public virtual void ClearAnimationState () { }
public void OnEnable () {
if (skeletonRenderer == null)
skeletonRenderer = this.GetComponent<SkeletonRenderer>();
#if USE_THREADED_ANIMATION_UPDATE
if (Application.isPlaying && UsesThreadedAnimation && !isUpdatedExternally) {
SkeletonUpdateSystem system = SkeletonUpdateSystem.Instance;
if (system)
system.RegisterForUpdate(updateTiming, this);
}
#endif
}
#if USE_THREADED_ANIMATION_UPDATE
public void OnDisable () {
if (Application.isPlaying && UsesThreadedAnimation) {
SkeletonUpdateSystem system = SkeletonUpdateSystem.Instance;
if (system)
system.UnregisterFromUpdate(updateTiming, this);
}
}
#endif
protected void OnRendererRebuild (ISkeletonRenderer skeletonRenderer) {
Initialize(overwrite: true, quiet: false, calledFromRendererCallback: true); //InitializeAnimationComponent();
}
#if UNITY_EDITOR
protected void OnValidate () {
if (!Application.isPlaying) {
Renderer.OnRebuild -= OnRendererRebuild;
Renderer.OnRebuild += OnRendererRebuild;
requiresEditorUpdate = true;
} else if (Time.frameCount != 0) {
// OnValidate is called once when starting play mode in the Editor, don't trigger re-init then.
requiresEditorUpdate = true;
}
}
#endif
protected virtual void Update () {
#if UNITY_EDITOR
if (requiresEditorUpdate)
InitializeAnimationComponent();
if (!ApplicationIsPlaying) {
if (!IsValid)
Initialize(false);
Update(0f);
return;
}
#endif
#if USE_THREADED_ANIMATION_UPDATE
if (isUpdatedExternally) return;
#endif
if (updateTiming != UpdateTiming.InUpdate) return;
UpdateOncePerFrame(DeltaTime);
}
protected virtual void FixedUpdate () {
#if USE_THREADED_ANIMATION_UPDATE
if (isUpdatedExternally) return;
#endif
if (updateTiming != UpdateTiming.InFixedUpdate) return;
UpdateOncePerFrame(DeltaTime);
}
protected virtual void LateUpdate () {
#if USE_THREADED_ANIMATION_UPDATE
if (isUpdatedExternally) return;
#endif
if (updateTiming != UpdateTiming.InLateUpdate) return;
UpdateOncePerFrame(DeltaTime);
}
/// <summary>Calls <see cref="Update()"/> if it has not yet been called this frame.</summary>
public virtual void UpdateOncePerFrame (float deltaTime) {
if (frameOfLastUpdate != Time.frameCount) {
MainThreadBeforeUpdateInternal();
UpdateInternal(deltaTime, Time.frameCount, calledFromOnlyMainThread: true);
}
}
/// <summary>
/// Main thread update part preparing properties only accessible from main thread.
/// To be followed by a potentially threaded call to <see cref="UpdateInternal"/>.
/// </summary>
public virtual void MainThreadBeforeUpdateInternal () {
if (skeletonRenderer == null || !skeletonRenderer.IsValid || skeletonRenderer.Freeze || !this.IsValid
|| skeletonRenderer.UpdateMode < UpdateMode.OnlyAnimationStatus) {
skipUpdate = true;
return;
}
skipUpdate = false;
skeletonRenderer.GatherTransformMovementForPhysics();
if (_BeforeUpdate != null)
_BeforeUpdate(this);
}
public virtual void MainThreadAfterUpdateInternal () { }
#if USE_THREADED_ANIMATION_UPDATE
public virtual void UpdateExternal (int currentFrameCount, bool calledFromOnlyMainThread = true) {
UpdateInternal(UsedExternalDeltaTime, currentFrameCount, calledFromOnlyMainThread);
}
#endif
public virtual void UpdateInternal (float deltaTime, int currentFrameCount, bool calledFromOnlyMainThread = true) {
if (skipUpdate)
return;
frameOfLastUpdate = currentFrameCount;
UpdateAnimationStatus(deltaTime);
skeletonRenderer.ApplyTransformMovementToPhysics();
if (skeletonRenderer.UpdateMode == UpdateMode.OnlyAnimationStatus)
return;
ApplyAnimation(calledFromOnlyMainThread);
}
#if USE_THREADED_ANIMATION_UPDATE
/// <summary>Progresses the AnimationState according to the given deltaTime, and applies it to the Skeleton.
/// Use Time.deltaTime to update manually. Use deltaTime 0 to update without progressing the time.</summary>
public virtual CoroutineIterator UpdateInternalSplit (CoroutineIterator coroutineIterator,
int currentFrameCount) {
if (coroutineIterator.IsDone)
return CoroutineIterator.Done;
const int StateBits = 1;
const uint StateMask = (1 << StateBits) - 1;
switch (coroutineIterator.State(StateMask)) {
case 0:
if (skipUpdate)
return CoroutineIterator.Done;
frameOfLastUpdate = currentFrameCount;
UpdateAnimationStatus(UsedExternalDeltaTime);
skeletonRenderer.ApplyTransformMovementToPhysics();
if (skeletonRenderer.UpdateMode == UpdateMode.OnlyAnimationStatus)
return CoroutineIterator.Done;
goto case 1;
case 1:
return ApplyAnimationSplit(coroutineIterator.ToNestedCall(StateBits))
.FromNestedCall(1, StateBits);
default:
Debug.LogError(string.Format(
"Internal coroutine logic error: SkeletonAnimationBase.UpdateInternalSplit state was {0}.",
coroutineIterator.State(StateMask)), this);
return CoroutineIterator.Done;
}
}
#endif
/// <summary>Progresses the AnimationState according to the given deltaTime, and applies it to the Skeleton.
/// Use Time.deltaTime to update manually. Use deltaTime 0 to update without progressing the time.</summary>
public virtual void Update (float deltaTime) {
MainThreadBeforeUpdateInternal();
UpdateInternal(deltaTime, Time.frameCount, calledFromOnlyMainThread: true);
}
public virtual void ApplyAnimation (bool calledFromMainThread = true) {
if (_BeforeApply != null)
_BeforeApply(this);
ApplyStateToSkeleton(calledFromMainThread);
skeletonRenderer.AfterAnimationApplied(calledFromMainThread);
}
public virtual CoroutineIterator ApplyAnimationSplit (CoroutineIterator coroutineIterator) {
if (coroutineIterator.IsDone)
return CoroutineIterator.Done;
const int StateBits = 2;
const uint StateMask = (1 << StateBits) - 1;
switch (coroutineIterator.State(StateMask)) {
case 0:
if (_BeforeApply != null) {
AssertIsWorkerThread();
return coroutineIterator.YieldReturnAtState(1, StateMask);
} else {
goto case 2;
}
case 1:
AssertIsMainThread();
_BeforeApply(this);
return coroutineIterator.YieldReturnAtState(2, StateMask);
case 2:
AssertIsWorkerThread();
ApplyStateToSkeleton(calledFromMainThread: false);
goto case 3;
case 3:
return skeletonRenderer.AfterAnimationAppliedSplit(coroutineIterator.ToNestedCall(StateBits))
.FromNestedCall(3, StateBits);
default:
Debug.LogError(string.Format(
"Internal coroutine logic error: SkeletonAnimationBase.ApplyAnimationSplit state was {0}.",
coroutineIterator.State(StateMask)), this);
return CoroutineIterator.Done;
}
}
public void OnBecameVisibleFromMode (UpdateMode previousUpdateMode) {
// OnBecameVisible is called after Update and LateUpdate(),
// so update if previousUpdateMode didn't already update this frame.
if (previousUpdateMode != UpdateMode.FullUpdate &&
previousUpdateMode != UpdateMode.EverythingExceptMesh)
Update(0);
}
protected virtual float DeltaTime { get { return Time.deltaTime; } }
protected abstract void UpdateAnimationStatus (float deltaTime);
protected abstract void ApplyStateToSkeleton (bool calledFromMainThread = true);
}
}