This commit is contained in:
badlogic 2018-07-18 12:04:18 +02:00
commit 5898057c96
336 changed files with 1970 additions and 1504 deletions

View File

@ -53,10 +53,6 @@ namespace Spine {
internal float a, b, worldX;
internal float c, d, worldY;
// internal float worldSignX, worldSignY;
// public float WorldSignX { get { return worldSignX; } }
// public float WorldSignY { get { return worldSignY; } }
internal bool sorted;
public BoneData Data { get { return data; } }
@ -152,27 +148,13 @@ namespace Spine {
Bone parent = this.parent;
if (parent == null) { // Root bone.
float rotationY = rotation + 90 + shearY;
float la = MathUtils.CosDeg(rotation + shearX) * scaleX;
float lb = MathUtils.CosDeg(rotationY) * scaleY;
float lc = MathUtils.SinDeg(rotation + shearX) * scaleX;
float ld = MathUtils.SinDeg(rotationY) * scaleY;
if (skeleton.flipX) {
x = -x;
la = -la;
lb = -lb;
}
if (skeleton.flipY != yDown) {
y = -y;
lc = -lc;
ld = -ld;
}
a = la;
b = lb;
c = lc;
d = ld;
worldX = x + skeleton.x;
worldY = y + skeleton.y;
float rotationY = rotation + 90 + shearY, sx = skeleton.scaleX, sy = skeleton.scaleY;
a = MathUtils.CosDeg(rotation + shearX) * scaleX * sx;
b = MathUtils.CosDeg(rotationY) * scaleY * sy;
c = MathUtils.SinDeg(rotation + shearX) * scaleX * sx;
d = MathUtils.SinDeg(rotationY) * scaleY * sy;
worldX = x * sx + skeleton.x;
worldY = y * sy + skeleton.y;
return;
}
@ -228,8 +210,8 @@ namespace Spine {
case TransformMode.NoScale:
case TransformMode.NoScaleOrReflection: {
float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation);
float za = pa * cos + pb * sin;
float zc = pc * cos + pd * sin;
float za = (pa * cos + pb * sin) / skeleton.scaleX;
float zc = (pc * cos + pd * sin) / skeleton.scaleY;
float s = (float)Math.Sqrt(za * za + zc * zc);
if (s > 0.00001f) s = 1 / s;
za *= s;
@ -242,26 +224,18 @@ namespace Spine {
float lb = MathUtils.CosDeg(90 + shearY) * scaleY;
float lc = MathUtils.SinDeg(shearX) * scaleX;
float ld = MathUtils.SinDeg(90 + shearY) * scaleY;
if (data.transformMode != TransformMode.NoScaleOrReflection? pa * pd - pb* pc< 0 : skeleton.flipX != skeleton.flipY) {
zb = -zb;
zd = -zd;
}
a = za * la + zb * lc;
b = za * lb + zb * ld;
c = zc * la + zd * lc;
d = zc * lb + zd * ld;
return;
d = zc * lb + zd * ld;
break;
}
}
if (skeleton.flipX) {
a = -a;
b = -b;
}
if (skeleton.flipY != Bone.yDown) {
c = -c;
d = -d;
}
a *= skeleton.scaleX;
b *= skeleton.scaleX;
c *= skeleton.scaleY;
d *= skeleton.scaleY;
}
public void SetToSetupPose () {

View File

@ -45,7 +45,7 @@ namespace Spine {
internal Skin skin;
internal float r = 1, g = 1, b = 1, a = 1;
internal float time;
internal bool flipX, flipY;
internal float scaleX, scaleY;
internal float x, y;
public SkeletonData Data { get { return data; } }
@ -64,8 +64,14 @@ namespace Spine {
public float Time { get { return time; } set { time = value; } }
public float X { get { return x; } set { x = value; } }
public float Y { get { return y; } set { y = value; } }
public bool FlipX { get { return flipX; } set { flipX = value; } }
public bool FlipY { get { return flipY; } set { flipY = value; } }
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
public float ScaleY { get { return scaleY; } set { scaleY = value; } }
[Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")]
public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } }
[Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")]
public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } }
public Bone RootBone {
get { return bones.Count == 0 ? null : bones.Items[0]; }

View File

@ -50,7 +50,7 @@ public class TestHarness extends ApplicationAdapter {
skeleton = new Skeleton(skeletonData);
skeleton.setPosition(320, 590);
skeleton.flipY = true;
skeleton.setScaleY(-1);
AnimationStateData stateData = new AnimationStateData(skeletonData);
state = new AnimationState(stateData);

View File

@ -37,6 +37,7 @@ import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.FloatArray;
import com.esotericsoftware.spine.attachments.Attachment;
import com.esotericsoftware.spine.attachments.VertexAttachment;
@ -1294,11 +1295,12 @@ public class Animation {
}
}
/** Changes an IK constraint's {@link IkConstraint#getMix()} and {@link IkConstraint#getBendDirection()}. */
/** Changes an IK constraint's {@link IkConstraint#getMix()}, {@link IkConstraint#getBendDirection()}, and
* {@link IkConstraint#getStretch()}. */
static public class IkConstraintTimeline extends CurveTimeline {
static public final int ENTRIES = 3;
static private final int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1;
static private final int MIX = 1, BEND_DIRECTION = 2;
static public final int ENTRIES = 4;
static private final int PREV_TIME = -4, PREV_MIX = -3, PREV_BEND_DIRECTION = -2, PREV_STRETCH = -1;
static private final int MIX = 1, BEND_DIRECTION = 2, STRETCH = 3;
int ikConstraintIndex;
private final float[] frames; // time, mix, bendDirection, ...
@ -1328,11 +1330,12 @@ public class Animation {
}
/** Sets the time in seconds, mix, and bend direction for the specified key frame. */
public void setFrame (int frameIndex, float time, float mix, int bendDirection) {
public void setFrame (int frameIndex, float time, float mix, int bendDirection, boolean stretch) {
frameIndex *= ENTRIES;
frames[frameIndex] = time;
frames[frameIndex + MIX] = mix;
frames[frameIndex + BEND_DIRECTION] = bendDirection;
frames[frameIndex + STRETCH] = stretch ? 1 : 0;
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixBlend blend,
@ -1345,10 +1348,12 @@ public class Animation {
case setup:
constraint.mix = constraint.data.mix;
constraint.bendDirection = constraint.data.bendDirection;
constraint.stretch = constraint.data.stretch;
return;
case first:
constraint.mix += (constraint.data.mix - constraint.mix) * alpha;
constraint.bendDirection = constraint.data.bendDirection;
constraint.stretch = constraint.data.stretch;
}
return;
}
@ -1356,11 +1361,19 @@ public class Animation {
if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
if (blend == setup) {
constraint.mix = constraint.data.mix + (frames[frames.length + PREV_MIX] - constraint.data.mix) * alpha;
constraint.bendDirection = direction == out ? constraint.data.bendDirection
: (int)frames[frames.length + PREV_BEND_DIRECTION];
if (direction == out) {
constraint.bendDirection = constraint.data.bendDirection;
constraint.stretch = constraint.data.stretch;
} else {
constraint.bendDirection = (int)frames[frames.length + PREV_BEND_DIRECTION];
constraint.stretch = frames[frames.length + PREV_STRETCH] != 0;
}
} else {
constraint.mix += (frames[frames.length + PREV_MIX] - constraint.mix) * alpha;
if (direction == in) constraint.bendDirection = (int)frames[frames.length + PREV_BEND_DIRECTION];
if (direction == in) {
constraint.bendDirection = (int)frames[frames.length + PREV_BEND_DIRECTION];
constraint.stretch = frames[frames.length + PREV_STRETCH] != 0;
}
}
return;
}
@ -1373,11 +1386,19 @@ public class Animation {
if (blend == setup) {
constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha;
constraint.bendDirection = direction == out ? constraint.data.bendDirection
: (int)frames[frame + PREV_BEND_DIRECTION];
if (direction == out) {
constraint.bendDirection = constraint.data.bendDirection;
constraint.stretch = constraint.data.stretch;
} else {
constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
constraint.stretch = frames[frame + PREV_STRETCH] != 0;
}
} else {
constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha;
if (direction == in) constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
if (direction == in) {
constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
constraint.stretch = frames[frame + PREV_STRETCH] != 0;
}
}
}
}

View File

@ -30,13 +30,12 @@
package com.esotericsoftware.spine;
import static com.esotericsoftware.spine.utils.SpineUtils.*;
import static com.badlogic.gdx.math.Matrix3.*;
import static com.esotericsoftware.spine.utils.SpineUtils.*;
import com.badlogic.gdx.math.Matrix3;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.esotericsoftware.spine.BoneData.TransformMode;
/** Stores a bone's current pose.
* <p>
@ -111,28 +110,14 @@ public class Bone implements Updatable {
Bone parent = this.parent;
if (parent == null) { // Root bone.
float rotationY = rotation + 90 + shearY;
float la = cosDeg(rotation + shearX) * scaleX;
float lb = cosDeg(rotationY) * scaleY;
float lc = sinDeg(rotation + shearX) * scaleX;
float ld = sinDeg(rotationY) * scaleY;
Skeleton skeleton = this.skeleton;
if (skeleton.flipX) {
x = -x;
la = -la;
lb = -lb;
}
if (skeleton.flipY) {
y = -y;
lc = -lc;
ld = -ld;
}
a = la;
b = lb;
c = lc;
d = ld;
worldX = x + skeleton.x;
worldY = y + skeleton.y;
float rotationY = rotation + 90 + shearY, sx = skeleton.scaleX, sy = skeleton.scaleY;
a = cosDeg(rotation + shearX) * scaleX * sx;
b = cosDeg(rotationY) * scaleY * sy;
c = sinDeg(rotation + shearX) * scaleX * sx;
d = sinDeg(rotationY) * scaleY * sy;
worldX = x * sx + skeleton.x;
worldY = y * sy + skeleton.y;
return;
}
@ -188,8 +173,8 @@ public class Bone implements Updatable {
case noScale:
case noScaleOrReflection: {
float cos = cosDeg(rotation), sin = sinDeg(rotation);
float za = pa * cos + pb * sin;
float zc = pc * cos + pd * sin;
float za = (pa * cos + pb * sin) / skeleton.scaleX;
float zc = (pc * cos + pd * sin) / skeleton.scaleY;
float s = (float)Math.sqrt(za * za + zc * zc);
if (s > 0.00001f) s = 1 / s;
za *= s;
@ -201,26 +186,18 @@ public class Bone implements Updatable {
float la = cosDeg(shearX) * scaleX;
float lb = cosDeg(90 + shearY) * scaleY;
float lc = sinDeg(shearX) * scaleX;
float ld = sinDeg(90 + shearY) * scaleY;
if (data.transformMode != TransformMode.noScaleOrReflection ? pa * pd - pb * pc < 0 : skeleton.flipX != skeleton.flipY) {
zb = -zb;
zd = -zd;
}
float ld = sinDeg(90 + shearY) * scaleY;
a = za * la + zb * lc;
b = za * lb + zb * ld;
c = zc * la + zd * lc;
d = zc * lb + zd * ld;
return;
break;
}
}
if (skeleton.flipX) {
a = -a;
b = -b;
}
if (skeleton.flipY) {
c = -c;
d = -d;
}
a *= skeleton.scaleX;
b *= skeleton.scaleX;
c *= skeleton.scaleY;
d *= skeleton.scaleY;
}
/** Sets this bone's local transform to the setup pose. */

View File

@ -42,8 +42,9 @@ public class IkConstraint implements Constraint {
final IkConstraintData data;
final Array<Bone> bones;
Bone target;
float mix = 1;
int bendDirection;
boolean stretch;
float mix = 1;
public IkConstraint (IkConstraintData data, Skeleton skeleton) {
if (data == null) throw new IllegalArgumentException("data cannot be null.");
@ -51,6 +52,7 @@ public class IkConstraint implements Constraint {
this.data = data;
mix = data.mix;
bendDirection = data.bendDirection;
stretch = data.stretch;
bones = new Array(data.bones.size);
for (BoneData boneData : data.bones)
@ -69,6 +71,7 @@ public class IkConstraint implements Constraint {
target = skeleton.bones.get(constraint.target.data.index);
mix = constraint.mix;
bendDirection = constraint.bendDirection;
stretch = constraint.stretch;
}
/** Applies the constraint to the constrained bones. */
@ -81,10 +84,10 @@ public class IkConstraint implements Constraint {
Array<Bone> bones = this.bones;
switch (bones.size) {
case 1:
apply(bones.first(), target.worldX, target.worldY, mix);
apply(bones.first(), target.worldX, target.worldY, stretch, mix);
break;
case 2:
apply(bones.first(), bones.get(1), target.worldX, target.worldY, bendDirection, mix);
apply(bones.first(), bones.get(1), target.worldX, target.worldY, bendDirection, stretch, mix);
break;
}
}
@ -125,6 +128,16 @@ public class IkConstraint implements Constraint {
this.bendDirection = bendDirection;
}
/** When true, if the target is out of range, the parent bone is scaled on the X axis to reach it. If the parent bone has local
* nonuniform scale, stretching is not applied. */
public boolean getStretch () {
return stretch;
}
public void setStretch (boolean stretch) {
this.stretch = stretch;
}
/** The IK constraint's setup pose data. */
public IkConstraintData getData () {
return data;
@ -135,7 +148,7 @@ public class IkConstraint implements Constraint {
}
/** Applies 1 bone IK. The target is specified in the world coordinate system. */
static public void apply (Bone bone, float targetX, float targetY, float alpha) {
static public void apply (Bone bone, float targetX, float targetY, boolean stretch, float alpha) {
if (!bone.appliedValid) bone.updateAppliedTransform();
Bone p = bone.parent;
float id = 1 / (p.a * p.d - p.b * p.c);
@ -146,20 +159,25 @@ public class IkConstraint implements Constraint {
if (rotationIK > 180)
rotationIK -= 360;
else if (rotationIK < -180) rotationIK += 360;
bone.updateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, bone.ascaleX, bone.ascaleY, bone.ashearX,
float sx = bone.ascaleX;
if (stretch) {
float b = bone.data.length * sx, dd = (float)Math.sqrt(tx * tx + ty * ty);
if (dd > b && b > 0.0001f) sx *= (dd / b - 1) * alpha + 1;
}
bone.updateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, bone.ascaleY, bone.ashearX,
bone.ashearY);
}
/** Applies 2 bone IK. The target is specified in the world coordinate system.
* @param child A direct descendant of the parent bone. */
static public void apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) {
static public void apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, boolean stretch, float alpha) {
if (alpha == 0) {
child.updateWorldTransform();
return;
}
if (!parent.appliedValid) parent.updateAppliedTransform();
if (!child.appliedValid) child.updateAppliedTransform();
float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, csx = child.ascaleX;
float px = parent.ax, py = parent.ay, psx = parent.ascaleX, sx = psx, psy = parent.ascaleY, csx = child.ascaleX;
int os1, os2, s2;
if (psx < 0) {
psx = -psx;
@ -195,7 +213,7 @@ public class IkConstraint implements Constraint {
c = pp.c;
d = pp.d;
float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY;
float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py;
float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py, dd = tx * tx + ty * ty;
x = cwx - pp.worldX;
y = cwy - pp.worldY;
float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py;
@ -203,10 +221,13 @@ public class IkConstraint implements Constraint {
outer:
if (u) {
l2 *= psx;
float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2);
float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2);
if (cos < -1)
cos = -1;
else if (cos > 1) cos = 1;
else if (cos > 1) {
cos = 1;
if (stretch && l1 + l2 > 0.0001f) sx *= ((float)Math.sqrt(dd) / (l1 + l2) - 1) * alpha + 1;
}
a2 = (float)Math.acos(cos) * bendDir;
a = l1 + l2 * cos;
b = l2 * sin(a2);
@ -214,7 +235,7 @@ public class IkConstraint implements Constraint {
} else {
a = psx * l2;
b = psy * l2;
float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = atan2(ty, tx);
float aa = a * a, bb = b * b, ta = atan2(ty, tx);
c = bb * l1 * l1 + aa * dd - aa * bb;
float c1 = -2 * bb * l1, c2 = bb - aa;
d = c1 * c1 - 4 * c2 * c;
@ -266,7 +287,7 @@ public class IkConstraint implements Constraint {
if (a1 > 180)
a1 -= 360;
else if (a1 < -180) a1 += 360;
parent.updateWorldTransform(px, py, rotation + a1 * alpha, parent.ascaleX, parent.ascaleY, 0, 0);
parent.updateWorldTransform(px, py, rotation + a1 * alpha, sx, parent.ascaleY, 0, 0);
rotation = child.arotation;
a2 = ((a2 + os) * radDeg - child.ashearX) * s2 + os2 - rotation;
if (a2 > 180)

View File

@ -41,6 +41,7 @@ public class IkConstraintData {
final Array<BoneData> bones = new Array();
BoneData target;
int bendDirection = 1;
boolean stretch;
float mix = 1;
public IkConstraintData (String name) {
@ -86,6 +87,16 @@ public class IkConstraintData {
this.bendDirection = bendDirection;
}
/** When true, if the target is out of range, the parent bone is scaled on the X axis to reach it. If the parent bone has local
* nonuniform scale, stretching is not applied. */
public boolean getStretch () {
return stretch;
}
public void setStretch (boolean stretch) {
this.stretch = stretch;
}
/** A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. */
public float getMix () {
return mix;

View File

@ -30,14 +30,14 @@
package com.esotericsoftware.spine;
import static com.esotericsoftware.spine.utils.SpineUtils.cosDeg;
import static com.esotericsoftware.spine.utils.SpineUtils.sinDeg;
import static com.esotericsoftware.spine.utils.SpineUtils.*;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.FloatArray;
import com.badlogic.gdx.utils.ObjectMap.Entry;
import com.esotericsoftware.spine.Skin.Key;
import com.esotericsoftware.spine.attachments.Attachment;
import com.esotericsoftware.spine.attachments.MeshAttachment;
@ -61,7 +61,7 @@ public class Skeleton {
Skin skin;
final Color color;
float time;
boolean flipX, flipY;
float scaleX = 1, scaleY = 1;
float x, y;
public Skeleton (SkeletonData data) {
@ -150,8 +150,8 @@ public class Skeleton {
skin = skeleton.skin;
color = new Color(skeleton.color);
time = skeleton.time;
flipX = skeleton.flipX;
flipY = skeleton.flipY;
scaleX = skeleton.scaleX;
scaleY = skeleton.scaleY;
updateCache();
}
@ -331,9 +331,9 @@ public class Skeleton {
for (int i = 0, n = updateCache.size; i < n; i++)
updateCache.get(i).update();
}
/** Updates the world transform for each bone and applies all constraints. The
* root bone will be temporarily parented to the specified bone.
/** Updates the world transform for each bone and applies all constraints. The root bone will be temporarily parented to the
* specified bone.
* <p>
* See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
* Runtimes Guide. */
@ -353,7 +353,7 @@ public class Skeleton {
bone.ashearY = bone.shearY;
bone.appliedValid = true;
}
// Apply the parent bone transform to the root bone. The root bone
// always inherits scale, rotation and reflection.
Bone rootBone = getRootBone();
@ -366,20 +366,11 @@ public class Skeleton {
float lb = cosDeg(rotationY) * rootBone.scaleY;
float lc = sinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
float ld = sinDeg(rotationY) * rootBone.scaleY;
rootBone.a = pa * la + pb * lc;
rootBone.b = pa * lb + pb * ld;
rootBone.c = pc * la + pd * lc;
rootBone.d = pc * lb + pd * ld;
if (flipY) {
rootBone.a = -rootBone.a;
rootBone.b = -rootBone.b;
}
if (flipX) {
rootBone.c = -rootBone.c;
rootBone.d = -rootBone.d;
}
rootBone.a = (pa * la + pb * lc) * scaleX;
rootBone.b = (pa * lb + pb * ld) * scaleX;
rootBone.c = (pc * la + pd * lc) * scaleY;
rootBone.d = (pc * lb + pd * ld) * scaleY;
// Update everything except root bone.
Array<Updatable> updateCache = this.updateCache;
for (int i = 0, n = updateCache.size; i < n; i++) {
@ -404,6 +395,7 @@ public class Skeleton {
for (int i = 0, n = ikConstraints.size; i < n; i++) {
IkConstraint constraint = ikConstraints.get(i);
constraint.bendDirection = constraint.data.bendDirection;
constraint.stretch = constraint.data.stretch;
constraint.mix = constraint.data.mix;
}
@ -685,29 +677,29 @@ public class Skeleton {
this.color.set(color);
}
/** If true, the entire skeleton is flipped over the Y axis. This affects all bones, even if the bone's transform mode
* disallows scale inheritance. */
public boolean getFlipX () {
return flipX;
/** Scales the entire skeleton on the X axis. This affects all bones, even if the bone's transform mode disallows scale
* inheritance. */
public float getScaleX () {
return scaleX;
}
public void setFlipX (boolean flipX) {
this.flipX = flipX;
public void setScaleX (float scaleX) {
this.scaleX = scaleX;
}
/** If true, the entire skeleton is flipped over the X axis. This affects all bones, even if the bone's transform mode
* disallows scale inheritance. */
public boolean getFlipY () {
return flipY;
/** Scales the entire skeleton on the Y axis. This affects all bones, even if the bone's transform mode disallows scale
* inheritance. */
public float getScaleY () {
return scaleY;
}
public void setFlipY (boolean flipY) {
this.flipY = flipY;
public void setScaleY (float scaleY) {
this.scaleY = scaleY;
}
public void setFlip (boolean flipX, boolean flipY) {
this.flipX = flipX;
this.flipY = flipY;
public void setScale (float scaleX, float scaleY) {
this.scaleX = scaleX;
this.scaleY = scaleY;
}
/** Sets the skeleton X position, which is added to the root bone worldX position. */

View File

@ -232,6 +232,7 @@ public class SkeletonBinary {
data.target = skeletonData.bones.get(input.readInt(true));
data.mix = input.readFloat();
data.bendDirection = input.readByte();
data.stretch = input.readBoolean();
skeletonData.ikConstraints.add(data);
}
@ -660,7 +661,7 @@ public class SkeletonBinary {
IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount);
timeline.ikConstraintIndex = index;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readByte());
timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readByte(), input.readBoolean());
if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
}
timelines.add(timeline);

View File

@ -186,6 +186,7 @@ public class SkeletonJson {
if (data.target == null) throw new SerializationException("IK target bone not found: " + targetName);
data.bendDirection = constraintMap.getBoolean("bendPositive", true) ? 1 : -1;
data.stretch = constraintMap.getBoolean("stretch", false);
data.mix = constraintMap.getFloat("mix", 1);
skeletonData.ikConstraints.add(data);
@ -568,7 +569,7 @@ public class SkeletonJson {
int frameIndex = 0;
for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) {
timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat("mix", 1),
valueMap.getBoolean("bendPositive", true) ? 1 : -1);
valueMap.getBoolean("bendPositive", true) ? 1 : -1, valueMap.getBoolean("stretch", false));
readCurve(valueMap, timeline, frameIndex);
frameIndex++;
}

View File

@ -67,7 +67,7 @@ public class SkeletonRenderer {
* skeleton is rendered without two color tinting and any mesh attachments will throw an exception.
* <p>
* This method may change the batch's {@link Batch#setBlendFunctionSeparate(int, int, int, int) blending function}. The
* previous blend function is not restore, since that could result in unnecessary flushes, depending on what is rendered
* previous blend function is not restored, since that could result in unnecessary flushes, depending on what is rendered
* next. */
public void draw (Batch batch, Skeleton skeleton) {
if (batch instanceof PolygonSpriteBatch) {
@ -144,7 +144,7 @@ public class SkeletonRenderer {
/** Renders the specified skeleton, including meshes, but without two color tinting.
* <p>
* This method may change the batch's {@link Batch#setBlendFunctionSeparate(int, int, int, int) blending function}. The
* previous blend function is not restore, since that could result in unnecessary flushes, depending on what is rendered
* previous blend function is not restored, since that could result in unnecessary flushes, depending on what is rendered
* next. */
@SuppressWarnings("null")
public void draw (PolygonSpriteBatch batch, Skeleton skeleton) {
@ -266,7 +266,7 @@ public class SkeletonRenderer {
/** Renders the specified skeleton, including meshes and two color tinting.
* <p>
* This method may change the batch's {@link Batch#setBlendFunctionSeparate(int, int, int, int) blending function}. The
* previous blend function is not restore, since that could result in unnecessary flushes, depending on what is rendered
* previous blend function is not restored, since that could result in unnecessary flushes, depending on what is rendered
* next. */
@SuppressWarnings("null")
public void draw (TwoColorPolygonBatch batch, Skeleton skeleton) {

View File

@ -70,7 +70,7 @@ public class SkeletonActorPool extends Pool<SkeletonActor> {
protected void reset (Skeleton skeleton) {
skeleton.setColor(Color.WHITE);
skeleton.setFlip(false, false);
skeleton.setScale(1, 1);
skeleton.setSkin((Skin)null);
skeleton.setSkin(SkeletonActorPool.this.skeletonData.getDefaultSkin());
skeleton.setToSetupPose();

View File

@ -192,11 +192,11 @@ public class SkeletonViewer extends ApplicationAdapter {
String extension = skeletonFile.extension();
if (extension.equalsIgnoreCase("json") || extension.equalsIgnoreCase("txt")) {
SkeletonJson json = new SkeletonJson(atlas);
json.setScale(ui.scaleSlider.getValue());
json.setScale(ui.loadScaleSlider.getValue());
skeletonData = json.readSkeletonData(skeletonFile);
} else {
SkeletonBinary binary = new SkeletonBinary(atlas);
binary.setScale(ui.scaleSlider.getValue());
binary.setScale(ui.loadScaleSlider.getValue());
skeletonData = binary.readSkeletonData(skeletonFile);
if (skeletonData.getBones().size == 0) throw new Exception("No bones in skeleton data.");
}
@ -311,7 +311,10 @@ public class SkeletonViewer extends ApplicationAdapter {
renderer.setPremultipliedAlpha(ui.premultipliedCheckbox.isChecked());
batch.setPremultipliedAlpha(ui.premultipliedCheckbox.isChecked());
skeleton.setFlip(ui.flipXCheckbox.isChecked(), ui.flipYCheckbox.isChecked());
float scaleX = ui.xScaleSlider.getValue(), scaleY = ui.yScaleSlider.getValue();
if (skeleton.scaleX == 0) skeleton.scaleX = 0.01f;
if (skeleton.scaleY == 0) skeleton.scaleY = 0.01f;
skeleton.setScale(scaleX, scaleY);
delta = Math.min(delta, 0.032f) * ui.speedSlider.getValue();
skeleton.update(delta);
@ -412,16 +415,21 @@ public class SkeletonViewer extends ApplicationAdapter {
TextButton openButton = new TextButton("Open", skin);
TextButton minimizeButton = new TextButton("-", skin);
Slider scaleSlider = new Slider(0.1f, 3, 0.01f, false, skin);
Label scaleLabel = new Label("1.0", skin);
TextButton scaleResetButton = new TextButton("Reset", skin);
Slider loadScaleSlider = new Slider(0.1f, 3, 0.01f, false, skin);
Label loadScaleLabel = new Label("100%", skin);
TextButton loadScaleResetButton = new TextButton("Reset", skin);
Slider zoomSlider = new Slider(0.01f, 10, 0.01f, false, skin);
Label zoomLabel = new Label("1.0", skin);
Label zoomLabel = new Label("100%", skin);
TextButton zoomResetButton = new TextButton("Reset", skin);
CheckBox flipXCheckbox = new CheckBox("X", skin);
CheckBox flipYCheckbox = new CheckBox("Y", skin);
Slider xScaleSlider = new Slider(-2, 2, 0.01f, false, skin);
Label xScaleLabel = new Label("100%", skin);
TextButton xScaleResetButton = new TextButton("Reset", skin);
Slider yScaleSlider = new Slider(-2, 2, 0.01f, false, skin);
Label yScaleLabel = new Label("100%", skin);
TextButton yScaleResetButton = new TextButton("Reset", skin);
CheckBox debugBonesCheckbox = new CheckBox("Bones", skin);
CheckBox debugRegionsCheckbox = new CheckBox("Regions", skin);
@ -448,17 +456,17 @@ public class SkeletonViewer extends ApplicationAdapter {
CheckBox addCheckbox = new CheckBox("Add", skin);
Slider alphaSlider = new Slider(0, 1, 0.01f, false, skin);
Label alphaLabel = new Label("1.0", skin);
Label alphaLabel = new Label("100%", skin);
List<String> animationList = new List(skin);
ScrollPane animationScroll = new ScrollPane(animationList, skin, "bg");
Slider speedSlider = new Slider(0, 3, 0.01f, false, skin);
Label speedLabel = new Label("1.0", skin);
Label speedLabel = new Label("1.0x", skin);
TextButton speedResetButton = new TextButton("Reset", skin);
Slider mixSlider = new Slider(0, 4, 0.01f, false, skin);
Label mixLabel = new Label("0.3", skin);
Label mixLabel = new Label("0.3s", skin);
Label statusLabel = new Label("", skin);
WidgetGroup toasts = new WidgetGroup();
@ -483,21 +491,27 @@ public class SkeletonViewer extends ApplicationAdapter {
loopCheckbox.setChecked(true);
scaleSlider.setValue(1);
scaleSlider.setSnapToValues(new float[] {0.5f, 1, 1.5f, 2, 2.5f, 3, 3.5f}, 0.01f);
loadScaleSlider.setValue(1);
loadScaleSlider.setSnapToValues(new float[] {0.5f, 1, 1.5f, 2, 2.5f}, 0.09f);
zoomSlider.setValue(1);
zoomSlider.setSnapToValues(new float[] {0.5f, 1, 1.5f, 2, 2.5f, 3, 3.5f}, 0.01f);
zoomSlider.setSnapToValues(new float[] {1, 2}, 0.30f);
xScaleSlider.setValue(1);
xScaleSlider.setSnapToValues(new float[] {-1.5f, -1, -0.5f, 0.5f, 1, 1.5f}, 0.12f);
yScaleSlider.setValue(1);
yScaleSlider.setSnapToValues(new float[] {-1.5f, -1, -0.5f, 0.5f, 1, 1.5f}, 0.12f);
mixSlider.setValue(0.3f);
mixSlider.setSnapToValues(new float[] {1, 1.5f, 2, 2.5f, 3, 3.5f}, 0.1f);
mixSlider.setSnapToValues(new float[] {1, 1.5f, 2, 2.5f, 3, 3.5f}, 0.12f);
speedSlider.setValue(1);
speedSlider.setSnapToValues(new float[] {0.5f, 0.75f, 1, 1.25f, 1.5f, 2, 2.5f}, 0.01f);
speedSlider.setSnapToValues(new float[] {0.5f, 0.75f, 1, 1.25f, 1.5f, 2, 2.5f}, 0.09f);
alphaSlider.setValue(1);
alphaSlider.setDisabled(true);
addCheckbox.setDisabled(true);
window.setMovable(false);
@ -519,12 +533,12 @@ public class SkeletonViewer extends ApplicationAdapter {
root.defaults().space(6);
root.columnDefaults(0).top().right().padTop(3);
root.columnDefaults(1).left();
root.add("Scale:");
root.add("Load scale:");
{
Table table = table();
table.add(scaleLabel).width(29);
table.add(scaleSlider).growX();
table.add(scaleResetButton);
table.add(loadScaleLabel).width(29);
table.add(loadScaleSlider).growX();
table.add(loadScaleResetButton);
root.add(table).fill().row();
}
root.add("Zoom:");
@ -535,8 +549,22 @@ public class SkeletonViewer extends ApplicationAdapter {
table.add(zoomResetButton);
root.add(table).fill().row();
}
root.add("Flip:");
root.add(table(flipXCheckbox, flipYCheckbox)).row();
root.add("Scale X:");
{
Table table = table();
table.add(xScaleLabel).width(29);
table.add(xScaleSlider).growX();
table.add(xScaleResetButton).row();
root.add(table).fill().row();
}
root.add("Scale Y:");
{
Table table = table();
table.add(yScaleLabel).width(29);
table.add(yScaleSlider).growX();
table.add(yScaleResetButton);
root.add(table).fill().row();
}
root.add("Debug:");
root.add(table(debugBonesCheckbox, debugRegionsCheckbox, debugBoundingBoxesCheckbox)).row();
root.add();
@ -677,25 +705,25 @@ public class SkeletonViewer extends ApplicationAdapter {
}
});
scaleSlider.addListener(new ChangeListener() {
loadScaleSlider.addListener(new ChangeListener() {
public void changed (ChangeEvent event, Actor actor) {
scaleLabel.setText(Float.toString((int)(scaleSlider.getValue() * 100) / 100f));
if (!scaleSlider.isDragging()) loadSkeleton(skeletonFile);
loadScaleLabel.setText(Integer.toString((int)(loadScaleSlider.getValue() * 100)) + "%");
if (!loadScaleSlider.isDragging()) loadSkeleton(skeletonFile);
}
});
scaleResetButton.addListener(new ChangeListener() {
loadScaleResetButton.addListener(new ChangeListener() {
public void changed (ChangeEvent event, Actor actor) {
resetCameraPosition();
if (scaleSlider.getValue() == 1)
if (loadScaleSlider.getValue() == 1)
loadSkeleton(skeletonFile);
else
scaleSlider.setValue(1);
loadScaleSlider.setValue(1);
}
});
zoomSlider.addListener(new ChangeListener() {
public void changed (ChangeEvent event, Actor actor) {
zoomLabel.setText(Float.toString((int)(zoomSlider.getValue() * 100) / 100f));
zoomLabel.setText(Integer.toString((int)(zoomSlider.getValue() * 100)) + "%");
float newZoom = 1 / zoomSlider.getValue();
camera.position.x -= window.getWidth() / 2 * (newZoom - camera.zoom);
camera.zoom = newZoom;
@ -710,9 +738,33 @@ public class SkeletonViewer extends ApplicationAdapter {
}
});
xScaleSlider.addListener(new ChangeListener() {
public void changed (ChangeEvent event, Actor actor) {
if (xScaleSlider.getValue() == 0) xScaleSlider.setValue(0.01f);
xScaleLabel.setText(Integer.toString((int)(xScaleSlider.getValue() * 100)) + "%");
}
});
xScaleResetButton.addListener(new ChangeListener() {
public void changed (ChangeEvent event, Actor actor) {
xScaleSlider.setValue(1);
}
});
yScaleSlider.addListener(new ChangeListener() {
public void changed (ChangeEvent event, Actor actor) {
if (yScaleSlider.getValue() == 0) yScaleSlider.setValue(0.01f);
yScaleLabel.setText(Integer.toString((int)(yScaleSlider.getValue() * 100)) + "%");
}
});
yScaleResetButton.addListener(new ChangeListener() {
public void changed (ChangeEvent event, Actor actor) {
yScaleSlider.setValue(1);
}
});
speedSlider.addListener(new ChangeListener() {
public void changed (ChangeEvent event, Actor actor) {
speedLabel.setText(Float.toString((int)(speedSlider.getValue() * 100) / 100f));
speedLabel.setText(Float.toString((int)(speedSlider.getValue() * 100) / 100f) + "x");
}
});
speedResetButton.addListener(new ChangeListener() {
@ -723,7 +775,7 @@ public class SkeletonViewer extends ApplicationAdapter {
alphaSlider.addListener(new ChangeListener() {
public void changed (ChangeEvent event, Actor actor) {
alphaLabel.setText(Float.toString((int)(alphaSlider.getValue() * 100) / 100f));
alphaLabel.setText(Integer.toString((int)(alphaSlider.getValue() * 100)) + "%");
int track = trackButtons.getCheckedIndex();
if (track > 0) {
TrackEntry current = state.getCurrent(track);
@ -737,7 +789,7 @@ public class SkeletonViewer extends ApplicationAdapter {
mixSlider.addListener(new ChangeListener() {
public void changed (ChangeEvent event, Actor actor) {
mixLabel.setText(Float.toString((int)(mixSlider.getValue() * 100) / 100f));
mixLabel.setText(Float.toString((int)(mixSlider.getValue() * 100) / 100f) + "s");
if (state != null) state.getData().setDefaultMix(mixSlider.getValue());
}
});
@ -878,8 +930,8 @@ public class SkeletonViewer extends ApplicationAdapter {
speedSlider.addListener(savePrefsListener);
speedResetButton.addListener(savePrefsListener);
mixSlider.addListener(savePrefsListener);
scaleSlider.addListener(savePrefsListener);
scaleResetButton.addListener(savePrefsListener);
loadScaleSlider.addListener(savePrefsListener);
loadScaleResetButton.addListener(savePrefsListener);
zoomSlider.addListener(savePrefsListener);
zoomResetButton.addListener(savePrefsListener);
animationList.addListener(savePrefsListener);
@ -942,7 +994,7 @@ public class SkeletonViewer extends ApplicationAdapter {
prefs.putBoolean("add", addCheckbox.isChecked());
prefs.putFloat("speed", speedSlider.getValue());
prefs.putFloat("mix", mixSlider.getValue());
prefs.putFloat("scale", scaleSlider.getValue());
prefs.putFloat("scale", loadScaleSlider.getValue());
prefs.putFloat("zoom", zoomSlider.getValue());
prefs.putFloat("x", camera.position.x);
prefs.putFloat("y", camera.position.y);
@ -978,7 +1030,7 @@ public class SkeletonViewer extends ApplicationAdapter {
camera.position.x = prefs.getFloat("x", 0);
camera.position.y = prefs.getFloat("y", 0);
scaleSlider.setValue(prefs.getFloat("scale", 1));
loadScaleSlider.setValue(prefs.getFloat("scale", 1));
animationList.setSelected(prefs.getString("animationName", null));
skinList.setSelected(prefs.getString("skinName", null));
} catch (Exception ex) {

View File

@ -30,6 +30,8 @@
#include "SpinePluginPrivatePCH.h"
DEFINE_LOG_CATEGORY(SpineLog);
class FSpinePlugin : public SpinePlugin {
virtual void StartupModule() override;
virtual void ShutdownModule() override;

View File

@ -107,12 +107,14 @@ void USpineSkeletonAnimationComponent::CheckState () {
if (Atlas && SkeletonData) {
spSkeletonData* data = SkeletonData->GetSkeletonData(Atlas->GetAtlas(false), false);
skeleton = spSkeleton_create(data);
spAnimationStateData* stateData = SkeletonData->GetAnimationStateData(Atlas->GetAtlas(false));
state = spAnimationState_create(stateData);
state->rendererObject = (void*)this;
state->listener = callback;
trackEntries.Empty();
if (data) {
skeleton = spSkeleton_create(data);
spAnimationStateData* stateData = SkeletonData->GetAnimationStateData(Atlas->GetAtlas(false));
state = spAnimationState_create(stateData);
state->rendererObject = (void*)this;
state->listener = callback;
trackEntries.Empty();
}
}
lastAtlas = Atlas;

View File

@ -33,6 +33,7 @@
#include <string.h>
#include <string>
#include <stdlib.h>
#include "Runtime/Core/Public/Misc/MessageDialog.h"
#define LOCTEXT_NAMESPACE "Spine"
@ -103,10 +104,22 @@ spSkeletonData* USpineSkeletonDataAsset::GetSkeletonData (spAtlas* Atlas, bool F
if (skeletonDataFileName.GetPlainNameString().Contains(TEXT(".json"))) {
spSkeletonJson* json = spSkeletonJson_create(Atlas);
this->skeletonData = spSkeletonJson_readSkeletonData(json, (const char*)rawData.GetData());
if (!skeletonData) {
#if WITH_EDITORONLY_DATA
FMessageDialog::Debugf(FText::FromString(UTF8_TO_TCHAR(json->error)));
#endif
UE_LOG(SpineLog, Error, TEXT("Couldn't load skeleton data and atlas: %s"), UTF8_TO_TCHAR(json->error));
}
spSkeletonJson_dispose(json);
} else {
spSkeletonBinary* binary = spSkeletonBinary_create(Atlas);
this->skeletonData = spSkeletonBinary_readSkeletonData(binary, (const unsigned char*)rawData.GetData(), (int)rawData.Num());
if (!skeletonData) {
#if WITH_EDITORONLY_DATA
FMessageDialog::Debugf(FText::FromString(UTF8_TO_TCHAR(binary->error)));
#endif
UE_LOG(SpineLog, Error, TEXT("Couldn't load skeleton data and atlas: %s"), UTF8_TO_TCHAR(binary->error));
}
spSkeletonBinary_dispose(binary);
}
if (animationStateData) {

View File

@ -32,6 +32,8 @@
#include "ModuleManager.h"
DECLARE_LOG_CATEGORY_EXTERN(SpineLog, Log, All);
class SPINEPLUGIN_API SpinePlugin : public IModuleInterface {
public:

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 173cd2c662ebd674f994bff2385cfbf6
folderAsset: yes
timeCreated: 1529972040
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b804088948820194cbda76af39c08174
timeCreated: 1529972058
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,117 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Spine;
using Spine.Unity;
using System.Text;
namespace Spine.Unity.Examples {
public class SpineAnimationTesterTool : MonoBehaviour, IHasSkeletonDataAsset, IHasSkeletonComponent {
public SkeletonAnimation skeletonAnimation;
public SkeletonDataAsset SkeletonDataAsset { get { return skeletonAnimation.SkeletonDataAsset; } }
public ISkeletonComponent SkeletonComponent { get { return skeletonAnimation; } }
public bool useOverrideMixDuration;
public float overrideMixDuration = 0.2f;
[System.Serializable]
public struct AnimationControl {
[SpineAnimation]
public string animationName;
public bool loop;
public KeyCode key;
[Space]
public bool useCustomMixDuration;
public float mixDuration;
//public bool useChainToControl;
//public int chainToControl;
}
[System.Serializable]
public class ControlledTrack {
public List<AnimationControl> controls = new List<AnimationControl>();
}
[Space]
public List<ControlledTrack> trackControls = new List<ControlledTrack>();
[Header("UI")]
public UnityEngine.UI.Text boundAnimationsText;
public UnityEngine.UI.Text skeletonNameText;
void OnValidate () {
// Fill in the SkeletonData asset name
if (skeletonNameText != null) {
if (skeletonAnimation != null && skeletonAnimation.skeletonDataAsset != null) {
skeletonNameText.text = SkeletonDataAsset.name.Replace("_SkeletonData", "");
}
}
// Fill in the control list.
if (boundAnimationsText != null) {
var boundAnimationsStringBuilder = new StringBuilder();
boundAnimationsStringBuilder.AppendLine("Animation Controls:");
for (int trackIndex = 0; trackIndex < trackControls.Count; trackIndex++) {
if (trackIndex > 0)
boundAnimationsStringBuilder.AppendLine();
boundAnimationsStringBuilder.AppendFormat("---- Track {0} ---- \n", trackIndex);
foreach (var ba in trackControls[trackIndex].controls) {
string animationName = ba.animationName;
if (string.IsNullOrEmpty(animationName))
animationName = "SetEmptyAnimation";
boundAnimationsStringBuilder.AppendFormat("[{0}] {1}\n", ba.key.ToString(), animationName);
}
}
boundAnimationsText.text = boundAnimationsStringBuilder.ToString();
}
}
void Start () {
if (useOverrideMixDuration) {
skeletonAnimation.AnimationState.Data.DefaultMix = overrideMixDuration;
}
}
void Update () {
var animationState = skeletonAnimation.AnimationState;
// For each track
for (int trackIndex = 0; trackIndex < trackControls.Count; trackIndex++) {
// For each control in the track
foreach (var control in trackControls[trackIndex].controls) {
// Check each control, and play the appropriate animation.
if (Input.GetKeyDown(control.key)) {
if (!string.IsNullOrEmpty(control.animationName)) {
var trackEntry = animationState.SetAnimation(trackIndex, control.animationName, control.loop);
if (control.useCustomMixDuration)
trackEntry.MixDuration = control.mixDuration;
} else {
float mix = control.useCustomMixDuration ? control.mixDuration : animationState.Data.DefaultMix;
animationState.SetEmptyAnimation(trackIndex, mix);
}
// Don't parse more than one animation per track.
break;
}
}
}
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: b99b2d8e59226fa4db070f241259fd98
timeCreated: 1529972356
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 4766fcfd6167d2e46aad772ce3bc898c
folderAsset: yes
timeCreated: 1531292725
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: afd3c9ec31200bc49b169c22f00b010b
timeCreated: 1531300871
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: cec34498f2eb26b488452ec274c54439
timeCreated: 1531292741
licenseType: Free
NativeFormatImporter:
mainObjectFileID: 9100000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,62 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Spine;
using Spine.Unity;
public class AnimationStateMecanimState : StateMachineBehaviour {
#region Inspector
public AnimationReferenceAsset animation;
[System.Serializable]
public struct AnimationTransition {
public AnimationReferenceAsset from;
public AnimationReferenceAsset transition;
}
[UnityEngine.Serialization.FormerlySerializedAs("transitions")]
public List<AnimationTransition> fromTransitions = new List<AnimationTransition>();
#endregion
Spine.AnimationState state;
public void Initialize (Animator animator) {
if (state == null) {
var animationStateComponent = (animator.GetComponent(typeof(IAnimationStateComponent))) as IAnimationStateComponent;
state = animationStateComponent.AnimationState;
}
}
override public void OnStateEnter (Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
if (state == null) {
Initialize(animator);
}
float timeScale = stateInfo.speed;
var current = state.GetCurrent(layerIndex);
bool transitionPlayed = false;
if (current != null && fromTransitions.Count > 0) {
foreach (var t in fromTransitions) {
if (t.from.Animation == current.Animation) {
var transitionEntry = state.SetAnimation(layerIndex, t.transition.Animation, false);
transitionEntry.TimeScale = timeScale;
transitionPlayed = true;
break;
}
}
}
TrackEntry trackEntry;
if (transitionPlayed) {
trackEntry = state.AddAnimation(layerIndex, animation.Animation, stateInfo.loop, 0);
} else {
trackEntry = state.SetAnimation(layerIndex, animation.Animation, stateInfo.loop);
}
trackEntry.TimeScale = timeScale;
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 536bdde8dc7bbb641b17da9221d6562f
timeCreated: 1531293563
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,82 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Spine;
using Spine.Unity;
namespace Spine.Unity.Examples {
public class AnimationStateWithMecanimExample : MonoBehaviour {
SkeletonAnimation skeletonAnimation;
Animator logicAnimator;
[Header("Controls")]
public KeyCode walkButton = KeyCode.LeftShift;
public KeyCode jumpButton = KeyCode.Space;
[Header("Animator Properties")]
public string horizontalSpeedProperty = "Speed";
public string verticalSpeedProperty = "VerticalSpeed";
public string groundedProperty = "Grounded";
[Header("Fake Physics")]
public float jumpDuration = 1.5f;
public Vector2 speed;
public bool isGrounded;
void Awake () {
skeletonAnimation = GetComponent<SkeletonAnimation>();
logicAnimator = GetComponent<Animator>();
isGrounded = true;
}
void Update () {
float x = Input.GetAxisRaw("Horizontal");
if (Input.GetKey(walkButton)) {
x *= 0.4f;
}
speed.x = x;
// Flip skeleton.
if (x != 0) {
skeletonAnimation.Skeleton.ScaleX = x > 0 ? 1f : -1f;
}
if (Input.GetKeyDown(jumpButton)) {
if (isGrounded)
StartCoroutine(FakeJump());
}
logicAnimator.SetFloat(horizontalSpeedProperty, Mathf.Abs(speed.x));
logicAnimator.SetFloat(verticalSpeedProperty, speed.y);
logicAnimator.SetBool(groundedProperty, isGrounded);
}
IEnumerator FakeJump () {
// Rise
isGrounded = false;
speed.y = 10f;
float durationLeft = jumpDuration * 0.5f;
while (durationLeft > 0) {
durationLeft -= Time.deltaTime;
if (!Input.GetKey(jumpButton)) break;
yield return null;
}
// Fall
speed.y = -10f;
float fallDuration = (jumpDuration * 0.5f) - durationLeft;
yield return new WaitForSeconds(fallDuration);
// Land
speed.y = 0f;
isGrounded = true;
yield return null;
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 782062825deffd64ba7e7e9f978788e5
timeCreated: 1531300740
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -80,11 +80,11 @@ namespace Spine.Unity.Examples {
} else {
if (Input.GetKey(rightKey)) {
skeletonAnimation.AnimationName = moveAnimation;
skeletonAnimation.Skeleton.FlipX = false;
skeletonAnimation.Skeleton.ScaleX = 1;
transform.Translate(moveSpeed * Time.deltaTime, 0, 0);
} else if(Input.GetKey(leftKey)) {
skeletonAnimation.AnimationName = moveAnimation;
skeletonAnimation.Skeleton.FlipX = true;
skeletonAnimation.Skeleton.ScaleX = -1;
transform.Translate(-moveSpeed * Time.deltaTime, 0, 0);
} else {
skeletonAnimation.AnimationName = idleAnimation;

View File

@ -183,7 +183,7 @@ namespace Spine.Unity.Examples {
// Face intended direction.
if (input.x != 0)
skeletonAnimation.Skeleton.FlipX = input.x < 0;
skeletonAnimation.Skeleton.ScaleX = Mathf.Sign(input.x);
// Effects

View File

@ -93,11 +93,11 @@ namespace Spine.Unity.Examples {
spineAnimationState.AddAnimation(0, idleAnimationName, true, 0);
yield return new WaitForSeconds(1f);
skeleton.FlipX = true; // skeleton allows you to flip the skeleton.
skeleton.ScaleX = -1; // skeleton allows you to flip the skeleton.
spineAnimationState.SetAnimation(0, idleTurnAnimationName, false);
spineAnimationState.AddAnimation(0, idleAnimationName, true, 0);
yield return new WaitForSeconds(0.5f);
skeleton.FlipX = false;
skeleton.ScaleX = 1;
spineAnimationState.SetAnimation(0, idleTurnAnimationName, false);
spineAnimationState.AddAnimation(0, idleAnimationName, true, 0);
yield return new WaitForSeconds(0.5f);

View File

@ -69,7 +69,7 @@ namespace Spine.Unity.Examples {
if (skeletonAnimation == null) return;
if (model == null) return;
if (skeletonAnimation.skeleton.FlipX != model.facingLeft) { // Detect changes in model.facingLeft
if ((skeletonAnimation.skeleton.ScaleX < 0) != model.facingLeft) { // Detect changes in model.facingLeft
Turn(model.facingLeft);
}
@ -134,7 +134,7 @@ namespace Spine.Unity.Examples {
}
public void Turn (bool facingLeft) {
skeletonAnimation.Skeleton.FlipX = facingLeft;
skeletonAnimation.Skeleton.ScaleX = facingLeft ? -1f : 1f;
// Maybe play a transient turning animation too, then call ChangeStableAnimation.
}
#endregion

View File

@ -23,7 +23,7 @@ namespace Spine.Unity.Examples {
var mousePosition = Input.mousePosition;
var worldMousePosition = camera.ScreenToWorldPoint(mousePosition);
var skeletonSpacePoint = skeletonAnimation.transform.InverseTransformPoint(worldMousePosition);
if (skeletonAnimation.Skeleton.FlipX) skeletonSpacePoint.x *= -1;
//if (skeletonAnimation.Skeleton.FlipX) skeletonSpacePoint.x *= -1;
bone.SetPosition(skeletonSpacePoint);
}
}

View File

@ -69,8 +69,8 @@ namespace Spine.Unity {
sa.initialFlipX = this.initialFlipX;
sa.initialFlipY = this.initialFlipY;
var skeleton = sa.skeleton;
skeleton.FlipX = this.initialFlipX;
skeleton.FlipY = this.initialFlipY;
skeleton.ScaleX = this.initialFlipX ? 1 : -1;
skeleton.ScaleY = this.initialFlipY ? 1 : -1;
sa.Initialize(false);
skeletonAnimations.Add(sa);

View File

Before

Width:  |  Height:  |  Size: 593 KiB

After

Width:  |  Height:  |  Size: 593 KiB

View File

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 192 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Some files were not shown because too many files have changed in this diff Show More