[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 holdPreviousProp = property.FindPropertyRelative("holdPrevious");
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 attachmentProp = property.FindPropertyRelative("attachmentThreshold");
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."));
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)) {
EditorGUI.PropertyField(singleFieldRect, dontPauseOnStopProp,
new GUIContent("Don't Pause on Stop",
"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."));
singleFieldRect.y += lineHeightWithSpacing;
using (new EditorGUI.DisabledGroupScope(dontEndWithClip.boolValue == true)) {
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;

View File

@ -54,7 +54,9 @@ namespace Spine.Unity.Playables {
public float mixDuration = 0.1f;
public bool holdPrevious = 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)]
public float attachmentThreshold = 0.5f;

View File

@ -38,15 +38,19 @@ namespace Spine.Unity.Playables {
public class SpineAnimationStateMixerBehaviour : PlayableBehaviour {
float[] lastInputWeights;
bool lastAnyTrackPlaying = false;
public int trackIndex;
IAnimationStateComponent animationStateComponent;
bool pauseWithDirector = true;
bool pauseWithDirectorButNotOnStop = false;
bool endAtClipEnd = true;
float endMixOutDuration = 0.1f;
bool isPaused = false;
TrackEntry pausedTrackEntry;
float previousTimeScale = 1;
TrackEntry timelineStartedTrackEntry;
public override void OnBehaviourPause (Playable playable, FrameData info) {
if (pauseWithDirector) {
if (!isPaused)
@ -56,8 +60,10 @@ namespace Spine.Unity.Playables {
}
public override void OnGraphStop (Playable playable) {
if (isPaused && pauseWithDirectorButNotOnStop)
HandleResume(playable); // this stop event occurs after pause, so resume again
if (endAtClipEnd)
HandleClipEnd();
else if (isPaused) // stop event occurred after pause, so resume again
HandleResume(playable);
}
public override void OnBehaviourPlay (Playable playable, FrameData info) {
@ -70,7 +76,7 @@ namespace Spine.Unity.Playables {
if (animationStateComponent == null) return;
TrackEntry current = animationStateComponent.AnimationState.GetCurrent(trackIndex);
if (current != null) {
if (current != null && current == timelineStartedTrackEntry) {
previousTimeScale = current.TimeScale;
current.TimeScale = 0;
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.
public override void ProcessFrame (Playable playable, FrameData info, object playerData) {
var skeletonAnimation = playerData as SkeletonAnimation;
var skeletonGraphic = playerData as SkeletonGraphic;
animationStateComponent = playerData as IAnimationStateComponent;
@ -116,12 +135,15 @@ namespace Spine.Unity.Playables {
this.lastInputWeights[i] = default(float);
}
var lastInputWeights = this.lastInputWeights;
bool anyTrackPlaying = false;
// Check all clips. If a clip that was weight 0 turned into weight 1, call SetAnimation.
for (int i = 0; i < inputCount; i++) {
float lastInputWeight = lastInputWeights[i];
float inputWeight = playable.GetInputWeight(i);
bool trackStarted = lastInputWeight == 0 && inputWeight > 0;
if (inputWeight > 0)
anyTrackPlaying = true;
lastInputWeights[i] = inputWeight;
if (trackStarted) {
@ -129,7 +151,8 @@ namespace Spine.Unity.Playables {
SpineAnimationStateBehaviour clipData = inputPlayable.GetBehaviour();
pauseWithDirector = !clipData.dontPauseWithDirector;
pauseWithDirectorButNotOnStop = pauseWithDirector && clipData.dontPauseOnStop;
endAtClipEnd = !clipData.dontEndWithClip;
endMixOutDuration = clipData.endMixOutDuration;
if (clipData.animationReference == null) {
float mixDuration = clipData.customDuration ? clipData.mixDuration : state.Data.DefaultMix;
@ -147,6 +170,8 @@ namespace Spine.Unity.Playables {
if (clipData.customDuration)
trackEntry.MixDuration = clipData.mixDuration;
timelineStartedTrackEntry = trackEntry;
}
//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