|
@@ -34,17 +34,16 @@ using System.Collections.Generic;
|
|
namespace Spine {
|
|
namespace Spine {
|
|
public class AnimationState {
|
|
public class AnimationState {
|
|
static readonly Animation EmptyAnimation = new Animation("<empty>", new ExposedList<Timeline>(), 0);
|
|
static readonly Animation EmptyAnimation = new Animation("<empty>", new ExposedList<Timeline>(), 0);
|
|
- internal const int Subsequent = 0, First = 1, Dip = 2, DipMix = 3;
|
|
|
|
|
|
+ internal const int Subsequent = 0, First = 1, Hold = 2, HoldMix = 3;
|
|
|
|
|
|
private AnimationStateData data;
|
|
private AnimationStateData data;
|
|
|
|
|
|
- Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
|
|
|
|
|
|
+ private readonly Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
|
|
private readonly ExposedList<TrackEntry> tracks = new ExposedList<TrackEntry>();
|
|
private readonly ExposedList<TrackEntry> tracks = new ExposedList<TrackEntry>();
|
|
private readonly ExposedList<Event> events = new ExposedList<Event>();
|
|
private readonly ExposedList<Event> events = new ExposedList<Event>();
|
|
private readonly EventQueue queue; // Initialized by constructor.
|
|
private readonly EventQueue queue; // Initialized by constructor.
|
|
|
|
|
|
private readonly HashSet<int> propertyIDs = new HashSet<int>();
|
|
private readonly HashSet<int> propertyIDs = new HashSet<int>();
|
|
- private readonly ExposedList<TrackEntry> mixingTo = new ExposedList<TrackEntry>();
|
|
|
|
private bool animationsChanged;
|
|
private bool animationsChanged;
|
|
|
|
|
|
private float timeScale = 1;
|
|
private float timeScale = 1;
|
|
@@ -119,6 +118,7 @@ namespace Spine {
|
|
// End mixing from entries once all have completed.
|
|
// End mixing from entries once all have completed.
|
|
var from = current.mixingFrom;
|
|
var from = current.mixingFrom;
|
|
current.mixingFrom = null;
|
|
current.mixingFrom = null;
|
|
|
|
+ if (from != null) from.mixingTo = null;
|
|
while (from != null) {
|
|
while (from != null) {
|
|
queue.End(from);
|
|
queue.End(from);
|
|
from = from.mixingFrom;
|
|
from = from.mixingFrom;
|
|
@@ -146,6 +146,7 @@ namespace Spine {
|
|
// Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame).
|
|
// Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame).
|
|
if (from.totalAlpha == 0 || to.mixDuration == 0) {
|
|
if (from.totalAlpha == 0 || to.mixDuration == 0) {
|
|
to.mixingFrom = from.mixingFrom;
|
|
to.mixingFrom = from.mixingFrom;
|
|
|
|
+ if (from.mixingFrom != null) from.mixingFrom.mixingTo = to;
|
|
to.interruptAlpha = from.interruptAlpha;
|
|
to.interruptAlpha = from.interruptAlpha;
|
|
queue.End(from);
|
|
queue.End(from);
|
|
}
|
|
}
|
|
@@ -187,11 +188,11 @@ namespace Spine {
|
|
int timelineCount = current.animation.timelines.Count;
|
|
int timelineCount = current.animation.timelines.Count;
|
|
var timelines = current.animation.timelines;
|
|
var timelines = current.animation.timelines;
|
|
var timelinesItems = timelines.Items;
|
|
var timelinesItems = timelines.Items;
|
|
- if (mix == 1 || blend == MixBlend.Add) {
|
|
|
|
|
|
+ if (i == 0 && (mix == 1 || blend == MixBlend.Add)) {
|
|
for (int ii = 0; ii < timelineCount; ii++)
|
|
for (int ii = 0; ii < timelineCount; ii++)
|
|
timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In);
|
|
timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In);
|
|
} else {
|
|
} else {
|
|
- var timelineData = current.timelineData.Items;
|
|
|
|
|
|
+ var timelineMode = current.timelineMode.Items;
|
|
|
|
|
|
bool firstFrame = current.timelinesRotation.Count == 0;
|
|
bool firstFrame = current.timelinesRotation.Count == 0;
|
|
if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1);
|
|
if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1);
|
|
@@ -199,7 +200,7 @@ namespace Spine {
|
|
|
|
|
|
for (int ii = 0; ii < timelineCount; ii++) {
|
|
for (int ii = 0; ii < timelineCount; ii++) {
|
|
Timeline timeline = timelinesItems[ii];
|
|
Timeline timeline = timelinesItems[ii];
|
|
- MixBlend timelineBlend = timelineData[ii] >= AnimationState.Subsequent ? blend : MixBlend.Setup;
|
|
|
|
|
|
+ MixBlend timelineBlend = timelineMode[ii] >= AnimationState.Subsequent ? blend : MixBlend.Setup;
|
|
var rotateTimeline = timeline as RotateTimeline;
|
|
var rotateTimeline = timeline as RotateTimeline;
|
|
if (rotateTimeline != null)
|
|
if (rotateTimeline != null)
|
|
ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame);
|
|
ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame);
|
|
@@ -237,14 +238,14 @@ namespace Spine {
|
|
var timelines = from.animation.timelines;
|
|
var timelines = from.animation.timelines;
|
|
int timelineCount = timelines.Count;
|
|
int timelineCount = timelines.Count;
|
|
var timelinesItems = timelines.Items;
|
|
var timelinesItems = timelines.Items;
|
|
- float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix);
|
|
|
|
|
|
+ float alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
|
|
|
|
|
|
if (blend == MixBlend.Add) {
|
|
if (blend == MixBlend.Add) {
|
|
for (int i = 0; i < timelineCount; i++)
|
|
for (int i = 0; i < timelineCount; i++)
|
|
(timelinesItems[i]).Apply(skeleton, animationLast, animationTime, eventBuffer, alphaMix, blend, MixDirection.Out);
|
|
(timelinesItems[i]).Apply(skeleton, animationLast, animationTime, eventBuffer, alphaMix, blend, MixDirection.Out);
|
|
} else {
|
|
} else {
|
|
- var timelineData = from.timelineData.Items;
|
|
|
|
- var timelineDipMix = from.timelineDipMix.Items;
|
|
|
|
|
|
+ var timelineMode = from.timelineMode.Items;
|
|
|
|
+ var timelineHoldMix = from.timelineHoldMix.Items;
|
|
|
|
|
|
bool firstFrame = from.timelinesRotation.Count == 0;
|
|
bool firstFrame = from.timelinesRotation.Count == 0;
|
|
if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize
|
|
if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize
|
|
@@ -255,7 +256,7 @@ namespace Spine {
|
|
Timeline timeline = timelinesItems[i];
|
|
Timeline timeline = timelinesItems[i];
|
|
MixBlend timelineBlend;
|
|
MixBlend timelineBlend;
|
|
float alpha;
|
|
float alpha;
|
|
- switch (timelineData[i]) {
|
|
|
|
|
|
+ switch (timelineMode[i]) {
|
|
case AnimationState.Subsequent:
|
|
case AnimationState.Subsequent:
|
|
if (!attachments && timeline is AttachmentTimeline)
|
|
if (!attachments && timeline is AttachmentTimeline)
|
|
continue;
|
|
continue;
|
|
@@ -268,14 +269,14 @@ namespace Spine {
|
|
timelineBlend = MixBlend.Setup;
|
|
timelineBlend = MixBlend.Setup;
|
|
alpha = alphaMix;
|
|
alpha = alphaMix;
|
|
break;
|
|
break;
|
|
- case AnimationState.Dip:
|
|
|
|
|
|
+ case AnimationState.Hold:
|
|
timelineBlend = MixBlend.Setup;
|
|
timelineBlend = MixBlend.Setup;
|
|
- alpha = alphaDip;
|
|
|
|
|
|
+ alpha = alphaHold;
|
|
break;
|
|
break;
|
|
default:
|
|
default:
|
|
timelineBlend = MixBlend.Setup;
|
|
timelineBlend = MixBlend.Setup;
|
|
- TrackEntry dipMix = timelineDipMix[i];
|
|
|
|
- alpha = alphaDip * Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
|
|
|
|
|
|
+ TrackEntry holdMix = timelineHoldMix[i];
|
|
|
|
+ alpha = alphaHold * Math.Max(0, 1 - holdMix.mixTime / holdMix.mixDuration);
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
from.totalAlpha += alpha;
|
|
from.totalAlpha += alpha;
|
|
@@ -428,6 +429,7 @@ namespace Spine {
|
|
if (from == null) break;
|
|
if (from == null) break;
|
|
queue.End(from);
|
|
queue.End(from);
|
|
entry.mixingFrom = null;
|
|
entry.mixingFrom = null;
|
|
|
|
+ entry.mixingTo = null;
|
|
entry = from;
|
|
entry = from;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -444,6 +446,7 @@ namespace Spine {
|
|
if (from != null) {
|
|
if (from != null) {
|
|
if (interrupt) queue.Interrupt(from);
|
|
if (interrupt) queue.Interrupt(from);
|
|
current.mixingFrom = from;
|
|
current.mixingFrom = from;
|
|
|
|
+ current.mixingTo = current;
|
|
current.mixTime = 0;
|
|
current.mixTime = 0;
|
|
|
|
|
|
// Store interrupted mix percentage.
|
|
// Store interrupted mix percentage.
|
|
@@ -599,6 +602,7 @@ namespace Spine {
|
|
entry.trackIndex = trackIndex;
|
|
entry.trackIndex = trackIndex;
|
|
entry.animation = animation;
|
|
entry.animation = animation;
|
|
entry.loop = loop;
|
|
entry.loop = loop;
|
|
|
|
+ entry.holdPrevious = false;
|
|
|
|
|
|
entry.eventThreshold = 0;
|
|
entry.eventThreshold = 0;
|
|
entry.attachmentThreshold = 0;
|
|
entry.attachmentThreshold = 0;
|
|
@@ -636,17 +640,71 @@ namespace Spine {
|
|
private void AnimationsChanged () {
|
|
private void AnimationsChanged () {
|
|
animationsChanged = false;
|
|
animationsChanged = false;
|
|
|
|
|
|
- var propertyIDs = this.propertyIDs;
|
|
|
|
- propertyIDs.Clear();
|
|
|
|
- var mixingTo = this.mixingTo;
|
|
|
|
|
|
+ this.propertyIDs.Clear();
|
|
|
|
|
|
var tracksItems = tracks.Items;
|
|
var tracksItems = tracks.Items;
|
|
for (int i = 0, n = tracks.Count; i < n; i++) {
|
|
for (int i = 0, n = tracks.Count; i < n; i++) {
|
|
var entry = tracksItems[i];
|
|
var entry = tracksItems[i];
|
|
- if (entry != null && (i == 0 || entry.mixBlend != MixBlend.Add)) entry.SetTimelineData(null, mixingTo, propertyIDs);
|
|
|
|
|
|
+ if (entry == null) continue;
|
|
|
|
+ // Move to last entry, then iterate in reverse (the order animations are applied).
|
|
|
|
+ while (entry.mixingFrom != null)
|
|
|
|
+ entry = entry.mixingFrom;
|
|
|
|
+
|
|
|
|
+ do {
|
|
|
|
+ if (entry.mixingTo == null || entry.mixBlend != MixBlend.Add) SetTimelineModes(entry);
|
|
|
|
+ entry = entry.mixingTo;
|
|
|
|
+ } while (entry != null);
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void SetTimelineModes (TrackEntry entry) {
|
|
|
|
+ var to = entry.mixingTo;
|
|
|
|
+ var timelines = entry.animation.timelines.Items;
|
|
|
|
+ int timelinesCount = entry.animation.timelines.Count;
|
|
|
|
+ var timelineMode = entry.timelineMode.Resize(timelinesCount).Items; //timelineMode.setSize(timelinesCount);
|
|
|
|
+ entry.timelineHoldMix.Clear();
|
|
|
|
+ var timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; //timelineHoldMix.setSize(timelinesCount);
|
|
|
|
+ var propertyIDs = this.propertyIDs;
|
|
|
|
+
|
|
|
|
+ if (to != null && to.holdPrevious) {
|
|
|
|
+ for (int i = 0; i < timelinesCount; i++) {
|
|
|
|
+ propertyIDs.Add(timelines[i].PropertyId);
|
|
|
|
+ timelineMode[i] = AnimationState.Hold;
|
|
|
|
+ }
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // outer:
|
|
|
|
+ for (int i = 0; i < timelinesCount; i++) {
|
|
|
|
+ int id = timelines[i].PropertyId;
|
|
|
|
+ if (!propertyIDs.Add(id))
|
|
|
|
+ timelineMode[i] = AnimationState.Subsequent;
|
|
|
|
+ else if (to == null || !HasTimeline(to, id))
|
|
|
|
+ timelineMode[i] = AnimationState.First;
|
|
|
|
+ else {
|
|
|
|
+ for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) {
|
|
|
|
+ if (HasTimeline(next, id)) continue;
|
|
|
|
+ if (next.mixDuration > 0) {
|
|
|
|
+ timelineMode[i] = AnimationState.HoldMix;
|
|
|
|
+ timelineHoldMix[i] = next;
|
|
|
|
+ goto continue_outer; // continue outer;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ timelineMode[i] = AnimationState.Hold;
|
|
|
|
+ }
|
|
|
|
+ continue_outer: {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ static bool HasTimeline (TrackEntry entry, int id) {
|
|
|
|
+ var timelines = entry.animation.timelines.Items;
|
|
|
|
+ for (int i = 0, n = entry.animation.timelines.Count; i < n; i++)
|
|
|
|
+ if (timelines[i].PropertyId == id) return true;
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
/// <returns>The track entry for the animation currently playing on the track, or null if no animation is currently playing.</returns>
|
|
/// <returns>The track entry for the animation currently playing on the track, or null if no animation is currently playing.</returns>
|
|
public TrackEntry GetCurrent (int trackIndex) {
|
|
public TrackEntry GetCurrent (int trackIndex) {
|
|
return (trackIndex >= tracks.Count) ? null : tracks.Items[trackIndex];
|
|
return (trackIndex >= tracks.Count) ? null : tracks.Items[trackIndex];
|
|
@@ -675,17 +733,17 @@ namespace Spine {
|
|
public class TrackEntry : Pool<TrackEntry>.IPoolable {
|
|
public class TrackEntry : Pool<TrackEntry>.IPoolable {
|
|
internal Animation animation;
|
|
internal Animation animation;
|
|
|
|
|
|
- internal TrackEntry next, mixingFrom;
|
|
|
|
|
|
+ internal TrackEntry next, mixingFrom, mixingTo;
|
|
internal int trackIndex;
|
|
internal int trackIndex;
|
|
|
|
|
|
- internal bool loop;
|
|
|
|
|
|
+ internal bool loop, holdPrevious;
|
|
internal float eventThreshold, attachmentThreshold, drawOrderThreshold;
|
|
internal float eventThreshold, attachmentThreshold, drawOrderThreshold;
|
|
internal float animationStart, animationEnd, animationLast, nextAnimationLast;
|
|
internal float animationStart, animationEnd, animationLast, nextAnimationLast;
|
|
internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f;
|
|
internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f;
|
|
internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha;
|
|
internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha;
|
|
internal MixBlend mixBlend = MixBlend.Replace;
|
|
internal MixBlend mixBlend = MixBlend.Replace;
|
|
- internal readonly ExposedList<int> timelineData = new ExposedList<int>();
|
|
|
|
- internal readonly ExposedList<TrackEntry> timelineDipMix = new ExposedList<TrackEntry>();
|
|
|
|
|
|
+ internal readonly ExposedList<int> timelineMode = new ExposedList<int>();
|
|
|
|
+ internal readonly ExposedList<TrackEntry> timelineHoldMix = new ExposedList<TrackEntry>();
|
|
internal readonly ExposedList<float> timelinesRotation = new ExposedList<float>();
|
|
internal readonly ExposedList<float> timelinesRotation = new ExposedList<float>();
|
|
|
|
|
|
// IPoolable.Reset()
|
|
// IPoolable.Reset()
|
|
@@ -693,8 +751,8 @@ namespace Spine {
|
|
next = null;
|
|
next = null;
|
|
mixingFrom = null;
|
|
mixingFrom = null;
|
|
animation = null;
|
|
animation = null;
|
|
- timelineData.Clear();
|
|
|
|
- timelineDipMix.Clear();
|
|
|
|
|
|
+ timelineMode.Clear();
|
|
|
|
+ timelineHoldMix.Clear();
|
|
timelinesRotation.Clear();
|
|
timelinesRotation.Clear();
|
|
|
|
|
|
Start = null;
|
|
Start = null;
|
|
@@ -705,47 +763,6 @@ namespace Spine {
|
|
Event = null;
|
|
Event = null;
|
|
}
|
|
}
|
|
|
|
|
|
- /// <summary>Sets the timeline data.</summary>
|
|
|
|
- /// <param name="to">May be null.</param>
|
|
|
|
- internal TrackEntry SetTimelineData (TrackEntry to, ExposedList<TrackEntry> mixingToArray, HashSet<int> propertyIDs) {
|
|
|
|
- if (to != null) mixingToArray.Add(to);
|
|
|
|
- var lastEntry = mixingFrom != null ? mixingFrom.SetTimelineData(this, mixingToArray, propertyIDs) : this;
|
|
|
|
- if (to != null) mixingToArray.Pop();
|
|
|
|
-
|
|
|
|
- var mixingTo = mixingToArray.Items;
|
|
|
|
- int mixingToLast = mixingToArray.Count - 1;
|
|
|
|
- var timelines = animation.timelines.Items;
|
|
|
|
- int timelinesCount = animation.timelines.Count;
|
|
|
|
- var timelineDataItems = timelineData.Resize(timelinesCount).Items; // timelineData.setSize(timelinesCount);
|
|
|
|
- timelineDipMix.Clear();
|
|
|
|
- var timelineDipMixItems = timelineDipMix.Resize(timelinesCount).Items; //timelineDipMix.setSize(timelinesCount);
|
|
|
|
-
|
|
|
|
- // outer:
|
|
|
|
- for (int i = 0; i < timelinesCount; i++) {
|
|
|
|
- int id = timelines[i].PropertyId;
|
|
|
|
- if (!propertyIDs.Add(id)) {
|
|
|
|
- timelineDataItems[i] = AnimationState.Subsequent;
|
|
|
|
- } else if (to == null || !to.HasTimeline(id)) {
|
|
|
|
- timelineDataItems[i] = AnimationState.First;
|
|
|
|
- } else {
|
|
|
|
- for (int ii = mixingToLast; ii >= 0; ii--) {
|
|
|
|
- var entry = mixingTo[ii];
|
|
|
|
- if (!entry.HasTimeline(id)) {
|
|
|
|
- if (entry.mixDuration > 0) {
|
|
|
|
- timelineDataItems[i] = AnimationState.DipMix;
|
|
|
|
- timelineDipMixItems[i] = entry;
|
|
|
|
- goto continue_outer; // continue outer;
|
|
|
|
- }
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- timelineDataItems[i] = AnimationState.Dip;
|
|
|
|
- }
|
|
|
|
- continue_outer: {}
|
|
|
|
- }
|
|
|
|
- return lastEntry;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
bool HasTimeline (int id) {
|
|
bool HasTimeline (int id) {
|
|
var timelines = animation.timelines.Items;
|
|
var timelines = animation.timelines.Items;
|
|
for (int i = 0, n = animation.timelines.Count; i < n; i++)
|
|
for (int i = 0, n = animation.timelines.Count; i < n; i++)
|
|
@@ -903,6 +920,15 @@ namespace Spine {
|
|
/// mixing is currently occuring. When mixing from multiple animations, MixingFrom makes up a linked list.</summary>
|
|
/// mixing is currently occuring. When mixing from multiple animations, MixingFrom makes up a linked list.</summary>
|
|
public TrackEntry MixingFrom { get { return mixingFrom; } }
|
|
public TrackEntry MixingFrom { get { return mixingFrom; } }
|
|
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// If true, when mixing from the previous animation to this animation, the previous animation is applied as normal instead of being mixed out.
|
|
|
|
+ ///
|
|
|
|
+ /// When mixing between animations that key the same property, if a lower track also keys that property then the value will briefly dip toward the lower track value during the mix. This happens because the first animation mixes from 100% to 0% while the second animation mixes from 0% to 100%. Setting HoldPrevious to true applies the first animation at 100% during the mix so the lower track value is overwritten. Such dipping does not occur on the lowest track which keys the property, only when a higher track also keys the property.
|
|
|
|
+ ///
|
|
|
|
+ /// Snapping will occur if HoldPrevious is true and this animation does not key all the same properties as the previous animation.
|
|
|
|
+ /// </summary>
|
|
|
|
+ public bool HoldPrevious { get { return holdPrevious; } set { holdPrevious = value; } }
|
|
|
|
+
|
|
public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete;
|
|
public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete;
|
|
public event AnimationState.TrackEntryEventDelegate Event;
|
|
public event AnimationState.TrackEntryEventDelegate Event;
|
|
internal void OnStart () { if (Start != null) Start(this); }
|
|
internal void OnStart () { if (Start != null) Start(this); }
|