[unity] Timeline clip end behaviour made more consistent and configurable, mixes out to empty animation by default. Partially reverted b4fd09b6. See #1933. Closes #1807.

This commit is contained in:
Harald Csaszar 2021-08-03 18:29:53 +02:00
parent b949e79ca5
commit a775209577
3 changed files with 51 additions and 12 deletions

View File

@ -51,7 +51,8 @@ public class SpineAnimationStateDrawer : PropertyDrawer {
SerializedProperty mixDurationProp = property.FindPropertyRelative("mixDuration"); SerializedProperty mixDurationProp = property.FindPropertyRelative("mixDuration");
SerializedProperty holdPreviousProp = property.FindPropertyRelative("holdPrevious"); SerializedProperty holdPreviousProp = property.FindPropertyRelative("holdPrevious");
SerializedProperty dontPauseWithDirectorProp = property.FindPropertyRelative("dontPauseWithDirector"); SerializedProperty dontPauseWithDirectorProp = property.FindPropertyRelative("dontPauseWithDirector");
SerializedProperty dontPauseOnStopProp = property.FindPropertyRelative("dontPauseOnStop"); SerializedProperty dontEndWithClip = property.FindPropertyRelative("dontEndWithClip");
SerializedProperty endMixOutDuration = property.FindPropertyRelative("endMixOutDuration");
SerializedProperty eventProp = property.FindPropertyRelative("eventThreshold"); SerializedProperty eventProp = property.FindPropertyRelative("eventThreshold");
SerializedProperty attachmentProp = property.FindPropertyRelative("attachmentThreshold"); SerializedProperty attachmentProp = property.FindPropertyRelative("attachmentThreshold");
SerializedProperty drawOrderProp = property.FindPropertyRelative("drawOrderThreshold"); SerializedProperty drawOrderProp = property.FindPropertyRelative("drawOrderThreshold");
@ -78,11 +79,19 @@ public class SpineAnimationStateDrawer : PropertyDrawer {
"If set to true, the animation will continue playing when the Director is paused.")); "If set to true, the animation will continue playing when the Director is paused."));
singleFieldRect.y += lineHeightWithSpacing; singleFieldRect.y += lineHeightWithSpacing;
EditorGUI.PropertyField(singleFieldRect, dontEndWithClip,
new GUIContent("Don't End with Clip",
"Normally when empty space follows the clip on the timeline, the empty animation is set on the track. " +
"Set this parameter to true to continue playing the clip's animation instead."));
using (new EditorGUI.DisabledGroupScope(dontPauseWithDirectorProp.boolValue == true)) { singleFieldRect.y += lineHeightWithSpacing;
EditorGUI.PropertyField(singleFieldRect, dontPauseOnStopProp,
new GUIContent("Don't Pause on Stop", using (new EditorGUI.DisabledGroupScope(dontEndWithClip.boolValue == true)) {
"If 'Don't Pause with Director' is true but this parameter is false, the animation will continue playing when the Graph is stopped, e.g. when reaching the track end.")); EditorGUI.PropertyField(singleFieldRect, endMixOutDuration,
new GUIContent("Clip End Mix Out Duration",
"When 'Don't End with Clip' is false, and the clip is followed by blank space or stopped, " +
"the empty animation is set with this MixDuration. When set to a negative value, " +
"the clip is paused instead."));
} }
singleFieldRect.y += lineHeightWithSpacing * 0.5f; singleFieldRect.y += lineHeightWithSpacing * 0.5f;

View File

