Merge branch '3.8' into 3.9-beta

This commit is contained in:
badlogic 2020-04-13 10:29:36 +02:00
commit d885ae45a2
10 changed files with 181 additions and 45 deletions

View File

@ -621,7 +621,7 @@ package spine {
}
// Path constraint timelines.
var paths : Object = map["paths"];
var paths : Object = map["path"];
for (var pathName : String in paths) {
var index : int = skeletonData.findPathConstraintIndex(pathName);
if (index == -1) throw new Error("Path constraint not found: " + pathName);

View File

@ -143,7 +143,7 @@ namespace Spine {
/// apply animations on top of each other (layered).</param>
/// <param name="blend"> Controls how mixing is applied when <code>alpha</code> < 1.</param>
/// <param name="direction"> Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions,
/// such as <see cref="DrawOrderTimeline"/> or <see cref="AttachmentTimeline"/>.</param>
/// such as <see cref="DrawOrderTimeline"/> or <see cref="AttachmentTimeline"/>, and other such as {@link ScaleTimeline}.</param>
void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha, MixBlend blend, MixDirection direction);
/// <summary>Uniquely encodes both the type of this timeline and the skeleton property that it affects.</summary>
int PropertyId { get; }
@ -973,18 +973,14 @@ namespace Spine {
string attachmentName;
Slot slot = skeleton.slots.Items[slotIndex];
if (!slot.bone.active) return;
if (direction == MixDirection.Out && blend == MixBlend.Setup) {
attachmentName = slot.data.attachmentName;
slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
if (direction == MixDirection.Out) {
if (blend == MixBlend.Setup) SetAttachment(skeleton, slot, slot.data.attachmentName);
return;
}
float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame.
if (blend == MixBlend.Setup || blend == MixBlend.First) {
attachmentName = slot.data.attachmentName;
slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
}
if (blend == MixBlend.Setup || blend == MixBlend.First) SetAttachment(skeleton, slot, slot.data.attachmentName);
return;
}
@ -994,7 +990,10 @@ namespace Spine {
else
frameIndex = Animation.BinarySearch(frames, time) - 1;
attachmentName = attachmentNames[frameIndex];
SetAttachment(skeleton, slot, attachmentNames[frameIndex]);
}
private void SetAttachment (Skeleton skeleton, Slot slot, string attachmentName) {
slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
}
}
@ -1332,8 +1331,8 @@ namespace Spine {
MixDirection direction) {
ExposedList<Slot> drawOrder = skeleton.drawOrder;
ExposedList<Slot> slots = skeleton.slots;
if (direction == MixDirection.Out && blend == MixBlend.Setup) {
Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count);
if (direction == MixDirection.Out) {
if (blend == MixBlend.Setup) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count);
return;
}

View File

@ -66,12 +66,12 @@ namespace Spine {
/// (which affects B and C). Without using D to mix out, A would be applied fully until mixing completes, then snap into
/// place.
internal const int HoldMix = 3;
/// 1) An attachment timeline in a subsequent track entry sets the attachment for the same slot as this attachment
/// timeline.<para />
/// Result: This attachment timeline will not use MixDirection.out, which would otherwise show the setup mode attachment (or
/// none if not visible in setup mode). This allows deform timelines to be applied for the subsequent entry to mix from, rather
/// than mixing from the setup pose.
internal const int NotLast = 4;
/// 1) This is the last attachment timeline to set the attachment for a slot.<para />
/// Result: Don't apply this timeline when mixing out. Attachment timelines that are not last are applied when mixing out so
/// any deform timelines are applied and subsequent entries can mix from that deform.
internal const int Last = 4;
internal const int Setup = 1, Current = 2;
protected AnimationStateData data;
private readonly ExposedList<TrackEntry> tracks = new ExposedList<TrackEntry>();
@ -94,6 +94,7 @@ namespace Spine {
private readonly HashSet<int> propertyIDs = new HashSet<int>();
private bool animationsChanged;
private float timeScale = 1;
private int unkeyedState;
private readonly Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
@ -196,8 +197,8 @@ namespace Spine {
}
/// <summary>
/// Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the
/// animation state can be applied to multiple skeletons to pose them identically.</summary>
/// Poses the skeleton using the track entry animations. The animation state is not changed, so can be applied to multiple
/// skeletons to pose them identically.</summary>
/// <returns>True if any animations were applied.</returns>
public bool Apply (Skeleton skeleton) {
if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
@ -227,8 +228,13 @@ namespace Spine {
var timelines = current.animation.timelines;
var timelinesItems = timelines.Items;
if ((i == 0 && mix == 1) || blend == MixBlend.Add) {
for (int ii = 0; ii < timelineCount; ii++)
timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In);
for (int ii = 0; ii < timelineCount; ii++) {
var timeline = timelinesItems[ii];
if (timeline is AttachmentTimeline)
ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, blend, true);
else
timeline.Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In);
}
} else {
var timelineMode = current.timelineMode.Items;
@ -238,11 +244,13 @@ namespace Spine {
for (int ii = 0; ii < timelineCount; ii++) {
Timeline timeline = timelinesItems[ii];
MixBlend timelineBlend = (timelineMode[ii] & AnimationState.NotLast - 1) == AnimationState.Subsequent ? blend : MixBlend.Setup;
MixBlend timelineBlend = (timelineMode[ii] & AnimationState.Last - 1) == AnimationState.Subsequent ? blend : MixBlend.Setup;
var rotateTimeline = timeline as RotateTimeline;
if (rotateTimeline != null)
ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation,
ii << 1, firstFrame);
else if (timeline is AttachmentTimeline)
ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, blend, true);
else
timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelineBlend, MixDirection.In);
}
@ -253,6 +261,20 @@ namespace Spine {
current.nextTrackLast = current.trackTime;
}
// Set slots attachments to the setup pose, if needed. This occurs if an animation that is mixing out sets attachments so
// subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or
// the time is before the first key).
int setupState = unkeyedState + Setup;
var slots = skeleton.slots.Items;
for (int i = 0, n = skeleton.slots.Count; i < n; i++) {
Slot slot = (Slot)slots[i];
if (slot.attachmentState == setupState) {
string attachmentName = slot.data.attachmentName;
slot.Attachment = (attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName));
}
}
unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot.
queue.Drain();
return applied;
}
@ -296,14 +318,10 @@ namespace Spine {
MixDirection direction = MixDirection.Out;
MixBlend timelineBlend;
float alpha;
switch (timelineMode[i] & AnimationState.NotLast - 1) {
switch (timelineMode[i] & AnimationState.Last - 1) {
case AnimationState.Subsequent:
timelineBlend = blend;
if (!attachments && timeline is AttachmentTimeline) {
if ((timelineMode[i] & AnimationState.NotLast) == AnimationState.NotLast) continue;
timelineBlend = MixBlend.Setup;
}
if (!drawOrder && timeline is DrawOrderTimeline) continue;
timelineBlend = blend;
alpha = alphaMix;
break;
case AnimationState.First:
@ -321,22 +339,18 @@ namespace Spine {
break;
}
from.totalAlpha += alpha;
var rotateTimeline = timeline as RotateTimeline;
if (rotateTimeline != null) {
ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation,
i << 1, firstFrame);
} else if (timeline is AttachmentTimeline) {
// If not showing attachments: do nothing if this is the last timeline, else apply the timeline so
// subsequent timelines see any deform, but don't set attachmentState to Current.
if (!attachments && (timelineMode[i] & Last) != 0) continue;
ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, timelineBlend, attachments);
} else {
if (timelineBlend == MixBlend.Setup) {
if (timeline is AttachmentTimeline) {
if (attachments || (timelineMode[i] & AnimationState.NotLast) == AnimationState.NotLast) direction = MixDirection.In;
} else if (timeline is DrawOrderTimeline) {
if (drawOrder) {
direction = MixDirection.In;
}
}
}
if (drawOrder && timeline is DrawOrderTimeline && timelineBlend == MixBlend.Setup)
direction = MixDirection.In;
timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, timelineBlend, direction);
}
}
@ -350,6 +364,42 @@ namespace Spine {
return mix;
}
/// <summary> Applies the attachment timeline and sets <see cref="Slot.attachmentState"/>.</summary>
/// <param name="attachments">False when: 1) the attachment timeline is mixing out, 2) mix < attachmentThreshold, and 3) the timeline
/// is not the last timeline to set the slot's attachment. In that case the timeline is applied only so subsequent
/// timelines see any deform.</param>
private void ApplyAttachmentTimeline (AttachmentTimeline timeline, Skeleton skeleton, float time, MixBlend blend,
bool attachments) {
Slot slot = skeleton.slots.Items[timeline.slotIndex];
if (!slot.bone.active) return;
float[] frames = timeline.frames;
if (time < frames[0]) { // Time is before first frame.
if (blend == MixBlend.Setup || blend == MixBlend.First)
SetAttachment(skeleton, slot, slot.data.attachmentName, attachments);
}
else {
int frameIndex;
if (time >= frames[frames.Length - 1]) // Time is after last frame.
frameIndex = frames.Length - 1;
else
frameIndex = Animation.BinarySearch(frames, time) - 1;
SetAttachment(skeleton, slot, timeline.attachmentNames[frameIndex], attachments);
}
// If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later.
if (slot.attachmentState <= unkeyedState) slot.attachmentState = unkeyedState + Setup;
}
private void SetAttachment (Skeleton skeleton, Slot slot, String attachmentName, bool attachments) {
slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName);
if (attachments) slot.attachmentState = unkeyedState + Current;
}
/// <summary>
/// Applies the rotate timeline, mixing with the current pose while keeping the same rotation direction chosen as the shortest
/// the first time the mixing was applied.</summary>
static private void ApplyRotateTimeline (RotateTimeline timeline, Skeleton skeleton, float time, float alpha, MixBlend blend,
float[] timelinesRotation, int i, bool firstFrame) {
@ -803,7 +853,7 @@ namespace Spine {
for (int i = 0; i < timelinesCount; i++) {
if (timelines[i] is AttachmentTimeline) {
AttachmentTimeline timeline = (AttachmentTimeline)timelines[i];
if (!propertyIDs.Add(timeline.slotIndex)) timelineMode[i] |= AnimationState.NotLast;
if (propertyIDs.Add(timeline.slotIndex)) timelineMode[i] |= AnimationState.Last;
}
}
}

View File

@ -45,6 +45,7 @@ namespace Spine {
internal Attachment attachment;
internal float attachmentTime;
internal ExposedList<float> deform = new ExposedList<float>();
internal int attachmentState;
public Slot (SlotData data, Bone bone) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");

View File

@ -0,0 +1,87 @@
<html>
<script src="../../build/spine-webgl.js"></script>
<style>
* { margin: 0; padding: 0; }
body, html { height: 100% }
canvas { position: absolute; width: 100% ;height: 100%; }
</style>
<body>
<canvas id="canvas" style="background: red;"></canvas>
</body>
<script>
var FILE = "spineboy-pro";
var ANIMATION = "walk";
var SCALE = 0.5;
var canvas, context, gl, renderer, input, assetManager;
var timeKeeper;
var skeleton;
var animationState;
function init() {
canvas = document.getElementById("canvas");
canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight;
context = new spine.webgl.ManagedWebGLRenderingContext(canvas, { alpha: false });
gl = context.gl;
renderer = new spine.webgl.SceneRenderer(canvas, context);
assetManager = new spine.webgl.AssetManager(context, "../example/assets/");
input = new spine.webgl.Input(canvas);
assetManager.loadTextureAtlas(FILE.replace("-pro", "").replace("-ess", "") + "-pma.atlas");
assetManager.loadBinary(FILE + ".skel");
timeKeeper = new spine.TimeKeeper();
requestAnimationFrame(load);
}
var run = true;
function load() {
timeKeeper.update();
if (assetManager.isLoadingComplete()) {
var atlas = assetManager.get(FILE.replace("-pro", "").replace("-ess", "") + "-pma.atlas");
var atlasLoader = new spine.AtlasAttachmentLoader(atlas);
var skeletonBinary = new spine.SkeletonBinary(atlasLoader);
skeletonBinary.scale = SCALE;
var time = new spine.TimeKeeper()
var skeletonData = skeletonBinary.readSkeletonData(assetManager.get(FILE + ".skel"));
skeleton = new spine.Skeleton(skeletonData);
var stateData = new spine.AnimationStateData(skeleton.data);
animationState = new spine.AnimationState(stateData);
stateData.defaultMix = 0;
animationState.setAnimation(0, ANIMATION, true);
requestAnimationFrame(render);
} else {
requestAnimationFrame(load);
}
}
function render() {
var start = Date.now()
timeKeeper.update();
var delta = timeKeeper.delta;
animationState.update(delta);
animationState.apply(skeleton);
skeleton.updateWorldTransform();
gl.clearColor(0.2, 0.2, 0.2, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
renderer.resize(spine.webgl.ResizeMode.Fit);
renderer.begin();
renderer.drawSkeleton(skeleton, true);
renderer.end();
requestAnimationFrame(render)
}
init();
</script>
</html>

View File

@ -87,7 +87,7 @@ namespace Spine.Unity.Editor {
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
EditorGUILayout.HelpBox("It's ideal to add BoundingBoxFollower to a separate child GameObject of the Spine GameObject.", MessageType.Warning);
if (GUILayout.Button(new GUIContent("Move BoundingBoxFollower to new GameObject", Icons.boundingBox), GUILayout.Height(50f))) {
if (GUILayout.Button(new GUIContent("Move BoundingBoxFollower to new GameObject", Icons.boundingBox), GUILayout.Height(30f))) {
AddBoundingBoxFollowerChild(skeletonRendererValue, follower);
DestroyImmediate(follower);
return;

View File

@ -220,13 +220,12 @@ namespace Spine.Unity.Editor {
#region Button
const float CenterButtonMaxWidth = 270f;
const float CenterButtonHeight = 35f;
const float CenterButtonHeight = 30f;
static GUIStyle spineButtonStyle;
static GUIStyle SpineButtonStyle {
get {
if (spineButtonStyle == null) {
spineButtonStyle = new GUIStyle(GUI.skin.button);
spineButtonStyle.name = "Spine Button";
spineButtonStyle.padding = new RectOffset(10, 10, 10, 10);
}
return spineButtonStyle;

View File

@ -182,7 +182,7 @@ namespace Spine.Unity.Examples {
if (component.enabled && component.SkeletonRenderer != null && extraRenderersNeeded > 0) {
EditorGUILayout.HelpBox(string.Format("Insufficient parts renderers. Some parts will not be rendered."), MessageType.Warning);
string addMissingLabel = string.Format("Add the missing renderer{1} ({0}) ", extraRenderersNeeded, SpineInspectorUtility.PluralThenS(extraRenderersNeeded));
if (GUILayout.Button(addMissingLabel, GUILayout.Height(40f))) {
if (GUILayout.Button(addMissingLabel, GUILayout.Height(30f))) {
AddPartsRenderer(extraRenderersNeeded);
DetectOrphanedPartsRenderers(component);
partsRendererInitRequired = true;