mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-06 15:24:55 +08:00
Merge branch '4.0' into 4.1-beta
This commit is contained in:
commit
e6aefc1065
@ -40,7 +40,7 @@ namespace Spine {
|
||||
/// See <a href='http://esotericsoftware.com/spine-applying-animations/'>Applying Animations</a> in the Spine Runtimes Guide.</para>
|
||||
/// </summary>
|
||||
public class AnimationState {
|
||||
static readonly Animation EmptyAnimation = new Animation("<empty>", new ExposedList<Timeline>(), 0);
|
||||
internal static readonly Animation EmptyAnimation = new Animation("<empty>", new ExposedList<Timeline>(), 0);
|
||||
|
||||
/// 1) A previously applied timeline has set this property.<para />
|
||||
/// Result: Mix from the current pose to the timeline pose.
|
||||
@ -309,9 +309,11 @@ namespace Spine {
|
||||
return applied;
|
||||
}
|
||||
|
||||
/// <summary>Version of <see cref="Apply"/> only applying EventTimelines for lightweight off-screen updates.</summary>
|
||||
/// <summary>Version of <see cref="Apply"/> only applying and updating time at
|
||||
/// EventTimelines for lightweight off-screen updates.</summary>
|
||||
/// <param name="issueEvents">When set to false, only animation times of TrackEntries are updated.</param>
|
||||
// Note: This method is not part of the libgdx reference implementation.
|
||||
public bool ApplyEventTimelinesOnly (Skeleton skeleton) {
|
||||
public bool ApplyEventTimelinesOnly (Skeleton skeleton, bool issueEvents = true) {
|
||||
if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
|
||||
|
||||
ExposedList<Event> events = this.events;
|
||||
@ -323,24 +325,28 @@ namespace Spine {
|
||||
applied = true;
|
||||
|
||||
// Apply mixing from entries first.
|
||||
if (current.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(current, skeleton);
|
||||
if (current.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(current, skeleton, issueEvents);
|
||||
|
||||
// Apply current entry.
|
||||
float animationLast = current.animationLast, animationTime = current.AnimationTime;
|
||||
int timelineCount = current.animation.timelines.Count;
|
||||
Timeline[] timelines = current.animation.timelines.Items;
|
||||
for (int ii = 0; ii < timelineCount; ii++) {
|
||||
Timeline timeline = timelines[ii];
|
||||
if (timeline is EventTimeline)
|
||||
timeline.Apply(skeleton, animationLast, animationTime, events, 1.0f, MixBlend.Setup, MixDirection.In);
|
||||
|
||||
if (issueEvents) {
|
||||
int timelineCount = current.animation.timelines.Count;
|
||||
Timeline[] timelines = current.animation.timelines.Items;
|
||||
for (int ii = 0; ii < timelineCount; ii++) {
|
||||
Timeline timeline = timelines[ii];
|
||||
if (timeline is EventTimeline)
|
||||
timeline.Apply(skeleton, animationLast, animationTime, events, 1.0f, MixBlend.Setup, MixDirection.In);
|
||||
}
|
||||
QueueEvents(current, animationTime);
|
||||
events.Clear(false);
|
||||
}
|
||||
QueueEvents(current, animationTime);
|
||||
events.Clear(false);
|
||||
current.nextAnimationLast = animationTime;
|
||||
current.nextTrackLast = current.trackTime;
|
||||
}
|
||||
|
||||
queue.Drain();
|
||||
if (issueEvents)
|
||||
queue.Drain();
|
||||
return applied;
|
||||
}
|
||||
|
||||
@ -434,11 +440,14 @@ namespace Spine {
|
||||
return mix;
|
||||
}
|
||||
|
||||
/// <summary>Version of <see cref="ApplyMixingFrom"/> only applying EventTimelines for lightweight off-screen updates.</summary>
|
||||
/// <summary>Version of <see cref="ApplyMixingFrom"/> only applying and updating time at
|
||||
/// EventTimelines for lightweight off-screen updates.</summary>
|
||||
/// <param name="issueEvents">When set to false, only animation times of TrackEntries are updated.</param>
|
||||
// Note: This method is not part of the libgdx reference implementation.
|
||||
private float ApplyMixingFromEventTimelinesOnly (TrackEntry to, Skeleton skeleton) {
|
||||
private float ApplyMixingFromEventTimelinesOnly (TrackEntry to, Skeleton skeleton, bool issueEvents) {
|
||||
TrackEntry from = to.mixingFrom;
|
||||
if (from.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(from, skeleton);
|
||||
if (from.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(from, skeleton, issueEvents);
|
||||
|
||||
|
||||
float mix;
|
||||
if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes.
|
||||
@ -452,16 +461,18 @@ namespace Spine {
|
||||
if (eventBuffer == null) return mix;
|
||||
|
||||
float animationLast = from.animationLast, animationTime = from.AnimationTime;
|
||||
int timelineCount = from.animation.timelines.Count;
|
||||
Timeline[] timelines = from.animation.timelines.Items;
|
||||
for (int i = 0; i < timelineCount; i++) {
|
||||
Timeline timeline = timelines[i];
|
||||
if (timeline is EventTimeline)
|
||||
timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, 0, MixBlend.Setup, MixDirection.Out);
|
||||
}
|
||||
if (issueEvents) {
|
||||
int timelineCount = from.animation.timelines.Count;
|
||||
Timeline[] timelines = from.animation.timelines.Items;
|
||||
for (int i = 0; i < timelineCount; i++) {
|
||||
Timeline timeline = timelines[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);
|
||||
if (to.mixDuration > 0) QueueEvents(from, animationTime);
|
||||
this.events.Clear(false);
|
||||
}
|
||||
from.nextAnimationLast = animationTime;
|
||||
from.nextTrackLast = from.trackTime;
|
||||
|
||||
@ -1242,6 +1253,10 @@ namespace Spine {
|
||||
/// If true, the animation will be applied in reverse. Events are not fired when an animation is applied in reverse.</summary>
|
||||
public bool Reverse { get { return reverse; } set { reverse = value; } }
|
||||
|
||||
/// <summary>Returns true if this entry is for the empty animation. See <see cref="AnimationState.SetEmptyAnimation(int, float)"/>,
|
||||
/// <see cref="AnimationState.AddEmptyAnimation(int, float, float)"/>, and <see cref="AnimationState.SetEmptyAnimations(float)"/>.
|
||||
public bool IsEmptyAnimation { get { return animation == AnimationState.EmptyAnimation; } }
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the
|
||||
|
||||
@ -274,7 +274,7 @@ namespace Spine {
|
||||
/// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied transform after
|
||||
/// calling this method is equivalent to the local transform used to compute the world transform, but may not be identical.
|
||||
/// </para></summary>
|
||||
internal void UpdateAppliedTransform () {
|
||||
public void UpdateAppliedTransform () {
|
||||
Bone parent = this.parent;
|
||||
if (parent == null) {
|
||||
ax = worldX - skeleton.x;
|
||||
|
||||
@ -173,11 +173,16 @@ namespace Spine.Unity {
|
||||
int i = 0;
|
||||
if (content.Length >= 3 && content[0] == 0xEF && content[1] == 0xBB && content[2] == 0xBF) // skip potential BOM
|
||||
i = 3;
|
||||
bool openingBraceFound = false;
|
||||
for (; i < numCharsToCheck; ++i) {
|
||||
char c = (char)content[i];
|
||||
if (char.IsWhiteSpace(c))
|
||||
continue;
|
||||
return c == '{';
|
||||
if (!openingBraceFound) {
|
||||
if (c == '{') openingBraceFound = true;
|
||||
else return false;
|
||||
} else
|
||||
return c == '"';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -206,8 +206,10 @@ namespace Spine.Unity {
|
||||
return;
|
||||
UpdateAnimationStatus(deltaTime);
|
||||
|
||||
if (updateMode == UpdateMode.OnlyAnimationStatus)
|
||||
if (updateMode == UpdateMode.OnlyAnimationStatus) {
|
||||
state.ApplyEventTimelinesOnly(skeleton, issueEvents: false);
|
||||
return;
|
||||
}
|
||||
ApplyAnimation();
|
||||
}
|
||||
|
||||
@ -224,7 +226,7 @@ namespace Spine.Unity {
|
||||
if (updateMode != UpdateMode.OnlyEventTimelines)
|
||||
state.Apply(skeleton);
|
||||
else
|
||||
state.ApplyEventTimelinesOnly(skeleton);
|
||||
state.ApplyEventTimelinesOnly(skeleton, issueEvents: true);
|
||||
|
||||
if (_UpdateLocal != null)
|
||||
_UpdateLocal(this);
|
||||
@ -246,6 +248,18 @@ namespace Spine.Unity {
|
||||
if (!wasUpdatedAfterInit) Update(0);
|
||||
base.LateUpdate();
|
||||
}
|
||||
|
||||
public override void OnBecameVisible () {
|
||||
UpdateMode previousUpdateMode = updateMode;
|
||||
updateMode = UpdateMode.FullUpdate;
|
||||
|
||||
// OnBecameVisible is called after LateUpdate()
|
||||
if (previousUpdateMode != UpdateMode.FullUpdate &&
|
||||
previousUpdateMode != UpdateMode.EverythingExceptMesh)
|
||||
Update(0);
|
||||
if (previousUpdateMode != UpdateMode.FullUpdate)
|
||||
LateUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -265,10 +265,13 @@ namespace Spine.Unity {
|
||||
wasUpdatedAfterInit = true;
|
||||
if (updateMode < UpdateMode.OnlyAnimationStatus)
|
||||
return;
|
||||
|
||||
UpdateAnimationStatus(deltaTime);
|
||||
|
||||
if (updateMode == UpdateMode.OnlyAnimationStatus)
|
||||
if (updateMode == UpdateMode.OnlyAnimationStatus) {
|
||||
state.ApplyEventTimelinesOnly(skeleton, issueEvents: false);
|
||||
return;
|
||||
}
|
||||
ApplyAnimation();
|
||||
}
|
||||
|
||||
@ -303,7 +306,7 @@ namespace Spine.Unity {
|
||||
if (updateMode != UpdateMode.OnlyEventTimelines)
|
||||
state.Apply(skeleton);
|
||||
else
|
||||
state.ApplyEventTimelinesOnly(skeleton);
|
||||
state.ApplyEventTimelinesOnly(skeleton, issueEvents: true);
|
||||
|
||||
if (UpdateLocal != null)
|
||||
UpdateLocal(this);
|
||||
|
||||
@ -140,6 +140,18 @@ namespace Spine.Unity {
|
||||
base.LateUpdate();
|
||||
}
|
||||
|
||||
public override void OnBecameVisible () {
|
||||
UpdateMode previousUpdateMode = updateMode;
|
||||
updateMode = UpdateMode.FullUpdate;
|
||||
|
||||
// OnBecameVisible is called after LateUpdate()
|
||||
if (previousUpdateMode != UpdateMode.FullUpdate &&
|
||||
previousUpdateMode != UpdateMode.EverythingExceptMesh)
|
||||
Update();
|
||||
if (previousUpdateMode != UpdateMode.FullUpdate)
|
||||
LateUpdate();
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class MecanimTranslator {
|
||||
|
||||
|
||||
@ -554,11 +554,13 @@ namespace Spine.Unity {
|
||||
OnMeshAndMaterialsUpdated(this);
|
||||
}
|
||||
|
||||
public void OnBecameVisible () {
|
||||
public virtual void OnBecameVisible () {
|
||||
UpdateMode previousUpdateMode = updateMode;
|
||||
updateMode = UpdateMode.FullUpdate;
|
||||
|
||||
// OnBecameVisible is called after LateUpdate()
|
||||
if (previousUpdateMode != UpdateMode.FullUpdate)
|
||||
LateUpdate(); // OnBecameVisible is called after LateUpdate()
|
||||
LateUpdate();
|
||||
}
|
||||
|
||||
public void OnBecameInvisible () {
|
||||
|
||||
@ -79,7 +79,7 @@ namespace Spine.Unity.Editor {
|
||||
if (timelineClip == null)
|
||||
return;
|
||||
|
||||
float blendInDur = (float)timelineClip.blendInDuration;
|
||||
float blendInDur = System.Math.Max((float)timelineClip.blendInDuration, (float)timelineClip.easeInDuration);
|
||||
bool isBlendingNow = blendInDur > 0;
|
||||
bool wasBlendingBefore = timelineClipInfo.previousBlendInDuration > 0;
|
||||
|
||||
|
||||
@ -38,8 +38,10 @@ namespace Spine.Unity.Playables {
|
||||
public class SpineAnimationStateMixerBehaviour : PlayableBehaviour {
|
||||
|
||||
float[] lastInputWeights;
|
||||
bool lastAnyTrackPlaying = false;
|
||||
bool lastAnyClipPlaying = false;
|
||||
public int trackIndex;
|
||||
ScriptPlayable<SpineAnimationStateBehaviour>[] startingClips
|
||||
= new ScriptPlayable<SpineAnimationStateBehaviour>[2];
|
||||
|
||||
IAnimationStateComponent animationStateComponent;
|
||||
bool pauseWithDirector = true;
|
||||
@ -135,60 +137,81 @@ namespace Spine.Unity.Playables {
|
||||
this.lastInputWeights[i] = default(float);
|
||||
}
|
||||
var lastInputWeights = this.lastInputWeights;
|
||||
bool anyTrackPlaying = false;
|
||||
int numStartingClips = 0;
|
||||
bool anyClipPlaying = 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;
|
||||
bool clipStarted = lastInputWeight == 0 && inputWeight > 0;
|
||||
if (inputWeight > 0)
|
||||
anyTrackPlaying = true;
|
||||
anyClipPlaying = true;
|
||||
lastInputWeights[i] = inputWeight;
|
||||
|
||||
if (trackStarted) {
|
||||
ScriptPlayable<SpineAnimationStateBehaviour> inputPlayable = (ScriptPlayable<SpineAnimationStateBehaviour>)playable.GetInput(i);
|
||||
SpineAnimationStateBehaviour clipData = inputPlayable.GetBehaviour();
|
||||
|
||||
pauseWithDirector = !clipData.dontPauseWithDirector;
|
||||
endAtClipEnd = !clipData.dontEndWithClip;
|
||||
endMixOutDuration = clipData.endMixOutDuration;
|
||||
|
||||
if (clipData.animationReference == null) {
|
||||
float mixDuration = clipData.customDuration ? clipData.mixDuration : state.Data.DefaultMix;
|
||||
state.SetEmptyAnimation(trackIndex, mixDuration);
|
||||
} else {
|
||||
if (clipData.animationReference.Animation != null) {
|
||||
Spine.TrackEntry trackEntry = state.SetAnimation(trackIndex, clipData.animationReference.Animation, clipData.loop);
|
||||
|
||||
trackEntry.EventThreshold = clipData.eventThreshold;
|
||||
trackEntry.DrawOrderThreshold = clipData.drawOrderThreshold;
|
||||
trackEntry.TrackTime = (float)inputPlayable.GetTime() * (float)inputPlayable.GetSpeed();
|
||||
trackEntry.TimeScale = (float)inputPlayable.GetSpeed();
|
||||
trackEntry.AttachmentThreshold = clipData.attachmentThreshold;
|
||||
trackEntry.HoldPrevious = clipData.holdPrevious;
|
||||
|
||||
if (clipData.customDuration)
|
||||
trackEntry.MixDuration = clipData.mixDuration;
|
||||
|
||||
timelineStartedTrackEntry = trackEntry;
|
||||
}
|
||||
//else Debug.LogWarningFormat("Animation named '{0}' not found", clipData.animationName);
|
||||
}
|
||||
|
||||
// Ensure that the first frame ends with an updated mesh.
|
||||
if (skeletonAnimation) {
|
||||
skeletonAnimation.Update(0);
|
||||
skeletonAnimation.LateUpdate();
|
||||
} else if (skeletonGraphic) {
|
||||
skeletonGraphic.Update(0);
|
||||
skeletonGraphic.LateUpdate();
|
||||
}
|
||||
if (clipStarted && numStartingClips < 2) {
|
||||
ScriptPlayable<SpineAnimationStateBehaviour> clipPlayable = (ScriptPlayable<SpineAnimationStateBehaviour>)playable.GetInput(i);
|
||||
startingClips[numStartingClips++] = clipPlayable;
|
||||
}
|
||||
}
|
||||
if (lastAnyTrackPlaying && !anyTrackPlaying)
|
||||
// unfortunately order of clips can be wrong when two start at the same time, we have to sort clips
|
||||
if (numStartingClips == 2) {
|
||||
ScriptPlayable<SpineAnimationStateBehaviour> clipPlayable0 = startingClips[0];
|
||||
ScriptPlayable<SpineAnimationStateBehaviour> clipPlayable1 = startingClips[1];
|
||||
if (clipPlayable0.GetDuration() > clipPlayable1.GetDuration()) { // swap, clip 0 ends after clip 1
|
||||
startingClips[0] = clipPlayable1;
|
||||
startingClips[1] = clipPlayable0;
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < numStartingClips; ++j) {
|
||||
ScriptPlayable<SpineAnimationStateBehaviour> clipPlayable = startingClips[j];
|
||||
SpineAnimationStateBehaviour clipData = clipPlayable.GetBehaviour();
|
||||
pauseWithDirector = !clipData.dontPauseWithDirector;
|
||||
endAtClipEnd = !clipData.dontEndWithClip;
|
||||
endMixOutDuration = clipData.endMixOutDuration;
|
||||
|
||||
if (clipData.animationReference == null) {
|
||||
float mixDuration = clipData.customDuration ? clipData.mixDuration : state.Data.DefaultMix;
|
||||
state.SetEmptyAnimation(trackIndex, mixDuration);
|
||||
} else {
|
||||
if (clipData.animationReference.Animation != null) {
|
||||
TrackEntry currentEntry = state.GetCurrent(trackIndex);
|
||||
Spine.TrackEntry trackEntry;
|
||||
if (currentEntry == null && (clipData.customDuration && clipData.mixDuration > 0)) {
|
||||
state.SetEmptyAnimation(trackIndex, 0); // ease in requires empty animation
|
||||
trackEntry = state.AddAnimation(trackIndex, clipData.animationReference.Animation, clipData.loop, 0);
|
||||
} else
|
||||
trackEntry = state.SetAnimation(trackIndex, clipData.animationReference.Animation, clipData.loop);
|
||||
|
||||
trackEntry.EventThreshold = clipData.eventThreshold;
|
||||
trackEntry.DrawOrderThreshold = clipData.drawOrderThreshold;
|
||||
trackEntry.TrackTime = (float)clipPlayable.GetTime() * (float)clipPlayable.GetSpeed();
|
||||
trackEntry.TimeScale = (float)clipPlayable.GetSpeed();
|
||||
trackEntry.AttachmentThreshold = clipData.attachmentThreshold;
|
||||
trackEntry.HoldPrevious = clipData.holdPrevious;
|
||||
|
||||
if (clipData.customDuration)
|
||||
trackEntry.MixDuration = clipData.mixDuration;
|
||||
|
||||
timelineStartedTrackEntry = trackEntry;
|
||||
}
|
||||
//else Debug.LogWarningFormat("Animation named '{0}' not found", clipData.animationName);
|
||||
}
|
||||
|
||||
// Ensure that the first frame ends with an updated mesh.
|
||||
if (skeletonAnimation) {
|
||||
skeletonAnimation.Update(0);
|
||||
skeletonAnimation.LateUpdate();
|
||||
} else if (skeletonGraphic) {
|
||||
skeletonGraphic.Update(0);
|
||||
skeletonGraphic.LateUpdate();
|
||||
}
|
||||
}
|
||||
startingClips[0] = startingClips[1] = ScriptPlayable<SpineAnimationStateBehaviour>.Null;
|
||||
if (lastAnyClipPlaying && !anyClipPlaying)
|
||||
HandleClipEnd();
|
||||
this.lastAnyTrackPlaying = anyTrackPlaying;
|
||||
this.lastAnyClipPlaying = anyClipPlaying;
|
||||
}
|
||||
|
||||
#if SPINE_EDITMODEPOSE
|
||||
@ -251,25 +274,25 @@ namespace Spine.Unity.Playables {
|
||||
if (fromAnimation != null && mixDuration > 0 && toClipTime < mixDuration) {
|
||||
dummyAnimationState = dummyAnimationState ?? new AnimationState(skeletonComponent.SkeletonDataAsset.GetAnimationStateData());
|
||||
|
||||
var toTrack = dummyAnimationState.GetCurrent(0);
|
||||
var fromTrack = toTrack != null ? toTrack.MixingFrom : null;
|
||||
bool isAnimationTransitionMatch = (toTrack != null && toTrack.Animation == toAnimation && fromTrack != null && fromTrack.Animation == fromAnimation);
|
||||
var toEntry = dummyAnimationState.GetCurrent(0);
|
||||
var fromEntry = toEntry != null ? toEntry.MixingFrom : null;
|
||||
bool isAnimationTransitionMatch = (toEntry != null && toEntry.Animation == toAnimation && fromEntry != null && fromEntry.Animation == fromAnimation);
|
||||
|
||||
if (!isAnimationTransitionMatch) {
|
||||
dummyAnimationState.ClearTracks();
|
||||
fromTrack = dummyAnimationState.SetAnimation(0, fromAnimation, fromClipLoop);
|
||||
fromTrack.AllowImmediateQueue();
|
||||
fromEntry = dummyAnimationState.SetAnimation(0, fromAnimation, fromClipLoop);
|
||||
fromEntry.AllowImmediateQueue();
|
||||
if (toAnimation != null) {
|
||||
toTrack = dummyAnimationState.SetAnimation(0, toAnimation, clipData.loop);
|
||||
toTrack.HoldPrevious = clipData.holdPrevious;
|
||||
toEntry = dummyAnimationState.SetAnimation(0, toAnimation, clipData.loop);
|
||||
toEntry.HoldPrevious = clipData.holdPrevious;
|
||||
}
|
||||
}
|
||||
|
||||
// Update track times.
|
||||
fromTrack.TrackTime = fromClipTime;
|
||||
if (toTrack != null) {
|
||||
toTrack.TrackTime = toClipTime;
|
||||
toTrack.MixTime = toClipTime;
|
||||
fromEntry.TrackTime = fromClipTime;
|
||||
if (toEntry != null) {
|
||||
toEntry.TrackTime = toClipTime;
|
||||
toEntry.MixTime = toClipTime;
|
||||
}
|
||||
|
||||
// Apply Pose
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"name": "com.esotericsoftware.spine.timeline",
|
||||
"displayName": "Spine Timeline Extensions",
|
||||
"description": "This plugin provides integration of spine-unity for the Unity Timeline.\n\nPrerequisites:\nIt requires a working installation of the spine-unity runtime (via the spine-unity unitypackage), version 4.0.\n(See http://esotericsoftware.com/git/spine-runtimes/spine-unity)",
|
||||
"version": "4.0.5",
|
||||
"version": "4.0.6",
|
||||
"unity": "2018.3",
|
||||
"author": {
|
||||
"name": "Esoteric Software",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"name": "com.esotericsoftware.spine.timeline",
|
||||
"displayName": "Spine Timeline Extensions",
|
||||
"description": "This plugin provides integration of spine-unity for the Unity Timeline.\n\nPrerequisites:\nIt requires a working installation of the spine-unity and spine-csharp runtimes as UPM packages (not as spine-unity unitypackage), version 4.0.\n(See http://esotericsoftware.com/git/spine-runtimes/spine-unity)",
|
||||
"version": "4.0.5",
|
||||
"version": "4.0.6",
|
||||
"unity": "2018.3",
|
||||
"author": {
|
||||
"name": "Esoteric Software",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user