@ -54,7 +54,9 @@ namespace Spine.Unity.Playables {
public float mixDuration = 0.1f; public float mixDuration = 0.1f;
public bool holdPrevious = false; public bool holdPrevious = false;
public bool dontPauseWithDirector = false; public bool dontPauseWithDirector = false;
public bool dontPauseOnStop = false; [UnityEngine.Serialization.FormerlySerializedAs("dontPauseOnStop")]
public bool dontEndWithClip = false;
public float endMixOutDuration = 0.1f;
[Range(0, 1f)] [Range(0, 1f)]
public float attachmentThreshold = 0.5f; public float attachmentThreshold = 0.5f;

View File

@ -38,15 +38,19 @@ namespace Spine.Unity.Playables {
public class SpineAnimationStateMixerBehaviour : PlayableBehaviour { public class SpineAnimationStateMixerBehaviour : PlayableBehaviour {
float[] lastInputWeights; float[] lastInputWeights;
bool lastAnyTrackPlaying = false;
public int trackIndex; public int trackIndex;
IAnimationStateComponent animationStateComponent; IAnimationStateComponent animationStateComponent;
bool pauseWithDirector = true; bool pauseWithDirector = true;
bool pauseWithDirectorButNotOnStop = false; bool endAtClipEnd = true;
float endMixOutDuration = 0.1f;
bool isPaused = false; bool isPaused = false;
TrackEntry pausedTrackEntry; TrackEntry pausedTrackEntry;
float previousTimeScale = 1; float previousTimeScale = 1;
TrackEntry timelineStartedTrackEntry;
public override void OnBehaviourPause (Playable playable, FrameData info) { public override void OnBehaviourPause (Playable playable, FrameData info) {
if (pauseWithDirector) { if (pauseWithDirector) {
if (!isPaused) if (!isPaused)
@ -56,8 +60,10 @@ namespace Spine.Unity.Playables {
} }
public override void OnGraphStop (Playable playable) { public override void OnGraphStop (Playable playable) {
if (isPaused && pauseWithDirectorButNotOnStop) if (endAtClipEnd)
HandleResume(playable); // this stop event occurs after pause, so resume again HandleClipEnd();
else if (isPaused) // stop event occurred after pause, so resume again
HandleResume(playable);
} }
public override void OnBehaviourPlay (Playable playable, FrameData info) { public override void OnBehaviourPlay (Playable playable, FrameData info) {
@ -70,7 +76,7 @@ namespace Spine.Unity.Playables {
if (animationStateComponent == null) return; if (animationStateComponent == null) return;
TrackEntry current = animationStateComponent.AnimationState.GetCurrent(trackIndex); TrackEntry current = animationStateComponent.AnimationState.GetCurrent(trackIndex);
if (current != null) { if (current != null && current == timelineStartedTrackEntry) {
previousTimeScale = current.TimeScale; previousTimeScale = current.TimeScale;
current.TimeScale = 0; current.TimeScale = 0;
pausedTrackEntry = current; pausedTrackEntry = current;
@ -86,9 +92,22 @@ namespace Spine.Unity.Playables {
} }
} }
protected void HandleClipEnd () {
var state = animationStateComponent.AnimationState;
if (endAtClipEnd &&
timelineStartedTrackEntry != null &&
timelineStartedTrackEntry == state.GetCurrent(trackIndex)) {
if (endMixOutDuration >= 0)
state.SetEmptyAnimation(trackIndex, endMixOutDuration);
else // pause if endMixOutDuration < 0
timelineStartedTrackEntry.TimeScale = 0;
timelineStartedTrackEntry = null;
}
}
// NOTE: This function is called at runtime and edit time. Keep that in mind when setting the values of properties. // NOTE: This function is called at runtime and edit time. Keep that in mind when setting the values of properties.
public override void ProcessFrame (Playable playable, FrameData info, object playerData) { public override void ProcessFrame (Playable playable, FrameData info, object playerData) {
var skeletonAnimation = playerData as SkeletonAnimation; var skeletonAnimation = playerData as SkeletonAnimation;
var skeletonGraphic = playerData as SkeletonGraphic; var skeletonGraphic = playerData as SkeletonGraphic;
animationStateComponent = playerData as IAnimationStateComponent; animationStateComponent = playerData as IAnimationStateComponent;
@ -116,12 +135,15 @@ namespace Spine.Unity.Playables {
this.lastInputWeights[i] = default(float); this.lastInputWeights[i] = default(float);
} }
var lastInputWeights = this.lastInputWeights; var lastInputWeights = this.lastInputWeights;
bool anyTrackPlaying = false;
// Check all clips. If a clip that was weight 0 turned into weight 1, call SetAnimation. // Check all clips. If a clip that was weight 0 turned into weight 1, call SetAnimation.
for (int i = 0; i < inputCount; i++) { for (int i = 0; i < inputCount; i++) {
float lastInputWeight = lastInputWeights[i]; float lastInputWeight = lastInputWeights[i];
float inputWeight = playable.GetInputWeight(i); float inputWeight = playable.GetInputWeight(i);
bool trackStarted = lastInputWeight == 0 && inputWeight > 0; bool trackStarted = lastInputWeight == 0 && inputWeight > 0;
if (inputWeight > 0)
anyTrackPlaying = true;
lastInputWeights[i] = inputWeight; lastInputWeights[i] = inputWeight;
if (trackStarted) { if (trackStarted) {
@ -129,7 +151,8 @@ namespace Spine.Unity.Playables {
SpineAnimationStateBehaviour clipData = inputPlayable.GetBehaviour(); SpineAnimationStateBehaviour clipData = inputPlayable.GetBehaviour();
pauseWithDirector = !clipData.dontPauseWithDirector; pauseWithDirector = !clipData.dontPauseWithDirector;
pauseWithDirectorButNotOnStop = pauseWithDirector && clipData.dontPauseOnStop; endAtClipEnd = !clipData.dontEndWithClip;
endMixOutDuration = clipData.endMixOutDuration;
if (clipData.animationReference == null) { if (clipData.animationReference == null) {
float mixDuration = clipData.customDuration ? clipData.mixDuration : state.Data.DefaultMix; float mixDuration = clipData.customDuration ? clipData.mixDuration : state.Data.DefaultMix;
@ -147,6 +170,8 @@ namespace Spine.Unity.Playables {
if (clipData.customDuration) if (clipData.customDuration)
trackEntry.MixDuration = clipData.mixDuration; trackEntry.MixDuration = clipData.mixDuration;
timelineStartedTrackEntry = trackEntry;
} }
//else Debug.LogWarningFormat("Animation named '{0}' not found", clipData.animationName); //else Debug.LogWarningFormat("Animation named '{0}' not found", clipData.animationName);
} }
@ -162,6 +187,9 @@ namespace Spine.Unity.Playables {
} }
} }
} }
if (lastAnyTrackPlaying && !anyTrackPlaying)
HandleClipEnd();
this.lastAnyTrackPlaying = anyTrackPlaying;
} }
#if SPINE_EDITMODEPOSE #if SPINE_EDITMODEPOSE