Initial libgdx runtime.

This commit is contained in:
NathanSweet 2013-02-20 01:15:31 +01:00
parent e2120ca07b
commit 2c78e22855
36 changed files with 4401 additions and 0 deletions

4
.gitignore vendored
View File

@ -1,2 +1,6 @@
spine-cpp/Debug/*
spine-libgdx/bin/*
spine-libgdx/libs/gdx-backend-lwjgl-natives.jar
spine-libgdx/libs/gdx-backend-lwjgl.jar
spine-libgdx/libs/gdx-natives.jar
spine-libgdx/libs/gdx.jar

9
spine-libgdx/.classpath Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="test"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry combineaccessrules="false" exported="true" kind="src" path="/gdx"/>
<classpathentry combineaccessrules="false" exported="true" kind="src" path="/gdx-backend-lwjgl"/>
<classpathentry kind="output" path="bin"/>
</classpath>

17
spine-libgdx/.project Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>spine-libgdx</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,95 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.5
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
org.eclipse.jdt.core.compiler.problem.deadCode=ignore
org.eclipse.jdt.core.compiler.problem.deprecation=ignore
org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore
org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
org.eclipse.jdt.core.compiler.problem.nullReference=warning
org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore
org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=warning
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=ignore
org.eclipse.jdt.core.compiler.problem.unclosedCloseable=ignore
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=enabled
org.eclipse.jdt.core.compiler.problem.unusedImport=ignore
org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
org.eclipse.jdt.core.compiler.problem.unusedLocal=ignore
org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=enabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=enabled
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=ignore
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.source=1.5

View File

@ -0,0 +1,477 @@
package com.esotericsoftware.spine;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Array;
public class Animation {
private final Array<Timeline> timelines;
private float duration;
public Animation (Array<Timeline> timelines, float duration) {
if (timelines == null) throw new IllegalArgumentException("timelines cannot be null.");
this.timelines = timelines;
this.duration = duration;
}
public Array<Timeline> getTimelines () {
return timelines;
}
/** Returns the duration of the animation in seconds. Defaults to the max {@link Timeline#getDuration() duration} of the
* timelines. */
public float getDuration () {
return duration;
}
public void setDuration (float duration) {
this.duration = duration;
}
/** Poses the skeleton at the specified time for this animation. */
public void apply (Skeleton skeleton, float time, boolean loop) {
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
if (loop && duration != 0) time %= duration;
Array<Timeline> timeline = this.timelines;
for (int i = 0, n = timeline.size; i < n; i++)
timeline.get(i).apply(skeleton, time, 1);
}
/** Poses the skeleton at the specified time for this animation mixed with the current pose.
* @param alpha The amount of this animation that affects the current pose. */
public void mix (Skeleton skeleton, float time, boolean loop, float alpha) {
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
if (loop && duration != 0) time %= duration;
Array<Timeline> timeline = this.timelines;
for (int i = 0, n = timeline.size; i < n; i++)
timeline.get(i).apply(skeleton, time, alpha);
}
/** @param target After the first and before the last entry. */
static int binarySearch (float[] values, float target, int step) {
int low = 0;
int high = values.length / step - 2;
if (high == 0) return step;
int current = high >>> 1;
while (true) {
if (values[(current + 1) * step] <= target)
low = current + 1;
else
high = current;
if (low == high) return (low + 1) * step;
current = (low + high) >>> 1;
}
}
static int linearSearch (float[] values, float target, int step) {
for (int i = 0, last = values.length - step; i <= last; i += step) {
if (values[i] <= target) continue;
return i;
}
return -1;
}
/** The keyframes for a single animation timeline. */
static public interface Timeline {
/** Returns the time in seconds of the last keyframe. */
public float getDuration ();
public int getKeyframeCount ();
/** Sets the value(s) for the specified time. */
public void apply (Skeleton skeleton, float time, float alpha);
}
/** Base class for frames that use an interpolation bezier curve. */
static public abstract class CurveTimeline implements Timeline {
static private final float LINEAR = 0;
static private final float STEPPED = -1;
static private final int BEZIER_SEGMENTS = 10;
private final float[] curves; // dfx, dfy, ddfx, ddfy, dddfx, dddfy, ...
public CurveTimeline (int keyframeCount) {
curves = new float[(keyframeCount - 1) * 6];
}
public void setLinear (int keyframeIndex) {
curves[keyframeIndex * 6] = LINEAR;
}
public void setStepped (int keyframeIndex) {
curves[keyframeIndex * 6] = STEPPED;
}
/** Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next.
* cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of
* the difference between the keyframe's values. */
public void setCurve (int keyframeIndex, float cx1, float cy1, float cx2, float cy2) {
float subdiv_step = 1f / BEZIER_SEGMENTS;
float subdiv_step2 = subdiv_step * subdiv_step;
float subdiv_step3 = subdiv_step2 * subdiv_step;
float pre1 = 3 * subdiv_step;
float pre2 = 3 * subdiv_step2;
float pre4 = 6 * subdiv_step2;
float pre5 = 6 * subdiv_step3;
float tmp1x = -cx1 * 2 + cx2;
float tmp1y = -cy1 * 2 + cy2;
float tmp2x = (cx1 - cx2) * 3 + 1;
float tmp2y = (cy1 - cy2) * 3 + 1;
int i = keyframeIndex * 6;
float[] curves = this.curves;
curves[i] = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3;
curves[i + 1] = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3;
curves[i + 2] = tmp1x * pre4 + tmp2x * pre5;
curves[i + 3] = tmp1y * pre4 + tmp2y * pre5;
curves[i + 4] = tmp2x * pre5;
curves[i + 5] = tmp2y * pre5;
}
public float getCurvePercent (int keyframeIndex, float percent) {
int curveIndex = keyframeIndex * 6;
float[] curves = this.curves;
float dfx = curves[curveIndex];
if (dfx == LINEAR) return percent;
if (dfx == STEPPED) return 0;
float dfy = curves[curveIndex + 1];
float ddfx = curves[curveIndex + 2];
float ddfy = curves[curveIndex + 3];
float dddfx = curves[curveIndex + 4];
float dddfy = curves[curveIndex + 5];
float x = dfx, y = dfy;
int i = BEZIER_SEGMENTS - 2;
while (true) {
if (x >= percent) {
float lastX = x - dfx;
float lastY = y - dfy;
return lastY + (y - lastY) * (percent - lastX) / (x - lastX);
}
if (i == 0) break;
i--;
dfx += ddfx;
dfy += ddfy;
ddfx += dddfx;
ddfy += dddfy;
x += dfx;
y += dfy;
}
return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1.
}
}
static public class RotateTimeline extends CurveTimeline {
static private final int LAST_FRAME_TIME = -2;
static private final int FRAME_VALUE = 1;
private int boneIndex;
private final float[] frames; // time, value, ...
public RotateTimeline (int keyframeCount) {
super(keyframeCount);
frames = new float[keyframeCount * 2];
}
public float getDuration () {
return frames[frames.length - 2];
}
public int getKeyframeCount () {
return frames.length / 2;
}
public void setBoneIndex (int boneIndex) {
this.boneIndex = boneIndex;
}
public int getBoneIndex () {
return boneIndex;
}
public float[] getKeyframes () {
return frames;
}
/** Sets the time and value of the specified keyframe. */
public void setKeyframe (int keyframeIndex, float time, float value) {
keyframeIndex *= 2;
frames[keyframeIndex] = time;
frames[keyframeIndex + 1] = value;
}
public void apply (Skeleton skeleton, float time, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
Bone bone = skeleton.bones.get(boneIndex);
if (time >= frames[frames.length - 2]) { // Time is after last frame.
float amount = bone.data.rotation + frames[frames.length - 1] - bone.rotation;
while (amount > 180)
amount -= 360;
while (amount < -180)
amount += 360;
bone.rotation += amount * alpha;
return;
}
// Interpolate between the last frame and the current frame.
int frameIndex = binarySearch(frames, time, 2);
float lastFrameValue = frames[frameIndex - 1];
float frameTime = frames[frameIndex];
float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime), 0, 1);
percent = getCurvePercent(frameIndex / 2 - 1, percent);
float amount = frames[frameIndex + FRAME_VALUE] - lastFrameValue;
while (amount > 180)
amount -= 360;
while (amount < -180)
amount += 360;
amount = bone.data.rotation + (lastFrameValue + amount * percent) - bone.rotation;
while (amount > 180)
amount -= 360;
while (amount < -180)
amount += 360;
bone.rotation += amount * alpha;
}
}
static public class TranslateTimeline extends CurveTimeline {
static final int LAST_FRAME_TIME = -3;
static final int FRAME_X = 1;
static final int FRAME_Y = 2;
int boneIndex;
final float[] frames; // time, value, value, ...
public TranslateTimeline (int keyframeCount) {
super(keyframeCount);
frames = new float[keyframeCount * 3];
}
public float getDuration () {
return frames[frames.length - 3];
}
public int getKeyframeCount () {
return frames.length / 3;
}
public void setBoneIndex (int boneIndex) {
this.boneIndex = boneIndex;
}
public int getBoneIndex () {
return boneIndex;
}
public float[] getKeyframes () {
return frames;
}
/** Sets the time and value of the specified keyframe. */
public void setKeyframe (int keyframeIndex, float time, float x, float y) {
keyframeIndex *= 3;
frames[keyframeIndex] = time;
frames[keyframeIndex + 1] = x;
frames[keyframeIndex + 2] = y;
}
public void apply (Skeleton skeleton, float time, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
Bone bone = skeleton.bones.get(boneIndex);
if (time >= frames[frames.length - 3]) { // Time is after last frame.
bone.x += (bone.data.x + frames[frames.length - 2] - bone.x) * alpha;
bone.y += (bone.data.y + frames[frames.length - 1] - bone.y) * alpha;
return;
}
// Interpolate between the last frame and the current frame.
int frameIndex = binarySearch(frames, time, 3);
float lastFrameX = frames[frameIndex - 2];
float lastFrameY = frames[frameIndex - 1];
float frameTime = frames[frameIndex];
float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime), 0, 1);
percent = getCurvePercent(frameIndex / 3 - 1, percent);
bone.x += (bone.data.x + lastFrameX + (frames[frameIndex + FRAME_X] - lastFrameX) * percent - bone.x) * alpha;
bone.y += (bone.data.y + lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent - bone.y) * alpha;
}
}
static public class ScaleTimeline extends TranslateTimeline {
public ScaleTimeline (int keyframeCount) {
super(keyframeCount);
}
public void apply (Skeleton skeleton, float time, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
Bone bone = skeleton.bones.get(boneIndex);
if (time >= frames[frames.length - 3]) { // Time is after last frame.
bone.scaleX += (bone.data.scaleX - 1 + frames[frames.length - 2] - bone.scaleX) * alpha;
bone.scaleY += (bone.data.scaleY - 1 + frames[frames.length - 1] - bone.scaleY) * alpha;
return;
}
// Interpolate between the last frame and the current frame.
int frameIndex = binarySearch(frames, time, 3);
float lastFrameX = frames[frameIndex - 2];
float lastFrameY = frames[frameIndex - 1];
float frameTime = frames[frameIndex];
float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime), 0, 1);
percent = getCurvePercent(frameIndex / 3 - 1, percent);
bone.scaleX += (bone.data.scaleX - 1 + lastFrameX + (frames[frameIndex + FRAME_X] - lastFrameX) * percent - bone.scaleX)
* alpha;
bone.scaleY += (bone.data.scaleY - 1 + lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent - bone.scaleY)
* alpha;
}
}
static public class ColorTimeline extends CurveTimeline {
static private final int LAST_FRAME_TIME = -5;
static private final int FRAME_R = 1;
static private final int FRAME_G = 2;
static private final int FRAME_B = 3;
static private final int FRAME_A = 4;
private int slotIndex;
private final float[] frames; // time, r, g, b, a, ...
public ColorTimeline (int keyframeCount) {
super(keyframeCount);
frames = new float[keyframeCount * 5];
}
public float getDuration () {
return frames[frames.length - 5];
}
public int getKeyframeCount () {
return frames.length / 5;
}
public void setSlotIndex (int slotIndex) {
this.slotIndex = slotIndex;
}
public int getSlotIndex () {
return slotIndex;
}
public float[] getKeyframes () {
return frames;
}
/** Sets the time and value of the specified keyframe. */
public void setKeyframe (int keyframeIndex, float time, float r, float g, float b, float a) {
keyframeIndex *= 5;
frames[keyframeIndex] = time;
frames[keyframeIndex + 1] = r;
frames[keyframeIndex + 2] = g;
frames[keyframeIndex + 3] = b;
frames[keyframeIndex + 4] = a;
}
public void apply (Skeleton skeleton, float time, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
Color color = skeleton.slots.get(slotIndex).color;
if (time >= frames[frames.length - 5]) { // Time is after last frame.
int i = frames.length - 1;
float r = frames[i - 3];
float g = frames[i - 2];
float b = frames[i - 1];
float a = frames[i];
color.set(r, g, b, a);
return;
}
// Interpolate between the last frame and the current frame.
int frameIndex = binarySearch(frames, time, 5);
float lastFrameR = frames[frameIndex - 4];
float lastFrameG = frames[frameIndex - 3];
float lastFrameB = frames[frameIndex - 2];
float lastFrameA = frames[frameIndex - 1];
float frameTime = frames[frameIndex];
float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime), 0, 1);
percent = getCurvePercent(frameIndex / 5 - 1, percent);
float r = lastFrameR + (frames[frameIndex + FRAME_R] - lastFrameR) * percent;
float g = lastFrameG + (frames[frameIndex + FRAME_G] - lastFrameG) * percent;
float b = lastFrameB + (frames[frameIndex + FRAME_B] - lastFrameB) * percent;
float a = lastFrameA + (frames[frameIndex + FRAME_A] - lastFrameA) * percent;
if (alpha < 1)
color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha);
else
color.set(r, g, b, a);
}
}
static public class AttachmentTimeline implements Timeline {
private int slotIndex;
private final float[] frames; // time, ...
private final String[] attachmentNames;
public AttachmentTimeline (int keyframeCount) {
frames = new float[keyframeCount];
attachmentNames = new String[keyframeCount];
}
public float getDuration () {
return frames[frames.length - 1];
}
public int getKeyframeCount () {
return frames.length;
}
public int getSlotIndex () {
return slotIndex;
}
public void setSlotIndex (int slotIndex) {
this.slotIndex = slotIndex;
}
public float[] getKeyframes () {
return frames;
}
public String[] getAttachmentNames () {
return attachmentNames;
}
/** Sets the time and value of the specified keyframe. */
public void setKeyframe (int keyframeIndex, float time, String attachmentName) {
frames[keyframeIndex] = time;
attachmentNames[keyframeIndex] = attachmentName;
}
public void apply (Skeleton skeleton, float time, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
int frameIndex;
if (time >= frames[frames.length - 1]) // Time is after last frame.
frameIndex = frames.length - 1;
else
frameIndex = binarySearch(frames, time, 1) - 1;
String attachmentName = attachmentNames[frameIndex];
skeleton.slots.get(slotIndex).setAttachment(
attachmentName == null ? null : skeleton.getAttachment(slotIndex, attachmentName));
}
}
}

View File

@ -0,0 +1,91 @@
package com.esotericsoftware.spine;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
abstract public class Attachment {
final String name;
boolean resolved;
private float x, y, scaleX, scaleY, rotation, width, height;
public Attachment (String name) {
if (name == null) throw new IllegalArgumentException("name cannot be null.");
this.name = name;
}
abstract public void updateOffset ();
abstract public void draw (SpriteBatch batch, Slot slot);
public float getX () {
return x;
}
public void setX (float x) {
this.x = x;
}
public float getY () {
return y;
}
public void setY (float y) {
this.y = y;
}
public float getScaleX () {
return scaleX;
}
public void setScaleX (float scaleX) {
this.scaleX = scaleX;
}
public float getScaleY () {
return scaleY;
}
public void setScaleY (float scaleY) {
this.scaleY = scaleY;
}
public float getRotation () {
return rotation;
}
public void setRotation (float rotation) {
this.rotation = rotation;
}
public float getWidth () {
return width;
}
public void setWidth (float width) {
this.width = width;
}
public float getHeight () {
return height;
}
public void setHeight (float height) {
this.height = height;
}
public boolean isResolved () {
return resolved;
}
public void setResolved (boolean resolved) {
this.resolved = resolved;
}
public String getName () {
return name;
}
public String toString () {
return name;
}
}

View File

@ -0,0 +1,6 @@
package com.esotericsoftware.spine;
public interface AttachmentResolver {
public void resolve (Attachment attachment);
}

View File

@ -0,0 +1,185 @@
package com.esotericsoftware.spine;
import static com.badlogic.gdx.math.Matrix3.*;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Matrix3;
public class Bone {
final BoneData data;
final Bone parent;
float x, y;
float rotation;
float scaleX = 1, scaleY = 1;
float m00, m01, worldX; // a b x
float m10, m11, worldY; // c d y
float worldRotation;
float worldScaleX, worldScaleY;
/** @param parent May be null. */
public Bone (BoneData data, Bone parent) {
if (data == null) throw new IllegalArgumentException("data cannot be null.");
this.data = data;
this.parent = parent;
setToBindPose();
}
/** Copy constructor.
* @param parent May be null. */
public Bone (Bone bone, Bone parent) {
if (bone == null) throw new IllegalArgumentException("bone cannot be null.");
this.parent = parent;
data = bone.data;
x = bone.x;
y = bone.y;
rotation = bone.rotation;
scaleX = bone.scaleX;
scaleY = bone.scaleY;
}
/** Computes the world SRT using the parent bone and the local SRT. */
public void updateWorldTransform (boolean flipX, boolean flipY) {
Bone parent = this.parent;
if (parent != null) {
worldX = x * parent.m00 + y * parent.m01 + parent.worldX;
worldY = x * parent.m10 + y * parent.m11 + parent.worldY;
worldScaleX = parent.worldScaleX * scaleX;
worldScaleY = parent.worldScaleY * scaleY;
worldRotation = parent.worldRotation + rotation;
} else {
worldX = x;
worldY = y;
worldScaleX = scaleX;
worldScaleY = scaleY;
worldRotation = rotation;
}
float cos = MathUtils.cosDeg(worldRotation);
float sin = MathUtils.sinDeg(worldRotation);
m00 = cos * worldScaleX;
m10 = sin * worldScaleX;
m01 = -sin * worldScaleY;
m11 = cos * worldScaleY;
if (flipX) {
m00 = -m00;
m01 = -m01;
}
if (flipY) {
m10 = -m10;
m11 = -m11;
}
}
public void setToBindPose () {
BoneData data = this.data;
x = data.x;
y = data.y;
rotation = data.rotation;
scaleX = data.scaleX;
scaleY = data.scaleY;
}
public BoneData getData () {
return data;
}
public Bone getParent () {
return parent;
}
public float getX () {
return x;
}
public void setX (float x) {
this.x = x;
}
public float getY () {
return y;
}
public void setY (float y) {
this.y = y;
}
public float getRotation () {
return rotation;
}
public void setRotation (float rotation) {
this.rotation = rotation;
}
public float getScaleX () {
return scaleX;
}
public void setScaleX (float scaleX) {
this.scaleX = scaleX;
}
public float getScaleY () {
return scaleY;
}
public void setScaleY (float scaleY) {
this.scaleY = scaleY;
}
public float getM00 () {
return m00;
}
public float getM01 () {
return m01;
}
public float getM10 () {
return m10;
}
public float getM11 () {
return m11;
}
public float getWorldX () {
return worldX;
}
public float getWorldY () {
return worldY;
}
public float getWorldRotation () {
return worldRotation;
}
public float getWorldScaleX () {
return worldScaleX;
}
public float getWorldScaleY () {
return worldScaleY;
}
public Matrix3 getWorldTransform (Matrix3 worldTransform) {
if (worldTransform == null) throw new IllegalArgumentException("worldTransform cannot be null.");
float[] val = worldTransform.val;
val[M00] = m00;
val[M01] = m01;
val[M02] = worldX;
val[M10] = m10;
val[M11] = m11;
val[M12] = worldY;
val[M20] = 0;
val[M21] = 0;
val[M22] = 1;
return worldTransform;
}
public String toString () {
return data.name;
}
}

View File

@ -0,0 +1,93 @@
package com.esotericsoftware.spine;
public class BoneData {
final BoneData parent;
final String name;
float length;
float x, y;
float rotation;
float scaleX = 1, scaleY = 1;
/** @param parent May be null. */
public BoneData (String name, BoneData parent) {
if (name == null) throw new IllegalArgumentException("name cannot be null.");
this.name = name;
this.parent = parent;
}
/** Copy constructor.
* @param parent May be null. */
public BoneData (BoneData bone, BoneData parent) {
if (bone == null) throw new IllegalArgumentException("bone cannot be null.");
this.parent = parent;
name = bone.name;
length = bone.length;
x = bone.x;
y = bone.y;
rotation = bone.rotation;
scaleX = bone.scaleX;
scaleY = bone.scaleY;
}
/** @return May be null. */
public BoneData getParent () {
return parent;
}
public String getName () {
return name;
}
public float getLength () {
return length;
}
public void setLength (float length) {
this.length = length;
}
public float getX () {
return x;
}
public void setX (float x) {
this.x = x;
}
public float getY () {
return y;
}
public void setY (float y) {
this.y = y;
}
public float getRotation () {
return rotation;
}
public void setRotation (float rotation) {
this.rotation = rotation;
}
public float getScaleX () {
return scaleX;
}
public void setScaleX (float scaleX) {
this.scaleX = scaleX;
}
public float getScaleY () {
return scaleY;
}
public void setScaleY (float scaleY) {
this.scaleY = scaleY;
}
public String toString () {
return name;
}
}

View File

@ -0,0 +1,279 @@
package com.esotericsoftware.spine;
import com.esotericsoftware.spine.Skin.Key;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ObjectMap.Entry;
public class Skeleton {
final SkeletonData data;
final Array<Bone> bones;
final Array<Slot> slots;
final Array<Slot> drawOrder;
Skin skin;
final Color color;
float time;
boolean flipX, flipY;
public Skeleton (SkeletonData data) {
if (data == null) throw new IllegalArgumentException("data cannot be null.");
this.data = data;
bones = new Array(data.bones.size);
for (BoneData boneData : data.bones) {
Bone parent = boneData.parent == null ? null : bones.get(data.bones.indexOf(boneData.parent, true));
bones.add(new Bone(boneData, parent));
}
slots = new Array(data.slots.size);
drawOrder = new Array(data.slots.size);
for (SlotData slotData : data.slots) {
Bone bone = bones.get(data.bones.indexOf(slotData.boneData, true));
Slot slot = new Slot(slotData, this, bone);
slots.add(slot);
drawOrder.add(slot);
}
color = new Color(1, 1, 1, 1);
}
/** Copy constructor. */
public Skeleton (Skeleton skeleton) {
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
data = skeleton.data;
bones = new Array(skeleton.bones.size);
for (Bone bone : skeleton.bones) {
Bone parent = bones.get(skeleton.bones.indexOf(bone.parent, true));
bones.add(new Bone(bone, parent));
}
slots = new Array(skeleton.slots.size);
for (Slot slot : skeleton.slots) {
Bone bone = bones.get(skeleton.bones.indexOf(slot.bone, true));
Slot newSlot = new Slot(slot, this, bone);
slots.add(newSlot);
}
drawOrder = new Array(slots.size);
for (Slot slot : skeleton.drawOrder)
drawOrder.add(slots.get(skeleton.slots.indexOf(slot, true)));
skin = skeleton.skin;
color = new Color(skeleton.color);
time = skeleton.time;
}
/** Updates the world transform for each bone. */
public void updateWorldTransform () {
boolean flipX = this.flipX;
boolean flipY = this.flipY;
Array<Bone> bones = this.bones;
for (int i = 0, n = bones.size; i < n; i++)
bones.get(i).updateWorldTransform(flipX, flipY);
}
/** Sets the bones and slots to their bind pose values. */
public void setToBindPose () {
setBonesToBindPose();
setSlotsToBindPose();
}
public void setBonesToBindPose () {
Array<Bone> bones = this.bones;
for (int i = 0, n = bones.size; i < n; i++)
bones.get(i).setToBindPose();
}
public void setSlotsToBindPose () {
Array<Slot> slots = this.slots;
for (int i = 0, n = slots.size; i < n; i++)
slots.get(i).setToBindPose(i);
}
public void draw (SpriteBatch batch) {
Array<Slot> drawOrder = this.drawOrder;
for (int i = 0, n = drawOrder.size; i < n; i++) {
Slot slot = drawOrder.get(i);
Attachment attachment = slot.attachment;
if (attachment != null) {
if (!attachment.resolved) data.attachmentResolver.resolve(attachment);
attachment.updateOffset();
attachment.draw(batch, slot);
}
}
}
public void drawDebug (ShapeRenderer renderer) {
renderer.setColor(Color.RED);
renderer.begin(ShapeType.Line);
for (int i = 0, n = bones.size; i < n; i++) {
Bone bone = bones.get(i);
if (bone.parent == null) continue;
float x = bone.data.length * bone.m00 + bone.worldX;
float y = bone.data.length * bone.m10 + bone.worldY;
renderer.line(bone.worldX, bone.worldY, x, y);
}
renderer.end();
renderer.setColor(Color.GREEN);
renderer.begin(ShapeType.Filled);
for (int i = 0, n = bones.size; i < n; i++) {
Bone bone = bones.get(i);
renderer.setColor(Color.GREEN);
renderer.circle(bone.worldX, bone.worldY, 3);
}
renderer.end();
}
public SkeletonData getData () {
return data;
}
public Array<Bone> getBones () {
return bones;
}
/** @return May return null. */
public Bone getRootBone () {
if (bones.size == 0) return null;
return bones.first();
}
/** @return May be null. */
public Bone findBone (String boneName) {
if (boneName == null) throw new IllegalArgumentException("boneName cannot be null.");
Array<Bone> bones = this.bones;
for (int i = 0, n = bones.size; i < n; i++) {
Bone bone = bones.get(i);
if (bone.data.name.equals(boneName)) return bone;
}
return null;
}
/** @return -1 if the bone was not found. */
public int findBoneIndex (String boneName) {
if (boneName == null) throw new IllegalArgumentException("boneName cannot be null.");
Array<Bone> bones = this.bones;
for (int i = 0, n = bones.size; i < n; i++)
if (bones.get(i).data.name.equals(boneName)) return i;
return -1;
}
public Array<Slot> getSlots () {
return slots;
}
/** @return May be null. */
public Slot findSlot (String slotName) {
if (slotName == null) throw new IllegalArgumentException("slotName cannot be null.");
Array<Slot> slots = this.slots;
for (int i = 0, n = slots.size; i < n; i++) {
Slot slot = slots.get(i);
if (slot.data.name.equals(slotName)) return slot;
}
return null;
}
/** @return -1 if the bone was not found. */
public int findSlotIndex (String slotName) {
if (slotName == null) throw new IllegalArgumentException("slotName cannot be null.");
Array<Slot> slots = this.slots;
for (int i = 0, n = slots.size; i < n; i++)
if (slots.get(i).data.name.equals(slotName)) return i;
return -1;
}
/** Returns the slots in the order they will be drawn. The returned array may be modified to change the draw order. */
public Array<Slot> getDrawOrder () {
return drawOrder;
}
/** @return May be null. */
public Skin getSkin () {
return skin;
}
/** Sets a skin by name.
* @see #setSkin(Skin) */
public void setSkin (String skinName) {
Skin skin = data.findSkin(skinName);
if (skin == null) throw new IllegalArgumentException("Skin not found: " + skinName);
setSkin(skin);
}
/** Sets the skin used to look up attachments not found in the {@link SkeletonData#getDefaultSkin() default skin}. Attachments
* from the new skin are attached if the corresponding attachment from the old skin is currently attached.
* @param newSkin May be null. */
public void setSkin (Skin newSkin) {
if (skin != null && newSkin != null) newSkin.attachAll(this, skin);
skin = newSkin;
}
/** @return May be null. */
public Attachment getAttachment (String slotName, String attachmentName) {
return getAttachment(data.findSlotIndex(slotName), attachmentName);
}
/** @return May be null. */
public Attachment getAttachment (int slotIndex, String attachmentName) {
if (attachmentName == null) throw new IllegalArgumentException("attachmentName cannot be null.");
if (data.defaultSkin != null) {
Attachment attachment = data.defaultSkin.getAttachment(slotIndex, attachmentName);
if (attachment != null) return attachment;
}
if (skin != null) return skin.getAttachment(slotIndex, attachmentName);
return null;
}
/** @param attachmentName May be null. */
public void setAttachment (String slotName, String attachmentName) {
if (slotName == null) throw new IllegalArgumentException("slotName cannot be null.");
if (attachmentName == null) throw new IllegalArgumentException("attachmentName cannot be null.");
for (int i = 0, n = slots.size; i < n; i++) {
Slot slot = slots.get(i);
if (slot.data.name.equals(slotName)) {
slot.setAttachment(getAttachment(i, attachmentName));
return;
}
}
throw new IllegalArgumentException("Slot not found: " + slotName);
}
public Color getColor () {
return color;
}
public boolean getFlipX () {
return flipX;
}
public void setFlipX (boolean flipX) {
this.flipX = flipX;
}
public boolean getFlipY () {
return flipY;
}
public void setFlipY (boolean flipY) {
this.flipY = flipY;
}
public float getTime () {
return time;
}
public void setTime (float time) {
this.time = time;
}
public void update (float delta) {
time += delta;
}
}

View File

@ -0,0 +1,270 @@
package com.esotericsoftware.spine;
import com.esotericsoftware.spine.Animation.AttachmentTimeline;
import com.esotericsoftware.spine.Animation.ColorTimeline;
import com.esotericsoftware.spine.Animation.CurveTimeline;
import com.esotericsoftware.spine.Animation.RotateTimeline;
import com.esotericsoftware.spine.Animation.ScaleTimeline;
import com.esotericsoftware.spine.Animation.Timeline;
import com.esotericsoftware.spine.Animation.TranslateTimeline;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.RegionSequenceAttachment;
import com.esotericsoftware.spine.attachments.RegionSequenceAttachment.Mode;
import com.esotericsoftware.spine.attachments.TextureAtlasAttachmentResolver;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.DataInput;
import com.badlogic.gdx.utils.SerializationException;
import java.io.IOException;
public class SkeletonBinary {
static public final int TIMELINE_SCALE = 0;
static public final int TIMELINE_ROTATE = 1;
static public final int TIMELINE_TRANSLATE = 2;
static public final int TIMELINE_ATTACHMENT = 3;
static public final int TIMELINE_COLOR = 4;
static public final int ATTACHMENT_REGION = 0;
static public final int ATTACHMENT_REGION_SEQUENCE = 1;
static public final int CURVE_LINEAR = 0;
static public final int CURVE_STEPPED = 1;
static public final int CURVE_BEZIER = 2;
private final AttachmentResolver attachmentResolver;
private float scale = 1;
public SkeletonBinary (TextureAtlas atlas) {
attachmentResolver = new TextureAtlasAttachmentResolver(atlas);
}
public SkeletonBinary (AttachmentResolver attachmentResolver) {
this.attachmentResolver = attachmentResolver;
}
public float getScale () {
return scale;
}
/** Scales the bones, images, and animations as they are loaded. */
public void setScale (float scale) {
this.scale = scale;
}
public SkeletonData readSkeletonData (FileHandle file) {
if (file == null) throw new IllegalArgumentException("file cannot be null.");
SkeletonData skeletonData = new SkeletonData(attachmentResolver);
DataInput input = new DataInput(file.read(512));
try {
// Bones.
for (int i = 0, n = input.readInt(true); i < n; i++) {
String name = input.readString();
BoneData parent = null;
String parentName = input.readString();
if (parentName != null) {
parent = skeletonData.findBone(parentName);
if (parent == null) throw new SerializationException("Bone not found: " + parentName);
}
BoneData boneData = new BoneData(name, parent);
boneData.x = input.readFloat() * scale;
boneData.y = input.readFloat() * scale;
boneData.scaleX = input.readFloat();
boneData.scaleY = input.readFloat();
boneData.rotation = input.readFloat();
boneData.length = input.readFloat() * scale;
skeletonData.addBone(boneData);
}
// Slots.
for (int i = 0, n = input.readInt(true); i < n; i++) {
String slotName = input.readString();
String boneName = input.readString();
BoneData boneData = skeletonData.findBone(boneName);
if (boneData == null) throw new SerializationException("Bone not found: " + boneName);
SlotData slotData = new SlotData(slotName, boneData);
Color.rgba8888ToColor(slotData.getColor(), input.readInt());
slotData.setAttachmentName(input.readString());
skeletonData.addSlot(slotData);
}
// Default skin.
Skin defaultSkin = readSkin(input, "default");
if (defaultSkin != null) {
skeletonData.setDefaultSkin(defaultSkin);
skeletonData.addSkin(defaultSkin);
}
// Skins.
for (int i = 0, n = input.readInt(true); i < n; i++)
skeletonData.addSkin(readSkin(input, input.readString()));
input.close();
} catch (IOException ex) {
throw new SerializationException("Error reading skeleton file.", ex);
}
skeletonData.bones.shrink();
skeletonData.slots.shrink();
skeletonData.skins.shrink();
return skeletonData;
}
private Skin readSkin (DataInput input, String skinName) throws IOException {
int slotCount = input.readInt(true);
if (slotCount == 0) return null;
Skin skin = new Skin(skinName);
for (int i = 0; i < slotCount; i++) {
int slotIndex = input.readInt(true);
int attachmentCount = input.readInt(true);
for (int ii = 0; ii < attachmentCount; ii++) {
String name = input.readString();
skin.addAttachment(slotIndex, name, readAttachment(input, name));
}
}
return skin;
}
private Attachment readAttachment (DataInput input, String attachmentName) throws IOException {
String name = input.readString();
if (name == null) name = attachmentName;
Attachment attachment;
int type = input.readByte();
switch (type) {
case ATTACHMENT_REGION:
attachment = new RegionAttachment(name);
break;
case ATTACHMENT_REGION_SEQUENCE:
float fps = input.readFloat();
Mode mode = Mode.values()[input.readInt(true)];
attachment = new RegionSequenceAttachment(name, 1 / fps, mode);
break;
default:
throw new SerializationException("Unknown attachment type: " + type + " (" + name + ")");
}
attachment.setX(input.readFloat() * scale);
attachment.setY(input.readFloat() * scale);
attachment.setScaleX(input.readFloat());
attachment.setScaleY(input.readFloat());
attachment.setRotation(input.readFloat());
attachment.setWidth(input.readFloat() * scale);
attachment.setHeight(input.readFloat() * scale);
return attachment;
}
public Animation readAnimation (FileHandle file, SkeletonData skeleton) {
if (file == null) throw new IllegalArgumentException("file cannot be null.");
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
Array<Timeline> timelines = new Array();
float duration = 0;
DataInput input = new DataInput(file.read(512));
try {
int boneCount = input.readInt(true);
for (int i = 0; i < boneCount; i++) {
String boneName = input.readString();
int boneIndex = skeleton.findBoneIndex(boneName);
if (boneIndex == -1) throw new SerializationException("Bone not found: " + boneName);
int itemCount = input.readInt(true);
for (int ii = 0; ii < itemCount; ii++) {
int timelineType = input.readByte();
int keyCount = input.readInt(true);
switch (timelineType) {
case TIMELINE_ROTATE: {
RotateTimeline timeline = new RotateTimeline(keyCount);
timeline.setBoneIndex(boneIndex);
for (int keyframeIndex = 0; keyframeIndex < keyCount; keyframeIndex++) {
timeline.setKeyframe(keyframeIndex, input.readFloat(), input.readFloat());
if (keyframeIndex < keyCount - 1) readCurve(input, keyframeIndex, timeline);
}
timelines.add(timeline);
duration = Math.max(duration, timeline.getDuration());
break;
}
case TIMELINE_TRANSLATE:
case TIMELINE_SCALE:
TranslateTimeline timeline;
float timelineScale = 1;
if (timelineType == TIMELINE_SCALE)
timeline = new ScaleTimeline(keyCount);
else {
timeline = new TranslateTimeline(keyCount);
timelineScale = scale;
}
timeline.setBoneIndex(boneIndex);
for (int keyframeIndex = 0; keyframeIndex < keyCount; keyframeIndex++) {
timeline.setKeyframe(keyframeIndex, input.readFloat(), input.readFloat() * timelineScale, input.readFloat()
* timelineScale);
if (keyframeIndex < keyCount - 1) readCurve(input, keyframeIndex, timeline);
}
timelines.add(timeline);
duration = Math.max(duration, timeline.getDuration());
break;
default:
throw new RuntimeException("Invalid timeline type for a bone: " + timelineType + " (" + boneName + ")");
}
}
}
int slotCount = input.readInt(true);
for (int i = 0; i < slotCount; i++) {
String slotName = input.readString();
int slotIndex = skeleton.findSlotIndex(slotName);
int itemCount = input.readInt(true);
for (int ii = 0; ii < itemCount; ii++) {
int timelineType = input.readByte();
int keyCount = input.readInt(true);
switch (timelineType) {
case TIMELINE_COLOR: {
ColorTimeline timeline = new ColorTimeline(keyCount);
timeline.setSlotIndex(slotIndex);
for (int keyframeIndex = 0; keyframeIndex < keyCount; keyframeIndex++) {
float time = input.readFloat();
Color.rgba8888ToColor(Color.tmp, input.readInt());
timeline.setKeyframe(keyframeIndex, time, Color.tmp.r, Color.tmp.g, Color.tmp.b, Color.tmp.a);
if (keyframeIndex < keyCount - 1) readCurve(input, keyframeIndex, timeline);
}
timelines.add(timeline);
duration = Math.max(duration, timeline.getDuration());
break;
}
case TIMELINE_ATTACHMENT:
AttachmentTimeline timeline = new AttachmentTimeline(keyCount);
timeline.setSlotIndex(slotIndex);
for (int keyframeIndex = 0; keyframeIndex < keyCount; keyframeIndex++)
timeline.setKeyframe(keyframeIndex, input.readFloat(), input.readString());
timelines.add(timeline);
duration = Math.max(duration, timeline.getDuration());
break;
default:
throw new RuntimeException("Invalid timeline type for a slot: " + timelineType + " (" + slotName + ")");
}
}
}
} catch (IOException ex) {
throw new SerializationException("Error reading skeleton file.", ex);
}
timelines.shrink();
return new Animation(timelines, duration);
}
private void readCurve (DataInput input, int keyframeIndex, CurveTimeline timeline) throws IOException {
switch (input.readByte()) {
case CURVE_STEPPED:
timeline.setStepped(keyframeIndex);
break;
case CURVE_BEZIER:
timeline.setCurve(keyframeIndex, input.readFloat(), input.readFloat(), input.readFloat(), input.readFloat());
break;
}
}
}

View File

@ -0,0 +1,120 @@
package com.esotericsoftware.spine;
import com.badlogic.gdx.utils.Array;
public class SkeletonData {
final Array<BoneData> bones = new Array(); // Ordered parents first.
final Array<SlotData> slots = new Array(); // Bind pose draw order.
final Array<Skin> skins = new Array();
Skin defaultSkin;
final AttachmentResolver attachmentResolver;
public SkeletonData (AttachmentResolver attachmentResolver) {
if (attachmentResolver == null) throw new IllegalArgumentException("attachmentResolver cannot be null.");
this.attachmentResolver = attachmentResolver;
}
public void clear () {
bones.clear();
slots.clear();
skins.clear();
defaultSkin = null;
}
public AttachmentResolver getAttachmentResolver () {
return attachmentResolver;
}
// --- Bones.
public void addBone (BoneData bone) {
if (bone == null) throw new IllegalArgumentException("bone cannot be null.");
bones.add(bone);
}
public Array<BoneData> getBones () {
return bones;
}
/** @return May be null. */
public BoneData findBone (String boneName) {
if (boneName == null) throw new IllegalArgumentException("boneName cannot be null.");
Array<BoneData> bones = this.bones;
for (int i = 0, n = bones.size; i < n; i++) {
BoneData bone = bones.get(i);
if (bone.name.equals(boneName)) return bone;
}
return null;
}
/** @return -1 if the bone was not found. */
public int findBoneIndex (String boneName) {
if (boneName == null) throw new IllegalArgumentException("boneName cannot be null.");
Array<BoneData> bones = this.bones;
for (int i = 0, n = bones.size; i < n; i++)
if (bones.get(i).name.equals(boneName)) return i;
return -1;
}
// --- Slots.
public void addSlot (SlotData slot) {
if (slot == null) throw new IllegalArgumentException("slot cannot be null.");
slots.add(slot);
}
public Array<SlotData> getSlots () {
return slots;
}
/** @return May be null. */
public SlotData findSlot (String slotName) {
if (slotName == null) throw new IllegalArgumentException("slotName cannot be null.");
Array<SlotData> slots = this.slots;
for (int i = 0, n = slots.size; i < n; i++) {
SlotData slot = slots.get(i);
if (slot.name.equals(slotName)) return slot;
}
return null;
}
/** @return -1 if the bone was not found. */
public int findSlotIndex (String slotName) {
if (slotName == null) throw new IllegalArgumentException("slotName cannot be null.");
Array<SlotData> slots = this.slots;
for (int i = 0, n = slots.size; i < n; i++)
if (slots.get(i).name.equals(slotName)) return i;
return -1;
}
// --- Skins.
/** @return May be null. */
public Skin getDefaultSkin () {
return defaultSkin;
}
/** @param defaultSkin May be null. */
public void setDefaultSkin (Skin defaultSkin) {
this.defaultSkin = defaultSkin;
}
public void addSkin (Skin skin) {
if (skin == null) throw new IllegalArgumentException("skin cannot be null.");
skins.add(skin);
}
/** @return May be null. */
public Skin findSkin (String skinName) {
if (skinName == null) throw new IllegalArgumentException("skinName cannot be null.");
for (Skin skin : skins)
if (skin.name.equals(skinName)) return skin;
return null;
}
/** Returns all skins, including the default skin. */
public Array<Skin> getSkins () {
return skins;
}
}

View File

@ -0,0 +1,277 @@
package com.esotericsoftware.spine;
import com.esotericsoftware.spine.Animation.AttachmentTimeline;
import com.esotericsoftware.spine.Animation.ColorTimeline;
import com.esotericsoftware.spine.Animation.CurveTimeline;
import com.esotericsoftware.spine.Animation.RotateTimeline;
import com.esotericsoftware.spine.Animation.ScaleTimeline;
import com.esotericsoftware.spine.Animation.Timeline;
import com.esotericsoftware.spine.Animation.TranslateTimeline;
import com.esotericsoftware.spine.attachments.RegionSequenceAttachment;
import com.esotericsoftware.spine.attachments.RegionSequenceAttachment.Mode;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.TextureAtlasAttachmentResolver;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.ObjectMap.Entry;
import com.badlogic.gdx.utils.OrderedMap;
import com.badlogic.gdx.utils.SerializationException;
public class SkeletonJson {
static public final String TIMELINE_SCALE = "scale";
static public final String TIMELINE_ROTATE = "rotate";
static public final String TIMELINE_TRANSLATE = "translate";
static public final String TIMELINE_ATTACHMENT = "attachment";
static public final String TIMELINE_COLOR = "color";
static public final String ATTACHMENT_REGION = "region";
static public final String ATTACHMENT_REGION_SEQUENCE = "regionSequence";
private final Json json = new Json();
private final AttachmentResolver attachmentResolver;
private float scale = 1;
public SkeletonJson (TextureAtlas atlas) {
attachmentResolver = new TextureAtlasAttachmentResolver(atlas);
}
public SkeletonJson (AttachmentResolver attachmentResolver) {
this.attachmentResolver = attachmentResolver;
}
public float getScale () {
return scale;
}
/** Scales the bones, images, and animations as they are loaded. */
public void setScale (float scale) {
this.scale = scale;
}
public SkeletonData readSkeletonData (FileHandle file) {
if (file == null) throw new IllegalArgumentException("file cannot be null.");
SkeletonData skeletonData = new SkeletonData(attachmentResolver);
OrderedMap<String, ?> root = json.fromJson(OrderedMap.class, file);
// Bones.
for (OrderedMap boneMap : (Array<OrderedMap>)root.get("bones")) {
BoneData parent = null;
String parentName = (String)boneMap.get("parent");
if (parentName != null) {
parent = skeletonData.findBone(parentName);
if (parent == null) throw new SerializationException("Parent bone not found: " + parentName);
}
BoneData boneData = new BoneData((String)boneMap.get("name"), parent);
boneData.length = getFloat(boneMap, "length", 0) * scale;
boneData.x = getFloat(boneMap, "x", 0) * scale;
boneData.y = getFloat(boneMap, "y", 0) * scale;
boneData.rotation = getFloat(boneMap, "rotation", 0);
boneData.scaleX = getFloat(boneMap, "scaleX", 1);
boneData.scaleY = getFloat(boneMap, "scaleY", 1);
skeletonData.addBone(boneData);
}
// Slots.
Array<OrderedMap> slots = (Array<OrderedMap>)root.get("slots");
if (slots != null) {
for (OrderedMap slotMap : slots) {
String slotName = (String)slotMap.get("name");
String boneName = (String)slotMap.get("bone");
BoneData boneData = skeletonData.findBone(boneName);
if (boneData == null) throw new SerializationException("Slot bone not found: " + boneName);
SlotData slotData = new SlotData(slotName, boneData);
String color = (String)slotMap.get("color");
if (color != null) slotData.getColor().set(Color.valueOf(color));
slotData.setAttachmentName((String)slotMap.get("attachment"));
skeletonData.addSlot(slotData);
}
}
// Skins.
OrderedMap<String, OrderedMap> slotMap = (OrderedMap)root.get("skins");
if (slotMap != null) {
for (Entry<String, OrderedMap> entry : slotMap.entries()) {
Skin skin = new Skin(entry.key);
for (Entry<String, OrderedMap> slotEntry : ((OrderedMap<String, OrderedMap>)entry.value).entries()) {
int slotIndex = skeletonData.findSlotIndex(slotEntry.key);
for (Entry<String, OrderedMap> attachmentEntry : ((OrderedMap<String, OrderedMap>)slotEntry.value).entries()) {
Attachment attachment = readAttachment(attachmentEntry.key, attachmentEntry.value);
skin.addAttachment(slotIndex, attachmentEntry.key, attachment);
}
}
skeletonData.addSkin(skin);
if (skin.name.equals("default")) skeletonData.setDefaultSkin(skin);
}
}
skeletonData.bones.shrink();
skeletonData.slots.shrink();
skeletonData.skins.shrink();
return skeletonData;
}
private Attachment readAttachment (String name, OrderedMap map) {
name = (String)map.get("name", name);
Attachment attachment;
String type = (String)map.get("type");
if (type == null) type = ATTACHMENT_REGION;
if (type.equals(ATTACHMENT_REGION)) {
attachment = new RegionAttachment(name);
} else if (type.equals(ATTACHMENT_REGION_SEQUENCE)) {
Float fps = (Float)map.get("fps");
if (fps == null) throw new SerializationException("Region sequence attachment missing fps: " + name);
String modeString = (String)map.get("mode");
Mode mode = modeString == null ? Mode.forward : Mode.valueOf(modeString);
attachment = new RegionSequenceAttachment(name, 1 / fps, mode);
} else
throw new SerializationException("Unknown attachment type: " + type + " (" + name + ")");
attachment.setX(getFloat(map, "x", 0) * scale);
attachment.setY(getFloat(map, "y", 0) * scale);
attachment.setScaleX(getFloat(map, "scaleX", 1));
attachment.setScaleY(getFloat(map, "scaleY", 1));
attachment.setRotation(getFloat(map, "rotation", 0));
attachment.setWidth(getFloat(map, "width", 32) * scale);
attachment.setHeight(getFloat(map, "height", 32) * scale);
return attachment;
}
private float getFloat (OrderedMap map, String name, float defaultValue) {
Object value = map.get(name);
if (value == null) return defaultValue;
return (Float)value;
}
public Animation readAnimation (FileHandle file, SkeletonData skeletonData) {
if (file == null) throw new IllegalArgumentException("file cannot be null.");
if (skeletonData == null) throw new IllegalArgumentException("skeletonData cannot be null.");
OrderedMap<String, ?> map = json.fromJson(OrderedMap.class, file);
Array<Timeline> timelines = new Array();
float duration = 0;
OrderedMap<String, ?> bonesMap = (OrderedMap)map.get("bones");
for (Entry<String, ?> entry : bonesMap.entries()) {
String boneName = entry.key;
int boneIndex = skeletonData.findBoneIndex(boneName);
if (boneIndex == -1) throw new SerializationException("Bone not found: " + boneName);
OrderedMap<?, ?> propertyMap = (OrderedMap)entry.value;
for (Entry propertyEntry : propertyMap.entries()) {
Array<OrderedMap> values = (Array)propertyEntry.value;
String timelineType = (String)propertyEntry.key;
if (timelineType.equals(TIMELINE_ROTATE)) {
RotateTimeline timeline = new RotateTimeline(values.size);
timeline.setBoneIndex(boneIndex);
int keyframeIndex = 0;
for (OrderedMap valueMap : values) {
float time = (Float)valueMap.get("time");
timeline.setKeyframe(keyframeIndex, time, (Float)valueMap.get("angle"));
readCurve(timeline, keyframeIndex, valueMap);
keyframeIndex++;
}
timelines.add(timeline);
duration = Math.max(duration, timeline.getDuration());
} else if (timelineType.equals(TIMELINE_TRANSLATE) || timelineType.equals(TIMELINE_SCALE)) {
TranslateTimeline timeline;
float timelineScale = 1;
if (timelineType.equals(TIMELINE_SCALE))
timeline = new ScaleTimeline(values.size);
else {
timeline = new TranslateTimeline(values.size);
timelineScale = scale;
}
timeline.setBoneIndex(boneIndex);
int keyframeIndex = 0;
for (OrderedMap valueMap : values) {
float time = (Float)valueMap.get("time");
Float x = (Float)valueMap.get("x"), y = (Float)valueMap.get("y");
timeline.setKeyframe(keyframeIndex, time, x == null ? 0 : (x * timelineScale), y == null ? 0
: (y * timelineScale));
readCurve(timeline, keyframeIndex, valueMap);
keyframeIndex++;
}
timelines.add(timeline);
duration = Math.max(duration, timeline.getDuration());
} else
throw new RuntimeException("Invalid timeline type for a bone: " + timelineType + " (" + boneName + ")");
}
}
OrderedMap<String, ?> slotsMap = (OrderedMap)map.get("slots");
if (slotsMap != null) {
for (Entry<String, ?> entry : slotsMap.entries()) {
String slotName = entry.key;
int slotIndex = skeletonData.findSlotIndex(slotName);
OrderedMap<?, ?> propertyMap = (OrderedMap)entry.value;
for (Entry propertyEntry : propertyMap.entries()) {
Array<OrderedMap> values = (Array)propertyEntry.value;
String timelineType = (String)propertyEntry.key;
if (timelineType.equals(TIMELINE_COLOR)) {
ColorTimeline timeline = new ColorTimeline(values.size);
timeline.setSlotIndex(slotIndex);
int keyframeIndex = 0;
for (OrderedMap valueMap : values) {
float time = (Float)valueMap.get("time");
Color color = Color.valueOf((String)valueMap.get("color"));
timeline.setKeyframe(keyframeIndex, time, color.r, color.g, color.b, color.a);
readCurve(timeline, keyframeIndex, valueMap);
keyframeIndex++;
}
timelines.add(timeline);
duration = Math.max(duration, timeline.getDuration());
} else if (timelineType.equals(TIMELINE_ATTACHMENT)) {
AttachmentTimeline timeline = new AttachmentTimeline(values.size);
timeline.setSlotIndex(slotIndex);
int keyframeIndex = 0;
for (OrderedMap valueMap : values) {
float time = (Float)valueMap.get("time");
timeline.setKeyframe(keyframeIndex++, time, (String)valueMap.get("name"));
}
timelines.add(timeline);
duration = Math.max(duration, timeline.getDuration());
} else
throw new RuntimeException("Invalid timeline type for a slot: " + timelineType + " (" + slotName + ")");
}
}
}
timelines.shrink();
return new Animation(timelines, duration);
}
private void readCurve (CurveTimeline timeline, int keyframeIndex, OrderedMap valueMap) {
Object curveObject = valueMap.get("curve");
if (curveObject == null) return;
if (curveObject.equals("stepped"))
timeline.setStepped(keyframeIndex);
else if (curveObject instanceof Array) {
Array curve = (Array)curveObject;
timeline.setCurve(keyframeIndex, (Float)curve.get(0), (Float)curve.get(1), (Float)curve.get(2), (Float)curve.get(3));
}
}
}

View File

@ -0,0 +1,97 @@
package com.esotericsoftware.spine;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ObjectMap;
import com.badlogic.gdx.utils.ObjectMap.Entry;
/** Stores attachments by slot index and attachment name. */
public class Skin {
static private final Key lookup = new Key();
final String name;
final ObjectMap<Key, Attachment> attachments = new ObjectMap();
public Skin (String name) {
if (name == null) throw new IllegalArgumentException("name cannot be null.");
this.name = name;
}
public void addAttachment (int slotIndex, String name, Attachment attachment) {
if (attachment == null) throw new IllegalArgumentException("attachment cannot be null.");
Key key = new Key();
key.set(slotIndex, name);
attachments.put(key, attachment);
}
/** @return May be null. */
public Attachment getAttachment (int slotIndex, String name) {
lookup.set(slotIndex, name);
return attachments.get(lookup);
}
public void findNamesForSlot (int slotIndex, Array<String> names) {
if (names == null) throw new IllegalArgumentException("names cannot be null.");
for (Key key : attachments.keys())
if (key.slotIndex == slotIndex) names.add(key.name);
}
public void findAttachmentsForSlot (int slotIndex, Array<Attachment> attachments) {
if (attachments == null) throw new IllegalArgumentException("attachments cannot be null.");
for (Entry<Key, Attachment> entry : this.attachments.entries())
if (entry.key.slotIndex == slotIndex) attachments.add(entry.value);
}
public void clear () {
attachments.clear();
}
public String getName () {
return name;
}
public String toString () {
return name;
}
static class Key {
int slotIndex;
String name;
int hashCode;
public void set (int slotName, String name) {
if (name == null) throw new IllegalArgumentException("attachmentName cannot be null.");
this.slotIndex = slotName;
this.name = name;
hashCode = 31 * (31 + name.hashCode()) + slotIndex;
}
public int hashCode () {
return hashCode;
}
public boolean equals (Object object) {
if (object == null) return false;
Key other = (Key)object;
if (slotIndex != other.slotIndex) return false;
if (!name.equals(other.name)) return false;
return true;
}
public String toString () {
return slotIndex + ":" + name;
}
}
/** Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. */
void attachAll (Skeleton skeleton, Skin oldSkin) {
for (Entry<Key, Attachment> entry : oldSkin.attachments.entries()) {
int slotIndex = entry.key.slotIndex;
Slot slot = skeleton.slots.get(slotIndex);
if (slot.attachment == entry.value) {
Attachment attachment = getAttachment(slotIndex, entry.key.name);
if (attachment != null) slot.setAttachment(attachment);
}
}
}
}

View File

@ -0,0 +1,93 @@
package com.esotericsoftware.spine;
import com.badlogic.gdx.graphics.Color;
public class Slot {
final SlotData data;
final Bone bone;
private final Skeleton skeleton;
final Color color;
Attachment attachment;
private float attachmentTime;
Slot () {
data = null;
bone = null;
skeleton = null;
color = new Color(1, 1, 1, 1);
}
public Slot (SlotData data, Skeleton skeleton, Bone bone) {
if (data == null) throw new IllegalArgumentException("data cannot be null.");
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
if (bone == null) throw new IllegalArgumentException("bone cannot be null.");
this.data = data;
this.skeleton = skeleton;
this.bone = bone;
color = new Color(1, 1, 1, 1);
}
/** Copy constructor. */
public Slot (Slot slot, Skeleton skeleton, Bone bone) {
if (slot == null) throw new IllegalArgumentException("slot cannot be null.");
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
if (bone == null) throw new IllegalArgumentException("bone cannot be null.");
data = slot.data;
this.skeleton = skeleton;
this.bone = bone;
color = new Color(slot.color);
attachment = slot.attachment;
attachmentTime = slot.attachmentTime;
}
public SlotData getData () {
return data;
}
public Skeleton getSkeleton () {
return skeleton;
}
public Bone getBone () {
return bone;
}
public Color getColor () {
return color;
}
/** @return May be null. */
public Attachment getAttachment () {
return attachment;
}
/** Sets the attachment and resets {@link #getAttachmentTime()}.
* @param attachment May be null. */
public void setAttachment (Attachment attachment) {
this.attachment = attachment;
attachmentTime = skeleton.time;
}
public void setAttachmentTime (float time) {
attachmentTime = skeleton.time - time;
}
/** Returns the time since the attachment was set. */
public float getAttachmentTime () {
return skeleton.time - attachmentTime;
}
void setToBindPose (int slotIndex) {
color.set(data.color);
setAttachment(data.attachmentName == null ? null : skeleton.getAttachment(slotIndex, data.attachmentName));
}
public void setToBindPose () {
setToBindPose(skeleton.slots.indexOf(this, true));
}
public String toString () {
return data.name;
}
}

View File

@ -0,0 +1,49 @@
package com.esotericsoftware.spine;
import com.badlogic.gdx.graphics.Color;
public class SlotData {
final String name;
final BoneData boneData;
final Color color = new Color(1, 1, 1, 1);
String attachmentName;
SlotData () {
name = null;
boneData = null;
}
public SlotData (String name, BoneData boneData) {
if (name == null) throw new IllegalArgumentException("name cannot be null.");
if (boneData == null) throw new IllegalArgumentException("boneData cannot be null.");
this.name = name;
this.boneData = boneData;
}
public String getName () {
return name;
}
public BoneData getBoneData () {
return boneData;
}
public Color getColor () {
return color;
}
/** @param attachmentName May be null. */
public void setAttachmentName (String attachmentName) {
this.attachmentName = attachmentName;
}
/** @return May be null. */
public String getAttachmentName () {
return attachmentName;
}
public String toString () {
return name;
}
}

View File

@ -0,0 +1,153 @@
package com.esotericsoftware.spine.attachments;
import com.esotericsoftware.spine.Attachment;
import com.esotericsoftware.spine.Bone;
import com.esotericsoftware.spine.Slot;
import static com.badlogic.gdx.graphics.g2d.SpriteBatch.*;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.NumberUtils;
/** Attachment that displays a texture region. */
public class RegionAttachment extends Attachment {
private TextureRegion region;
private final float[] vertices = new float[20];
private final float[] offset = new float[8];
public RegionAttachment (String name) {
super(name);
}
public void updateOffset () {
float width = getWidth();
float height = getHeight();
float localX2 = width / 2;
float localY2 = height / 2;
float localX = -localX2;
float localY = -localY2;
if (region instanceof AtlasRegion) {
AtlasRegion region = (AtlasRegion)this.region;
if (region.rotate) {
localX += region.offsetX / region.originalWidth * height;
localY += region.offsetY / region.originalHeight * width;
localX2 -= (region.originalWidth - region.offsetX - region.packedHeight) / region.originalWidth * width;
localY2 -= (region.originalHeight - region.offsetY - region.packedWidth) / region.originalHeight * height;
} else {
localX += region.offsetX / region.originalWidth * width;
localY += region.offsetY / region.originalHeight * height;
localX2 -= (region.originalWidth - region.offsetX - region.packedWidth) / region.originalWidth * width;
localY2 -= (region.originalHeight - region.offsetY - region.packedHeight) / region.originalHeight * height;
}
}
float scaleX = getScaleX();
float scaleY = getScaleY();
localX *= scaleX;
localY *= scaleY;
localX2 *= scaleX;
localY2 *= scaleY;
float rotation = getRotation();
float cos = MathUtils.cosDeg(rotation);
float sin = MathUtils.sinDeg(rotation);
float x = getX();
float y = getY();
float localXCos = localX * cos + x;
float localXSin = localX * sin;
float localYCos = localY * cos + y;
float localYSin = localY * sin;
float localX2Cos = localX2 * cos + x;
float localX2Sin = localX2 * sin;
float localY2Cos = localY2 * cos + y;
float localY2Sin = localY2 * sin;
float[] offset = this.offset;
offset[0] = localXCos - localYSin;
offset[1] = localYCos + localXSin;
offset[2] = localXCos - localY2Sin;
offset[3] = localY2Cos + localXSin;
offset[4] = localX2Cos - localY2Sin;
offset[5] = localY2Cos + localX2Sin;
offset[6] = localX2Cos - localYSin;
offset[7] = localYCos + localX2Sin;
}
public void setRegion (TextureRegion region) {
if (region == null) throw new IllegalArgumentException("region cannot be null.");
TextureRegion oldRegion = this.region;
this.region = region;
float[] vertices = this.vertices;
if (region instanceof AtlasRegion && ((AtlasRegion)region).rotate) {
vertices[U2] = region.getU();
vertices[V2] = region.getV2();
vertices[U3] = region.getU();
vertices[V3] = region.getV();
vertices[U4] = region.getU2();
vertices[V4] = region.getV();
vertices[U1] = region.getU2();
vertices[V1] = region.getV2();
} else {
vertices[U1] = region.getU();
vertices[V1] = region.getV2();
vertices[U2] = region.getU();
vertices[V2] = region.getV();
vertices[U3] = region.getU2();
vertices[V3] = region.getV();
vertices[U4] = region.getU2();
vertices[V4] = region.getV2();
}
updateOffset();
}
public TextureRegion getRegion () {
if (region == null) throw new IllegalStateException("RegionAttachment is not resolved: " + this);
return region;
}
public void draw (SpriteBatch batch, Slot slot) {
if (region == null) throw new IllegalStateException("RegionAttachment is not resolved: " + this);
Color skeletonColor = slot.getSkeleton().getColor();
Color slotColor = slot.getColor();
float color = NumberUtils.intToFloatColor( //
((int)(255 * skeletonColor.a * slotColor.a) << 24) //
| ((int)(255 * skeletonColor.b * slotColor.b) << 16) //
| ((int)(255 * skeletonColor.g * slotColor.g) << 8) //
| ((int)(255 * skeletonColor.r * slotColor.r)));
float[] vertices = this.vertices;
vertices[C1] = color;
vertices[C2] = color;
vertices[C3] = color;
vertices[C4] = color;
updateWorldVertices(slot.getBone());
batch.draw(region.getTexture(), vertices, 0, vertices.length);
}
public void updateWorldVertices (Bone bone) {
float[] vertices = this.vertices;
float[] offset = this.offset;
float x = bone.getWorldX();
float y = bone.getWorldY();
float m00 = bone.getM00();
float m01 = bone.getM01();
float m10 = bone.getM10();
float m11 = bone.getM11();
vertices[X1] = offset[0] * m00 + offset[1] * m01 + x;
vertices[Y1] = offset[0] * m10 + offset[1] * m11 + y;
vertices[X2] = offset[2] * m00 + offset[3] * m01 + x;
vertices[Y2] = offset[2] * m10 + offset[3] * m11 + y;
vertices[X3] = offset[4] * m00 + offset[5] * m01 + x;
vertices[Y3] = offset[4] * m10 + offset[5] * m11 + y;
vertices[X4] = offset[6] * m00 + offset[7] * m01 + x;
vertices[Y4] = offset[6] * m10 + offset[7] * m11 + y;
}
public float[] getWorldVertices () {
return vertices;
}
}

View File

@ -0,0 +1,68 @@
package com.esotericsoftware.spine.attachments;
import com.esotericsoftware.spine.Slot;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.MathUtils;
/** Attachment that displays various texture regions over time. */
public class RegionSequenceAttachment extends RegionAttachment {
private final Mode mode;
private float frameTime;
private TextureRegion[] regions;
/** @param frameTime Time in seconds each frame is shown. */
public RegionSequenceAttachment (String name, float frameTime, Mode mode) {
super(name);
if (mode == null) throw new IllegalArgumentException("mode cannot be null.");
this.frameTime = frameTime;
this.mode = mode;
}
public void draw (SpriteBatch batch, Slot slot) {
if (regions == null) throw new IllegalStateException("RegionSequenceAttachment is not resolved: " + this);
int frameIndex = (int)(slot.getAttachmentTime() / frameTime);
switch (mode) {
case forward:
frameIndex = Math.min(regions.length - 1, frameIndex);
break;
case forwardLoop:
frameIndex = frameIndex % regions.length;
break;
case pingPong:
frameIndex = frameIndex % (regions.length * 2);
if (frameIndex >= regions.length) frameIndex = regions.length - 1 - (frameIndex - regions.length);
break;
case random:
frameIndex = MathUtils.random(regions.length - 1);
break;
case backward:
frameIndex = Math.max(regions.length - frameIndex - 1, 0);
break;
case backwardLoop:
frameIndex = frameIndex % regions.length;
frameIndex = regions.length - frameIndex - 1;
break;
}
setRegion(regions[frameIndex]);
super.draw(batch, slot);
}
/** May be null if the attachment is not resolved. */
public TextureRegion[] getRegions () {
if (regions == null) throw new IllegalStateException("RegionSequenceAttachment is not resolved: " + this);
return regions;
}
public void setRegions (TextureRegion[] regions) {
this.regions = regions;
}
static public enum Mode {
forward, backward, forwardLoop, backwardLoop, pingPong, random
}
}

View File

@ -0,0 +1,29 @@
package com.esotericsoftware.spine.attachments;
import com.esotericsoftware.spine.Attachment;
import com.esotericsoftware.spine.AttachmentResolver;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
public class TextureAtlasAttachmentResolver implements AttachmentResolver {
private TextureAtlas atlas;
public TextureAtlasAttachmentResolver (TextureAtlas atlas) {
if (atlas == null) throw new IllegalArgumentException("atlas cannot be null.");
this.atlas = atlas;
}
public void resolve (Attachment attachment) {
if (attachment instanceof RegionAttachment) {
AtlasRegion region = atlas.findRegion(attachment.getName());
if (region == null) throw new RuntimeException("Region not found in atlas: " + attachment);
((RegionAttachment)attachment).setRegion(region);
attachment.setResolved(true);
return;
}
throw new IllegalArgumentException("Unable to resolve attachment of type: " + attachment.getClass().getName());
}
}

View File

@ -0,0 +1,124 @@
package com.esotericsoftware.spine;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.InputAdapter;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
public class MixTest extends ApplicationAdapter {
SpriteBatch batch;
float time;
ShapeRenderer renderer;
SkeletonData skeletonData;
Skeleton skeleton;
Animation walkAnimation;
Animation jumpAnimation;
public void create () {
batch = new SpriteBatch();
renderer = new ShapeRenderer();
final String name = "spineboy";
TextureAtlas atlas = new TextureAtlas(Gdx.files.internal(name + ".atlas"));
if (true) {
SkeletonJson json = new SkeletonJson(atlas);
// json.setScale(2);
skeletonData = json.readSkeletonData(Gdx.files.internal(name + "-skeleton.json"));
walkAnimation = json.readAnimation(Gdx.files.internal(name + "-walk.json"), skeletonData);
jumpAnimation = json.readAnimation(Gdx.files.internal(name + "-jump.json"), skeletonData);
} else {
SkeletonBinary binary = new SkeletonBinary(atlas);
// binary.setScale(2);
skeletonData = binary.readSkeletonData(Gdx.files.internal(name + ".skel"));
walkAnimation = binary.readAnimation(Gdx.files.internal(name + "-walk.anim"), skeletonData);
jumpAnimation = binary.readAnimation(Gdx.files.internal(name + "-jump.anim"), skeletonData);
}
skeleton = new Skeleton(skeletonData);
skeleton.setToBindPose();
final Bone root = skeleton.getRootBone();
root.x = -50;
root.y = 20;
root.scaleX = 1f;
root.scaleY = 1f;
skeleton.updateWorldTransform();
}
public void render () {
float delta = Gdx.graphics.getDeltaTime() * 0.25f; // Reduced to make mixing easier to see.
float jump = jumpAnimation.getDuration();
float beforeJump = 1f;
float blendIn = 0.4f;
float blendOut = 0.4f;
float blendOutStart = beforeJump + jump - blendOut;
float total = 3.75f;
time += delta;
Bone root = skeleton.getRootBone();
float speed = 180;
if (time > beforeJump + blendIn && time < blendOutStart) speed = 360;
root.setX(root.getX() + speed * delta);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
batch.begin();
batch.setColor(Color.GRAY);
if (time > total) {
// restart
time = 0;
root.setX(-50);
} else if (time > beforeJump + jump) {
// just walk after jump
walkAnimation.apply(skeleton, time, true);
} else if (time > blendOutStart) {
// blend out jump
walkAnimation.apply(skeleton, time, true);
jumpAnimation.mix(skeleton, time - beforeJump, false, 1 - (time - blendOutStart) / blendOut);
} else if (time > beforeJump + blendIn) {
// just jump
jumpAnimation.apply(skeleton, time - beforeJump, false);
} else if (time > beforeJump) {
// blend in jump
walkAnimation.apply(skeleton, time, true);
jumpAnimation.mix(skeleton, time - beforeJump, false, (time - beforeJump) / blendIn);
} else {
// just walk before jump
walkAnimation.apply(skeleton, time, true);
}
skeleton.updateWorldTransform();
skeleton.update(Gdx.graphics.getDeltaTime());
skeleton.draw(batch);
batch.end();
// skeleton.drawDebug(renderer);
}
public void resize (int width, int height) {
batch.getProjectionMatrix().setToOrtho2D(0, 0, width, height);
renderer.setProjectionMatrix(batch.getProjectionMatrix());
}
public static void main (String[] args) throws Exception {
LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
config.title = "Mix Test";
config.width = 640;
config.height = 480;
new LwjglApplication(new MixTest(), config);
}
}

View File

@ -0,0 +1,123 @@
package com.esotericsoftware.spine;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.InputAdapter;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
public class SkeletonTest extends ApplicationAdapter {
SpriteBatch batch;
float time;
ShapeRenderer renderer;
SkeletonData skeletonData;
Skeleton skeleton;
Animation animation;
public void create () {
batch = new SpriteBatch();
renderer = new ShapeRenderer();
final String name = "goblins";
// A regular texture atlas would normally usually be used. This returns a white image for images not found in the atlas.
Pixmap pixmap = new Pixmap(32, 32, Format.RGBA8888);
pixmap.setColor(Color.WHITE);
pixmap.fill();
final AtlasRegion fake = new AtlasRegion(new Texture(pixmap), 0, 0, 32, 32);
pixmap.dispose();
FileHandle atlasFile = Gdx.files.internal(name + ".atlas");
TextureAtlasData data = !atlasFile.exists() ? null : new TextureAtlasData(atlasFile, atlasFile.parent(), false);
TextureAtlas atlas = new TextureAtlas(data) {
public AtlasRegion findRegion (String name) {
AtlasRegion region = super.findRegion(name);
return region != null ? region : fake;
}
};
if (true) {
SkeletonJson json = new SkeletonJson(atlas);
// json.setScale(2);
skeletonData = json.readSkeletonData(Gdx.files.internal(name + "-skeleton.json"));
animation = json.readAnimation(Gdx.files.internal(name + "-walk.json"), skeletonData);
} else {
SkeletonBinary binary = new SkeletonBinary(atlas);
// binary.setScale(2);
skeletonData = binary.readSkeletonData(Gdx.files.internal(name + ".skel"));
animation = binary.readAnimation(Gdx.files.internal(name + "-walk.anim"), skeletonData);
}
skeleton = new Skeleton(skeletonData);
if (name.equals("goblins")) skeleton.setSkin("goblin");
skeleton.setToBindPose();
Bone root = skeleton.getRootBone();
root.x = 50;
root.y = 20;
root.scaleX = 1f;
root.scaleY = 1f;
skeleton.updateWorldTransform();
Gdx.input.setInputProcessor(new InputAdapter() {
public boolean keyDown (int keycode) {
if (keycode == Keys.SPACE) {
if (name.equals("goblins")) {
skeleton.setSkin(skeleton.getSkin().getName().equals("goblin") ? "goblingirl" : "goblin");
skeleton.setSlotsToBindPose();
}
}
return true;
}
});
}
public void render () {
time += Gdx.graphics.getDeltaTime();
Bone root = skeleton.getRootBone();
float x = root.getX() + 160 * Gdx.graphics.getDeltaTime() * (skeleton.getFlipX() ? -1 : 1);
if (x > Gdx.graphics.getWidth()) skeleton.setFlipX(true);
if (x < 0) skeleton.setFlipX(false);
root.setX(x);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
batch.begin();
batch.setColor(Color.GRAY);
animation.apply(skeleton, time, true);
skeleton.updateWorldTransform();
skeleton.update(Gdx.graphics.getDeltaTime());
skeleton.draw(batch);
batch.end();
skeleton.drawDebug(renderer);
}
public void resize (int width, int height) {
batch.getProjectionMatrix().setToOrtho2D(0, 0, width, height);
renderer.setProjectionMatrix(batch.getProjectionMatrix());
}
public static void main (String[] args) throws Exception {
LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
config.title = "Skeleton Test";
config.width = 640;
config.height = 480;
new LwjglApplication(new SkeletonTest(), config);
}
}

View File

@ -0,0 +1,202 @@
{
"bones": [
{ "name": "root", "length": 0 },
{ "name": "hip", "parent": "root", "length": 0, "x": 0.64, "y": 114.41 },
{ "name": "left upper leg", "parent": "hip", "length": 50.39, "x": 14.45, "y": 2.81, "rotation": -89.09 },
{ "name": "left lower leg", "parent": "left upper leg", "length": 49.89, "x": 56.34, "y": 0.98, "rotation": -16.65 },
{ "name": "left foot", "parent": "left lower leg", "length": 46.5, "x": 58.94, "y": -7.61, "rotation": 102.43 },
{ "name": "right upper leg", "parent": "hip", "length": 42.45, "x": -20.07, "y": -6.83, "rotation": -97.49 },
{ "name": "right lower leg", "parent": "right upper leg", "length": 58.52, "x": 42.99, "y": -0.61, "rotation": -14.34 },
{ "name": "right foot", "parent": "right lower leg", "length": 45.45, "x": 64.88, "y": 0.04, "rotation": 110.3 },
{ "name": "torso", "parent": "hip", "length": 85.82, "x": -6.42, "y": 1.97, "rotation": 93.92 },
{ "name": "neck", "parent": "torso", "length": 18.38, "x": 81.67, "y": -6.34, "rotation": -1.51 },
{ "name": "head", "parent": "neck", "length": 68.28, "x": 20.93, "y": 11.59, "rotation": -13.92 },
{ "name": "right shoulder", "parent": "torso", "length": 37.24, "x": 76.02, "y": 18.14, "rotation": 133.88 },
{ "name": "right arm", "parent": "right shoulder", "length": 36.74, "x": 37.6, "y": 0.31, "rotation": 36.32 },
{ "name": "right hand", "parent": "right arm", "length": 15.32, "x": 36.9, "y": 0.34, "rotation": 2.35 },
{ "name": "left shoulder", "parent": "torso", "length": 35.43, "x": 74.04, "y": -20.38, "rotation": -156.96 },
{ "name": "left arm", "parent": "left shoulder", "length": 35.62, "x": 37.85, "y": -2.34, "rotation": 28.16 },
{ "name": "left hand", "parent": "left arm", "length": 11.52, "x": 35.62, "y": 0.07, "rotation": 2.7 },
{ "name": "pelvis", "parent": "hip", "length": 0, "x": 1.41, "y": -6.57 }
],
"slots": [
{ "name": "left shoulder", "bone": "left shoulder", "attachment": "left shoulder" },
{ "name": "left arm", "bone": "left arm", "attachment": "left arm" },
{ "name": "left hand item", "bone": "left hand", "attachment": "left hand item" },
{ "name": "left hand", "bone": "left hand", "attachment": "left hand" },
{ "name": "left foot", "bone": "left foot", "attachment": "left foot" },
{ "name": "left lower leg", "bone": "left lower leg", "attachment": "left lower leg" },
{ "name": "left upper leg", "bone": "left upper leg", "attachment": "left upper leg" },
{ "name": "neck", "bone": "neck", "attachment": "neck" },
{ "name": "torso", "bone": "torso", "attachment": "torso" },
{ "name": "pelvis", "bone": "pelvis", "attachment": "pelvis" },
{ "name": "right foot", "bone": "right foot", "attachment": "right foot" },
{ "name": "right lower leg", "bone": "right lower leg", "attachment": "right lower leg" },
{ "name": "undie straps", "bone": "pelvis", "attachment": "undie straps" },
{ "name": "undies", "bone": "pelvis", "attachment": "undies" },
{ "name": "right upper leg", "bone": "right upper leg", "attachment": "right upper leg" },
{ "name": "head", "bone": "head", "attachment": "head" },
{ "name": "eyes", "bone": "head", "attachment": "eyes open" },
{ "name": "right shoulder", "bone": "right shoulder", "attachment": "right shoulder" },
{ "name": "right arm", "bone": "right arm", "attachment": "right arm" },
{ "name": "right hand item 2", "bone": "right hand", "attachment": "right hand item 2" },
{ "name": "right hand", "bone": "right hand", "attachment": "right hand" },
{ "name": "right hand item", "bone": "right hand", "attachment": "right hand item" }
],
"skins": {
"goblin": {
"neck": {
"neck": { "name": "goblin/neck", "x": 10.1, "y": 0.42, "rotation": -93.69, "width": 36, "height": 41 }
},
"undies": {
"undies": { "name": "goblin/undies", "x": 6.3, "y": 0.12, "rotation": 0.91, "width": 36, "height": 29 }
},
"right hand": {
"right hand": { "name": "goblin/right-hand", "x": 7.88, "y": 2.78, "rotation": 91.96, "width": 36, "height": 37 }
},
"right arm": {
"right arm": { "name": "goblin/right-arm", "x": 16.44, "y": -1.04, "rotation": 94.32, "width": 23, "height": 50 }
},
"head": {
"head": { "name": "goblin/head", "x": 25.73, "y": 2.33, "rotation": -92.29, "width": 103, "height": 66 }
},
"left shoulder": {
"left shoulder": { "name": "goblin/left-shoulder", "x": 15.56, "y": -2.26, "rotation": 62.01, "width": 29, "height": 44 }
},
"left arm": {
"left arm": {
"name": "goblin/left-arm",
"x": 16.7,
"y": -1.69,
"scaleX": 1.057,
"scaleY": 1.057,
"rotation": 33.84,
"width": 37,
"height": 35
}
},
"left hand": {
"left hand": {
"name": "goblin/left-hand",
"x": 3.47,
"y": 3.41,
"scaleX": 0.892,
"scaleY": 0.892,
"rotation": 31.14,
"width": 36,
"height": 41
}
},
"right lower leg": {
"right lower leg": { "name": "goblin/right-lower-leg", "x": 25.68, "y": -3.15, "rotation": 111.83, "width": 36, "height": 76 }
},
"right upper leg": {
"right upper leg": { "name": "goblin/right-upper-leg", "x": 20.35, "y": 1.47, "rotation": 97.49, "width": 34, "height": 63 }
},
"pelvis": {
"pelvis": { "name": "goblin/pelvis", "x": -5.61, "y": 0.76, "width": 62, "height": 43 }
},
"left lower leg": {
"left lower leg": { "name": "goblin/left-lower-leg", "x": 23.58, "y": -2.06, "rotation": 105.75, "width": 33, "height": 70 }
},
"left upper leg": {
"left upper leg": { "name": "goblin/left-upper-leg", "x": 29.68, "y": -3.87, "rotation": 89.09, "width": 33, "height": 73 }
},
"torso": {
"torso": { "name": "goblin/torso", "x": 38.09, "y": -3.87, "rotation": -94.95, "width": 68, "height": 96 }
},
"right shoulder": {
"right shoulder": { "name": "goblin/right-shoulder", "x": 15.68, "y": -1.03, "rotation": 130.65, "width": 39, "height": 45 }
},
"right foot": {
"right foot": { "name": "goblin/right-foot", "x": 23.56, "y": 9.8, "rotation": 1.52, "width": 63, "height": 33 }
},
"left foot": {
"left foot": { "name": "goblin/left-foot", "x": 24.85, "y": 8.74, "rotation": 3.32, "width": 65, "height": 31 }
},
"right hand item": {
"right hand item": { "name": "goblin/shield", "x": -0.47, "y": 1.1, "rotation": 91.16, "width": 70, "height": 72 }
},
"left hand item": {
"left hand item": { "name": "goblin/spear", "x": -4.55, "y": 39.2, "rotation": 13.04, "width": 22, "height": 368 }
},
"undie straps": {
"undie straps": { "name": "goblin/undie-straps", "x": -3.87, "y": 13.1, "scaleX": 1.089, "width": 55, "height": 19 }
}
},
"goblingirl": {
"left upper leg": {
"left upper leg": { "name": "goblingirl/left-upper-leg", "x": 30.21, "y": -2.95, "rotation": 89.09, "width": 33, "height": 70 }
},
"left lower leg": {
"left lower leg": { "name": "goblingirl/left-lower-leg", "x": 25.02, "y": -0.6, "rotation": 105.75, "width": 33, "height": 70 }
},
"left foot": {
"left foot": { "name": "goblingirl/left-foot", "x": 25.17, "y": 7.92, "rotation": 3.32, "width": 65, "height": 31 }
},
"right upper leg": {
"right upper leg": { "name": "goblingirl/right-upper-leg", "x": 19.69, "y": 2.13, "rotation": 97.49, "width": 34, "height": 63 }
},
"right lower leg": {
"right lower leg": { "name": "goblingirl/right-lower-leg", "x": 26.15, "y": -3.27, "rotation": 111.83, "width": 36, "height": 76 }
},
"right foot": {
"right foot": { "name": "goblingirl/right-foot", "x": 23.46, "y": 9.66, "rotation": 1.52, "width": 63, "height": 33 }
},
"torso": {
"torso": { "name": "goblingirl/torso", "x": 36.28, "y": -5.14, "rotation": -95.74, "width": 68, "height": 96 }
},
"left shoulder": {
"left shoulder": { "name": "goblingirl/left-shoulder", "x": 19.8, "y": -0.42, "rotation": 61.21, "width": 28, "height": 46 }
},
"left arm": {
"left arm": { "name": "goblingirl/left-arm", "x": 19.64, "y": -2.42, "rotation": 33.05, "width": 37, "height": 35 }
},
"left hand": {
"left hand": {
"name": "goblingirl/left-hand",
"x": 4.34,
"y": 2.39,
"scaleX": 0.896,
"scaleY": 0.896,
"rotation": 30.34,
"width": 35,
"height": 40
}
},
"neck": {
"neck": { "name": "goblingirl/neck", "x": 6.16, "y": -3.14, "rotation": -98.86, "width": 35, "height": 41 }
},
"head": {
"head": { "name": "goblingirl/head", "x": 27.71, "y": -4.32, "rotation": -85.58, "width": 103, "height": 81 }
},
"right shoulder": {
"right shoulder": { "name": "goblingirl/right-shoulder", "x": 14.46, "y": 0.45, "rotation": 129.85, "width": 39, "height": 45 }
},
"right arm": {
"right arm": { "name": "goblingirl/right-arm", "x": 16.85, "y": -0.66, "rotation": 93.52, "width": 28, "height": 50 }
},
"right hand": {
"right hand": { "name": "goblingirl/right-hand", "x": 7.21, "y": 3.43, "rotation": 91.16, "width": 36, "height": 37 }
},
"pelvis": {
"pelvis": { "name": "goblingirl/pelvis", "x": -3.87, "y": 3.18, "width": 62, "height": 43 }
},
"undie straps": {
"undie straps": { "name": "goblingirl/undie-straps", "x": -1.51, "y": 14.18, "width": 55, "height": 19 }
},
"undies": {
"undies": { "name": "goblingirl/undies", "x": 5.4, "y": 1.7, "width": 36, "height": 29 }
},
"left hand item": {
"left hand item": { "name": "goblingirl/dagger", "x": 7.88, "y": -23.45, "rotation": 10.47, "width": 26, "height": 108 }
},
"right hand item 2": {
"right hand item 2": { "name": "goblingirl/dagger", "x": 7.17, "y": -22.38, "rotation": -5.27, "width": 26, "height": 108 }
},
"right hand item": {
"right hand item": { "name": "goblingirl/dagger-tip", "x": 13.45, "y": 22.07, "rotation": -3.23, "width": 17, "height": 17 }
}
}
}
}

Binary file not shown.

View File

@ -0,0 +1,288 @@
{
"bones": {
"left upper leg": {
"rotate": [
{ "time": 0, "angle": -26.55 },
{ "time": 0.1333, "angle": -8.78 },
{ "time": 0.2666, "angle": 9.51 },
{ "time": 0.4, "angle": 30.74 },
{ "time": 0.5333, "angle": 25.33 },
{ "time": 0.6666, "angle": 26.11 },
{ "time": 0.8, "angle": -7.7 },
{ "time": 0.9333, "angle": -21.19 },
{ "time": 1.0666, "angle": -26.55 }
],
"translate": [
{ "time": 0, "x": -1.32, "y": 1.7 },
{ "time": 0.4, "x": -0.06, "y": 2.42 },
{ "time": 1.0666, "x": -1.32, "y": 1.7 }
]
},
"right upper leg": {
"rotate": [
{ "time": 0, "angle": 42.45 },
{ "time": 0.1333, "angle": 52.1 },
{ "time": 0.2666, "angle": 8.53 },
{ "time": 0.5333, "angle": -16.93 },
{ "time": 0.6666, "angle": 1.89 },
{
"time": 0.8,
"angle": 28.06,
"curve": [ 0.462, 0.11, 1, 1 ]
},
{
"time": 0.9333,
"angle": 58.68,
"curve": [ 0.5, 0.02, 1, 1 ]
},
{ "time": 1.0666, "angle": 42.45 }
],
"translate": [
{ "time": 0, "x": 6.23, "y": 0 },
{ "time": 0.2666, "x": 2.14, "y": 2.4 },
{ "time": 0.5333, "x": 2.44, "y": 4.8 },
{ "time": 1.0666, "x": 6.23, "y": 0 }
]
},
"left lower leg": {
"rotate": [
{ "time": 0, "angle": -22.98 },
{ "time": 0.1333, "angle": -63.5 },
{ "time": 0.2666, "angle": -73.76 },
{ "time": 0.5333, "angle": 5.11 },
{ "time": 0.6666, "angle": -28.29 },
{ "time": 0.8, "angle": 4.08 },
{ "time": 0.9333, "angle": 3.53 },
{ "time": 1.0666, "angle": -22.98 }
],
"translate": [
{ "time": 0, "x": 0, "y": 0 },
{ "time": 0.2666, "x": 2.55, "y": -0.47 },
{ "time": 0.5333, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 1.0666, "x": 0, "y": 0 }
]
},
"left foot": {
"rotate": [
{ "time": 0, "angle": -3.69 },
{ "time": 0.1333, "angle": -10.42 },
{ "time": 0.2666, "angle": -5.01 },
{ "time": 0.4, "angle": 3.87 },
{ "time": 0.5333, "angle": -3.87 },
{ "time": 0.6666, "angle": 2.78 },
{ "time": 0.8, "angle": 1.68 },
{ "time": 0.9333, "angle": -8.54 },
{ "time": 1.0666, "angle": -3.69 }
]
},
"right shoulder": {
"rotate": [
{
"time": 0,
"angle": 5.29,
"curve": [ 0.264, 0, 0.75, 1 ]
},
{ "time": 0.6666, "angle": 6.65 },
{ "time": 1.0666, "angle": 5.29 }
]
},
"right arm": {
"rotate": [
{
"time": 0,
"angle": -4.02,
"curve": [ 0.267, 0, 0.804, 0.99 ]
},
{
"time": 0.6666,
"angle": 19.78,
"curve": [ 0.307, 0, 0.787, 0.99 ]
},
{ "time": 1.0666, "angle": -4.02 }
]
},
"right hand": {
"rotate": [
{ "time": 0, "angle": 8.98 },
{ "time": 0.6666, "angle": 0.51 },
{ "time": 1.0666, "angle": 8.98 }
]
},
"left shoulder": {
"rotate": [
{
"time": 0,
"angle": 6.25,
"curve": [ 0.339, 0, 0.683, 1 ]
},
{
"time": 0.5333,
"angle": -11.78,
"curve": [ 0.281, 0, 0.686, 0.99 ]
},
{ "time": 1.0666, "angle": 6.25 }
],
"translate": [
{ "time": 0, "x": 1.15, "y": 0.23 }
]
},
"left hand": {
"rotate": [
{
"time": 0,
"angle": -21.23,
"curve": [ 0.295, 0, 0.755, 0.98 ]
},
{
"time": 0.5333,
"angle": -27.28,
"curve": [ 0.241, 0, 0.75, 0.97 ]
},
{ "time": 1.0666, "angle": -21.23 }
]
},
"left arm": {
"rotate": [
{
"time": 0,
"angle": 28.37,
"curve": [ 0.339, 0, 0.683, 1 ]
},
{
"time": 0.5333,
"angle": 60.09,
"curve": [ 0.281, 0, 0.686, 0.99 ]
},
{ "time": 1.0666, "angle": 28.37 }
]
},
"torso": {
"rotate": [
{ "time": 0, "angle": -10.28 },
{
"time": 0.1333,
"angle": -15.38,
"curve": [ 0.545, 0, 0.818, 1 ]
},
{
"time": 0.4,
"angle": -9.78,
"curve": [ 0.58, 0.17, 0.669, 0.99 ]
},
{
"time": 0.6666,
"angle": -15.75,
"curve": [ 0.235, 0.01, 0.795, 1 ]
},
{
"time": 0.9333,
"angle": -7.06,
"curve": [ 0.209, 0, 0.816, 0.98 ]
},
{ "time": 1.0666, "angle": -10.28 }
],
"translate": [
{ "time": 0, "x": -1.29, "y": 1.68 }
]
},
"right foot": {
"rotate": [
{ "time": 0, "angle": -5.25 },
{ "time": 0.2666, "angle": -1.91 },
{ "time": 0.4, "angle": -6.45 },
{ "time": 0.5333, "angle": -5.39 },
{ "time": 0.8, "angle": -11.68 },
{ "time": 0.9333, "angle": 0.46 },
{ "time": 1.0666, "angle": -5.25 }
]
},
"right lower leg": {
"rotate": [
{
"time": 0,
"angle": -3.39,
"curve": [ 0.316, 0.01, 0.741, 0.98 ]
},
{
"time": 0.1333,
"angle": -45.53,
"curve": [ 0.229, 0, 0.738, 0.97 ]
},
{ "time": 0.2666, "angle": -4.83 },
{ "time": 0.5333, "angle": -19.53 },
{ "time": 0.6666, "angle": -64.8 },
{
"time": 0.8,
"angle": -82.56,
"curve": [ 0.557, 0.18, 1, 1 ]
},
{ "time": 1.0666, "angle": -3.39 }
],
"translate": [
{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 0.5333, "x": 0, "y": 0 },
{ "time": 0.6666, "x": 2.18, "y": 0.21 },
{ "time": 1.0666, "x": 0, "y": 0 }
]
},
"hip": {
"rotate": [
{ "time": 0, "angle": 0, "curve": "stepped" },
{ "time": 1.0666, "angle": 0 }
],
"translate": [
{ "time": 0, "x": 0, "y": -4.16 },
{
"time": 0.1333,
"x": 0,
"y": -7.05,
"curve": [ 0.359, 0.47, 0.646, 0.74 ]
},
{ "time": 0.4, "x": 0, "y": 6.78 },
{ "time": 0.5333, "x": 0, "y": -6.13 },
{
"time": 0.6666,
"x": 0,
"y": -7.05,
"curve": [ 0.359, 0.47, 0.646, 0.74 ]
},
{ "time": 0.9333, "x": 0, "y": 6.78 },
{ "time": 1.0666, "x": 0, "y": -4.16 }
]
},
"neck": {
"rotate": [
{ "time": 0, "angle": 3.6 },
{ "time": 0.1333, "angle": 17.49 },
{ "time": 0.2666, "angle": 6.1 },
{ "time": 0.4, "angle": 3.45 },
{ "time": 0.5333, "angle": 5.17 },
{ "time": 0.6666, "angle": 18.36 },
{ "time": 0.8, "angle": 6.09 },
{ "time": 0.9333, "angle": 2.28 },
{ "time": 1.0666, "angle": 3.6 }
]
},
"head": {
"rotate": [
{
"time": 0,
"angle": 3.6,
"curve": [ 0, 0, 0.704, 1.17 ]
},
{ "time": 0.1333, "angle": -0.2 },
{ "time": 0.2666, "angle": 6.1 },
{ "time": 0.4, "angle": 3.45 },
{
"time": 0.5333,
"angle": 5.17,
"curve": [ 0, 0, 0.704, 1.61 ]
},
{ "time": 0.7, "angle": 1.1 },
{ "time": 0.8, "angle": 6.09 },
{ "time": 0.9333, "angle": 2.28 },
{ "time": 1.0666, "angle": 3.6 }
]
}
}
}

View File

@ -0,0 +1,297 @@
goblins.png
format: RGBA8888
filter: Nearest,Nearest
repeat: none
goblin/spear
rotate: false
xy: 1, 123
size: 22, 368
orig: 22, 368
offset: 0, 0
index: -1
goblin/head
rotate: false
xy: 25, 425
size: 103, 66
orig: 103, 66
offset: 0, 0
index: -1
goblin/torso
rotate: false
xy: 25, 327
size: 68, 96
orig: 68, 96
offset: 0, 0
index: -1
goblin/shield
rotate: false
xy: 25, 253
size: 70, 72
orig: 70, 72
offset: 0, 0
index: -1
goblin/right-lower-leg
rotate: false
xy: 25, 175
size: 36, 76
orig: 36, 76
offset: 0, 0
index: -1
goblin/left-upper-leg
rotate: false
xy: 95, 350
size: 33, 73
orig: 33, 73
offset: 0, 0
index: -1
goblin/pelvis
rotate: false
xy: 130, 448
size: 62, 43
orig: 62, 43
offset: 0, 0
index: -1
goblin/left-lower-leg
rotate: false
xy: 130, 376
size: 33, 70
orig: 33, 70
offset: 0, 0
index: -1
goblin/right-upper-leg
rotate: false
xy: 63, 188
size: 34, 63
orig: 34, 63
offset: 0, 0
index: -1
goblin/right-shoulder
rotate: false
xy: 194, 446
size: 39, 45
orig: 39, 45
offset: 0, 0
index: -1
goblin/left-hand
rotate: false
xy: 25, 132
size: 36, 41
orig: 36, 41
offset: 0, 0
index: -1
goblin/neck
rotate: false
xy: 63, 145
size: 36, 41
orig: 36, 41
offset: 0, 0
index: -1
goblin/right-arm
rotate: false
xy: 165, 396
size: 23, 50
orig: 23, 50
offset: 0, 0
index: -1
goblin/left-foot
rotate: false
xy: 190, 413
size: 65, 31
orig: 65, 31
offset: 0, 0
index: -1
goblin/right-foot
rotate: false
xy: 190, 378
size: 63, 33
orig: 63, 33
offset: 0, 0
index: -1
goblin/left-shoulder
rotate: false
xy: 1, 77
size: 29, 44
orig: 29, 44
offset: 0, 0
index: -1
goblin/right-hand
rotate: false
xy: 1, 38
size: 36, 37
orig: 36, 37
offset: 0, 0
index: -1
goblin/left-arm
rotate: false
xy: 1, 1
size: 37, 35
orig: 37, 35
offset: 0, 0
index: -1
goblin/undies
rotate: false
xy: 130, 345
size: 36, 29
orig: 36, 29
offset: 0, 0
index: -1
goblin/undie-straps
rotate: false
xy: 168, 357
size: 55, 19
orig: 55, 19
offset: 0, 0
index: -1
goblins2.png
format: RGBA8888
filter: Nearest,Nearest
repeat: none
goblingirl/goblingirl
rotate: false
xy: 1, 217
size: 148, 294
orig: 148, 294
offset: 0, 0
index: -1
goblingirl/head
rotate: false
xy: 1, 134
size: 103, 81
orig: 103, 81
offset: 0, 0
index: -1
goblingirl/torso
rotate: false
xy: 151, 415
size: 68, 96
orig: 68, 96
offset: 0, 0
index: -1
goblingirl/right-upper-leg
rotate: false
xy: 221, 448
size: 34, 63
orig: 34, 63
offset: 0, 0
index: -1
goblingirl/dagger
rotate: false
xy: 1, 24
size: 26, 108
orig: 26, 108
offset: 0, 0
index: -1
goblingirl/right-lower-leg
rotate: false
xy: 106, 139
size: 36, 76
orig: 36, 76
offset: 0, 0
index: -1
goblingirl/pelvis
rotate: false
xy: 151, 370
size: 62, 43
orig: 62, 43
offset: 0, 0
index: -1
goblingirl/left-lower-leg
rotate: false
xy: 29, 62
size: 33, 70
orig: 33, 70
offset: 0, 0
index: -1
goblingirl/left-upper-leg
rotate: false
xy: 151, 298
size: 33, 70
orig: 33, 70
offset: 0, 0
index: -1
goblingirl/right-shoulder
rotate: false
xy: 64, 87
size: 39, 45
orig: 39, 45
offset: 0, 0
index: -1
goblingirl/right-arm
rotate: false
xy: 151, 246
size: 28, 50
orig: 28, 50
offset: 0, 0
index: -1
goblingirl/left-shoulder
rotate: false
xy: 186, 322
size: 28, 46
orig: 28, 46
offset: 0, 0
index: -1
goblingirl/left-hand
rotate: false
xy: 29, 20
size: 35, 40
orig: 35, 40
offset: 0, 0
index: -1
goblingirl/undie-straps
rotate: false
xy: 64, 66
size: 55, 19
orig: 55, 19
offset: 0, 0
index: -1
goblingirl/right-foot
rotate: false
xy: 66, 31
size: 63, 33
orig: 63, 33
offset: 0, 0
index: -1
goblingirl/left-arm
rotate: false
xy: 151, 209
size: 37, 35
orig: 37, 35
offset: 0, 0
index: -1
goblingirl/neck
rotate: false
xy: 144, 166
size: 35, 41
orig: 35, 41
offset: 0, 0
index: -1
goblingirl/left-foot
rotate: false
xy: 144, 133
size: 65, 31
orig: 65, 31
offset: 0, 0
index: -1
goblingirl/right-hand
rotate: false
xy: 186, 283
size: 36, 37
orig: 36, 37
offset: 0, 0
index: -1
goblingirl/undies
rotate: false
xy: 181, 252
size: 36, 29
orig: 36, 29
offset: 0, 0
index: -1
goblingirl/dagger-tip
rotate: false
xy: 1, 1
size: 17, 17
orig: 17, 17
offset: 0, 0
index: -1

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

View File

@ -0,0 +1,410 @@
{
"bones": {
"hip": {
"rotate": [
{ "time": 0, "angle": 0, "curve": "stepped" },
{ "time": 0.9333, "angle": 0, "curve": "stepped" },
{ "time": 1.3666, "angle": 0 }
],
"translate": [
{ "time": 0, "x": -11.57, "y": -3 },
{ "time": 0.2333, "x": -16.2, "y": -19.43 },
{
"time": 0.3333,
"x": 7.66,
"y": -8.48,
"curve": [ 0.057, 0.06, 0.712, 1 ]
},
{ "time": 0.3666, "x": 15.38, "y": 5.01 },
{ "time": 0.4666, "x": -7.84, "y": 57.22 },
{
"time": 0.6,
"x": -10.81,
"y": 96.34,
"curve": [ 0.241, 0, 1, 1 ]
},
{ "time": 0.7333, "x": -7.01, "y": 54.7 },
{ "time": 0.8, "x": -10.58, "y": 32.2 },
{ "time": 0.9333, "x": -31.99, "y": 0.45 },
{ "time": 1.0666, "x": -12.48, "y": -29.47 },
{ "time": 1.3666, "x": -11.57, "y": -3 }
],
"scale": [
{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 1.3666, "x": 1, "y": 1 }
]
},
"left upper leg": {
"rotate": [
{ "time": 0, "angle": 17.13 },
{ "time": 0.2333, "angle": 44.35 },
{ "time": 0.3333, "angle": 16.46 },
{ "time": 0.4, "angle": -9.88 },
{ "time": 0.4666, "angle": -11.42 },
{ "time": 0.5666, "angle": 23.46 },
{ "time": 0.7666, "angle": 71.82 },
{ "time": 0.9333, "angle": 65.53 },
{ "time": 1.0666, "angle": 51.01 },
{ "time": 1.3666, "angle": 17.13 }
],
"translate": [
{ "time": 0, "x": -3, "y": -2.25, "curve": "stepped" },
{ "time": 0.9333, "x": -3, "y": -2.25, "curve": "stepped" },
{ "time": 1.3666, "x": -3, "y": -2.25 }
],
"scale": [
{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 1.3666, "x": 1, "y": 1 }
]
},
"left lower leg": {
"rotate": [
{ "time": 0, "angle": -16.25 },
{ "time": 0.2333, "angle": -52.21 },
{ "time": 0.4, "angle": 15.04 },
{ "time": 0.4666, "angle": -8.95 },
{ "time": 0.5666, "angle": -39.53 },
{ "time": 0.7666, "angle": -27.27 },
{ "time": 0.9333, "angle": -3.52 },
{ "time": 1.0666, "angle": -61.92 },
{ "time": 1.3666, "angle": -16.25 }
],
"translate": [
{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 1.3666, "x": 0, "y": 0 }
],
"scale": [
{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 1.3666, "x": 1, "y": 1 }
]
},
"left foot": {
"rotate": [
{ "time": 0, "angle": 0.33 },
{ "time": 0.2333, "angle": 6.2 },
{ "time": 0.3333, "angle": 14.73 },
{ "time": 0.4, "angle": -15.54 },
{ "time": 0.4333, "angle": -21.2 },
{ "time": 0.5666, "angle": -7.55 },
{ "time": 0.7666, "angle": -0.67 },
{ "time": 0.9333, "angle": -0.58 },
{ "time": 1.0666, "angle": 14.64 },
{ "time": 1.3666, "angle": 0.33 }
],
"translate": [
{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 1.3666, "x": 0, "y": 0 }
],
"scale": [
{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 1.3666, "x": 1, "y": 1 }
]
},
"right upper leg": {
"rotate": [
{ "time": 0, "angle": 25.97 },
{ "time": 0.2333, "angle": 46.43 },
{ "time": 0.3333, "angle": 22.61 },
{ "time": 0.4, "angle": 2.13 },
{
"time": 0.4666,
"angle": 0.04,
"curve": [ 0, 0, 0.637, 0.98 ]
},
{ "time": 0.6, "angle": 65.55 },
{ "time": 0.7666, "angle": 64.93 },
{ "time": 0.9333, "angle": 41.08 },
{ "time": 1.0666, "angle": 66.25 },
{ "time": 1.3666, "angle": 25.97 }
],
"translate": [
{ "time": 0, "x": 5.74, "y": 0.61 },
{ "time": 0.2333, "x": 4.79, "y": 1.79 },
{ "time": 0.3333, "x": 6.05, "y": -4.55 },
{ "time": 0.9333, "x": 4.79, "y": 1.79, "curve": "stepped" },
{ "time": 1.0666, "x": 4.79, "y": 1.79 },
{ "time": 1.3666, "x": 5.74, "y": 0.61 }
],
"scale": [
{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 1.3666, "x": 1, "y": 1 }
]
},
"right lower leg": {
"rotate": [
{ "time": 0, "angle": -27.46 },
{ "time": 0.2333, "angle": -64.03 },
{ "time": 0.4, "angle": -48.36 },
{ "time": 0.5666, "angle": -76.86 },
{ "time": 0.7666, "angle": -26.89 },
{ "time": 0.9, "angle": -18.97 },
{ "time": 0.9333, "angle": -14.18 },
{ "time": 1.0666, "angle": -80.45 },
{ "time": 1.3666, "angle": -27.46 }
],
"translate": [
{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 1.3666, "x": 0, "y": 0 }
],
"scale": [
{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 1.3666, "x": 1, "y": 1 }
]
},
"right foot": {
"rotate": [
{ "time": 0, "angle": 1.08 },
{ "time": 0.2333, "angle": 16.02 },
{ "time": 0.3, "angle": 12.94 },
{ "time": 0.3333, "angle": 15.16 },
{ "time": 0.4, "angle": -14.7 },
{ "time": 0.4333, "angle": -12.85 },
{ "time": 0.4666, "angle": -19.18 },
{ "time": 0.5666, "angle": -15.82 },
{ "time": 0.6, "angle": -3.59 },
{ "time": 0.7666, "angle": -3.56 },
{ "time": 0.9333, "angle": 1.86 },
{ "time": 1.0666, "angle": 16.02 },
{ "time": 1.3666, "angle": 1.08 }
],
"translate": [
{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 1.3666, "x": 0, "y": 0 }
],
"scale": [
{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 1.3666, "x": 1, "y": 1 }
]
},
"torso": {
"rotate": [
{ "time": 0, "angle": -13.35 },
{ "time": 0.2333, "angle": -48.95 },
{ "time": 0.4333, "angle": -35.77 },
{ "time": 0.6, "angle": -4.59 },
{ "time": 0.7666, "angle": 14.61 },
{ "time": 0.9333, "angle": 15.74 },
{ "time": 1.0666, "angle": -32.44 },
{ "time": 1.3666, "angle": -13.35 }
],
"translate": [
{ "time": 0, "x": -3.67, "y": 1.68, "curve": "stepped" },
{ "time": 0.9333, "x": -3.67, "y": 1.68, "curve": "stepped" },
{ "time": 1.3666, "x": -3.67, "y": 1.68 }
],
"scale": [
{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 1.3666, "x": 1, "y": 1 }
]
},
"neck": {
"rotate": [
{ "time": 0, "angle": 12.78 },
{ "time": 0.2333, "angle": 16.46 },
{ "time": 0.4, "angle": 26.49 },
{ "time": 0.6, "angle": 15.51 },
{ "time": 0.7666, "angle": 1.34 },
{ "time": 0.9333, "angle": 2.35 },
{ "time": 1.0666, "angle": 6.08 },
{ "time": 1.3, "angle": 21.23 },
{ "time": 1.3666, "angle": 12.78 }
],
"translate": [
{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 1.3666, "x": 0, "y": 0 }
],
"scale": [
{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 1.3666, "x": 1, "y": 1 }
]
},
"head": {
"rotate": [
{ "time": 0, "angle": 5.19 },
{ "time": 0.2333, "angle": 20.27 },
{ "time": 0.4, "angle": 15.27 },
{ "time": 0.6, "angle": -24.69 },
{ "time": 0.7666, "angle": -11.02 },
{ "time": 0.9333, "angle": -24.38 },
{ "time": 1.0666, "angle": 11.99 },
{ "time": 1.3, "angle": 4.86 },
{ "time": 1.3666, "angle": 5.19 }
],
"translate": [
{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 1.3666, "x": 0, "y": 0 }
],
"scale": [
{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 1.3666, "x": 1, "y": 1 }
]
},
"left shoulder": {
"rotate": [
{
"time": 0,
"angle": 0.05,
"curve": [ 0, 0, 0.62, 1 ]
},
{
"time": 0.2333,
"angle": 279.66,
"curve": [ 0.218, 0.67, 0.66, 0.99 ]
},
{
"time": 0.5,
"angle": 62.27,
"curve": [ 0.462, 0, 0.764, 0.58 ]
},
{ "time": 0.9333, "angle": 28.91 },
{ "time": 1.0666, "angle": -8.62 },
{ "time": 1.1666, "angle": -18.43 },
{ "time": 1.3666, "angle": 0.05 }
],
"translate": [
{ "time": 0, "x": -1.76, "y": 0.56, "curve": "stepped" },
{ "time": 0.9333, "x": -1.76, "y": 0.56, "curve": "stepped" },
{ "time": 1.3666, "x": -1.76, "y": 0.56 }
],
"scale": [
{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 1.3666, "x": 1, "y": 1 }
]
},
"left hand": {
"rotate": [
{ "time": 0, "angle": 11.58, "curve": "stepped" },
{ "time": 0.9333, "angle": 11.58, "curve": "stepped" },
{ "time": 1.3666, "angle": 11.58 }
],
"translate": [
{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 1.3666, "x": 0, "y": 0 }
],
"scale": [
{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 1.3666, "x": 1, "y": 1 }
]
},
"left arm": {
"rotate": [
{ "time": 0, "angle": 0.51 },
{ "time": 0.4333, "angle": 12.82 },
{ "time": 0.6, "angle": 47.55 },
{ "time": 0.9333, "angle": 12.82 },
{ "time": 1.1666, "angle": -6.5 },
{ "time": 1.3666, "angle": 0.51 }
],
"translate": [
{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 1.3666, "x": 0, "y": 0 }
],
"scale": [
{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 1.3666, "x": 1, "y": 1 }
]
},
"right shoulder": {
"rotate": [
{
"time": 0,
"angle": 43.82,
"curve": [ 0, 0, 0.62, 1 ]
},
{
"time": 0.2333,
"angle": -8.74,
"curve": [ 0.304, 0.58, 0.709, 0.97 ]
},
{
"time": 0.5333,
"angle": -208.02,
"curve": [ 0.462, 0, 0.764, 0.58 ]
},
{ "time": 0.9333, "angle": -246.72 },
{ "time": 1.0666, "angle": -307.13 },
{ "time": 1.1666, "angle": 37.15 },
{ "time": 1.3666, "angle": 43.82 }
],
"translate": [
{ "time": 0, "x": -7.84, "y": 7.19, "curve": "stepped" },
{ "time": 0.9333, "x": -7.84, "y": 7.19, "curve": "stepped" },
{ "time": 1.3666, "x": -7.84, "y": 7.19 }
],
"scale": [
{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 1.3666, "x": 1, "y": 1 }
]
},
"right arm": {
"rotate": [
{ "time": 0, "angle": -4.02 },
{ "time": 0.6, "angle": 17.5 },
{ "time": 0.9333, "angle": -4.02 },
{ "time": 1.1666, "angle": -16.72 },
{ "time": 1.3666, "angle": -4.02 }
],
"translate": [
{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 1.3666, "x": 0, "y": 0 }
],
"scale": [
{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 1.3666, "x": 1, "y": 1 }
]
},
"right hand": {
"rotate": [
{ "time": 0, "angle": 22.92, "curve": "stepped" },
{ "time": 0.9333, "angle": 22.92, "curve": "stepped" },
{ "time": 1.3666, "angle": 22.92 }
],
"translate": [
{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" },
{ "time": 1.3666, "x": 0, "y": 0 }
],
"scale": [
{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 1.3666, "x": 1, "y": 1 }
]
},
"root": {
"rotate": [
{ "time": 0, "angle": 0 },
{ "time": 0.4333, "angle": -14.52 },
{ "time": 0.8, "angle": 9.86 },
{ "time": 1.3666, "angle": 0 }
],
"scale": [
{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 1.3666, "x": 1, "y": 1 }
]
}
}
}

View File

@ -0,0 +1,101 @@
{
"bones": [
{ "name": "root", "length": 0 },
{ "name": "hip", "parent": "root", "length": 0, "x": 0.64, "y": 114.41 },
{ "name": "left upper leg", "parent": "hip", "length": 50.39, "x": 14.45, "y": 2.81, "rotation": -89.09 },
{ "name": "left lower leg", "parent": "left upper leg", "length": 56.45, "x": 51.78, "y": 3.46, "rotation": -16.65 },
{ "name": "left foot", "parent": "left lower leg", "length": 46.5, "x": 64.02, "y": -8.67, "rotation": 102.43 },
{ "name": "right upper leg", "parent": "hip", "length": 45.76, "x": -18.27, "rotation": -101.13 },
{ "name": "right lower leg", "parent": "right upper leg", "length": 58.52, "x": 50.21, "y": 0.6, "rotation": -10.7 },
{ "name": "right foot", "parent": "right lower leg", "length": 45.45, "x": 64.88, "y": 0.04, "rotation": 110.3 },
{ "name": "torso", "parent": "hip", "length": 85.82, "x": -6.42, "y": 1.97, "rotation": 94.95 },
{ "name": "neck", "parent": "torso", "length": 18.38, "x": 83.64, "y": -1.78, "rotation": 0.9 },
{ "name": "head", "parent": "neck", "length": 68.28, "x": 19.09, "y": 6.97, "rotation": -8.94 },
{ "name": "right shoulder", "parent": "torso", "length": 49.95, "x": 81.9, "y": 6.79, "rotation": 130.6 },
{ "name": "right arm", "parent": "right shoulder", "length": 36.74, "x": 49.95, "y": -0.12, "rotation": 40.12 },
{ "name": "right hand", "parent": "right arm", "length": 15.32, "x": 36.9, "y": 0.34, "rotation": 2.35 },
{ "name": "left shoulder", "parent": "torso", "length": 44.19, "x": 78.96, "y": -15.75, "rotation": -156.96 },
{ "name": "left arm", "parent": "left shoulder", "length": 35.62, "x": 44.19, "y": -0.01, "rotation": 28.16 },
{ "name": "left hand", "parent": "left arm", "length": 11.52, "x": 35.62, "y": 0.07, "rotation": 2.7 },
{ "name": "pelvis", "parent": "hip", "length": 0, "x": 1.41, "y": -6.57 }
],
"slots": [
{ "name": "template", "bone": "root", "color": "ff898c86" },
{ "name": "left shoulder", "bone": "left shoulder", "attachment": "left-shoulder" },
{ "name": "left arm", "bone": "left arm", "attachment": "left-arm" },
{ "name": "left hand", "bone": "left hand", "attachment": "left-hand" },
{ "name": "left foot", "bone": "left foot", "attachment": "left-foot" },
{ "name": "left lower leg", "bone": "left lower leg", "attachment": "left-lower-leg" },
{ "name": "left upper leg", "bone": "left upper leg", "attachment": "left-upper-leg" },
{ "name": "pelvis", "bone": "pelvis", "attachment": "pelvis" },
{ "name": "right foot", "bone": "right foot", "attachment": "right-foot" },
{ "name": "right lower leg", "bone": "right lower leg", "attachment": "right-lower-leg" },
{ "name": "right upper leg", "bone": "right upper leg", "attachment": "right-upper-leg" },
{ "name": "torso", "bone": "torso", "attachment": "torso" },
{ "name": "neck", "bone": "neck", "attachment": "neck" },
{ "name": "head", "bone": "head", "attachment": "head" },
{ "name": "eyes", "bone": "head", "attachment": "eyes" },
{ "name": "right shoulder", "bone": "right shoulder", "attachment": "right-shoulder" },
{ "name": "right arm", "bone": "right arm", "attachment": "right-arm" },
{ "name": "right hand", "bone": "right hand", "attachment": "right-hand" }
],
"skins": {
"default": {
"template": {
"spineboy": { "y": 167.82, "width": 145, "height": 341 }
},
"left shoulder": {
"left-shoulder": { "x": 23.74, "y": 0.11, "rotation": 62.01, "width": 34, "height": 53 }
},
"left arm": {
"left-arm": { "x": 15.11, "y": -0.44, "rotation": 33.84, "width": 35, "height": 29 }
},
"left hand": {
"left-hand": { "x": 0.75, "y": 1.86, "rotation": 31.14, "width": 35, "height": 38 }
},
"left foot": {
"left-foot": { "x": 24.35, "y": 8.88, "rotation": 3.32, "width": 65, "height": 30 }
},
"left lower leg": {
"left-lower-leg": { "x": 24.55, "y": -1.92, "rotation": 105.75, "width": 49, "height": 64 }
},
"left upper leg": {
"left-upper-leg": { "x": 26.12, "y": -1.85, "rotation": 89.09, "width": 33, "height": 67 }
},
"pelvis": {
"pelvis": { "x": -4.83, "y": 10.62, "width": 63, "height": 47 }
},
"right foot": {
"right-foot": { "x": 19.02, "y": 8.47, "rotation": 1.52, "width": 67, "height": 30 }
},
"right lower leg": {
"right-lower-leg": { "x": 23.28, "y": -2.59, "rotation": 111.83, "width": 51, "height": 64 }
},
"right upper leg": {
"right-upper-leg": { "x": 23.03, "y": 0.25, "rotation": 101.13, "width": 44, "height": 70 }
},
"torso": {
"torso": { "x": 44.57, "y": -7.08, "rotation": -94.95, "width": 68, "height": 92 }
},
"neck": {
"neck": { "x": 9.42, "y": -3.66, "rotation": -100.15, "width": 34, "height": 28 }
},
"head": {
"head": { "x": 53.94, "y": -5.75, "rotation": -86.9, "width": 121, "height": 132 }
},
"eyes": {
"eyes": { "x": 28.94, "y": -32.92, "rotation": -86.9, "width": 34, "height": 27 },
"eyes-closed": { "x": 28.77, "y": -32.86, "rotation": -86.9, "width": 34, "height": 27 }
},
"right shoulder": {
"right-shoulder": { "x": 25.86, "y": 0.03, "rotation": 134.44, "width": 52, "height": 51 }
},
"right arm": {
"right-arm": { "x": 18.34, "y": -2.64, "rotation": 94.32, "width": 21, "height": 45 }
},
"right hand": {
"right-hand": { "x": 6.82, "y": 1.25, "rotation": 91.96, "width": 32, "height": 32 }
}
}
}
}

Binary file not shown.

View File

@ -0,0 +1,278 @@
{
"bones": {
"left upper leg": {
"rotate": [
{ "time": 0, "angle": -26.55 },
{ "time": 0.1333, "angle": -8.78 },
{ "time": 0.2666, "angle": 9.51 },
{ "time": 0.4, "angle": 30.74 },
{ "time": 0.5333, "angle": 25.33 },
{ "time": 0.6666, "angle": 26.11 },
{ "time": 0.8, "angle": -7.7 },
{ "time": 0.9333, "angle": -21.19 },
{ "time": 1.0666, "angle": -26.55 }
],
"translate": [
{ "time": 0, "x": -3, "y": -2.25 },
{ "time": 0.4, "x": -2.18, "y": -2.25 },
{ "time": 1.0666, "x": -3, "y": -2.25 }
]
},
"right upper leg": {
"rotate": [
{ "time": 0, "angle": 42.45 },
{ "time": 0.1333, "angle": 52.1 },
{ "time": 0.2666, "angle": 5.96 },
{ "time": 0.5333, "angle": -16.93 },
{ "time": 0.6666, "angle": 1.89 },
{
"time": 0.8,
"angle": 28.06,
"curve": [ 0.462, 0.11, 1, 1 ]
},
{
"time": 0.9333,
"angle": 58.68,
"curve": [ 0.5, 0.02, 1, 1 ]
},
{ "time": 1.0666, "angle": 42.45 }
],
"translate": [
{ "time": 0, "x": 8.11, "y": -2.36 },
{ "time": 0.1333, "x": 10.03, "y": -2.56 },
{ "time": 0.4, "x": 2.76, "y": -2.97 },
{ "time": 0.5333, "x": 2.76, "y": -2.81 },
{ "time": 0.9333, "x": 8.67, "y": -2.54 },
{ "time": 1.0666, "x": 8.11, "y": -2.36 }
]
},
"left lower leg": {
"rotate": [
{ "time": 0, "angle": -10.21 },
{ "time": 0.1333, "angle": -55.64 },
{ "time": 0.2666, "angle": -68.12 },
{ "time": 0.5333, "angle": 5.11 },
{ "time": 0.6666, "angle": -28.29 },
{ "time": 0.8, "angle": 4.08 },
{ "time": 0.9333, "angle": 3.53 },
{ "time": 1.0666, "angle": -10.21 }
]
},
"left foot": {
"rotate": [
{ "time": 0, "angle": -3.69 },
{ "time": 0.1333, "angle": -10.42 },
{ "time": 0.2666, "angle": -17.14 },
{ "time": 0.4, "angle": -2.83 },
{ "time": 0.5333, "angle": -3.87 },
{ "time": 0.6666, "angle": 2.78 },
{ "time": 0.8, "angle": 1.68 },
{ "time": 0.9333, "angle": -8.54 },
{ "time": 1.0666, "angle": -3.69 }
]
},
"right shoulder": {
"rotate": [
{
"time": 0,
"angle": 20.89,
"curve": [ 0.264, 0, 0.75, 1 ]
},
{
"time": 0.1333,
"angle": 3.72,
"curve": [ 0.272, 0, 0.841, 1 ]
},
{ "time": 0.6666, "angle": -278.28 },
{ "time": 1.0666, "angle": 20.89 }
],
"translate": [
{ "time": 0, "x": -7.84, "y": 7.19 },
{ "time": 0.1333, "x": -6.36, "y": 6.42 },
{ "time": 0.6666, "x": -11.07, "y": 5.25 },
{ "time": 1.0666, "x": -7.84, "y": 7.19 }
]
},
"right arm": {
"rotate": [
{
"time": 0,
"angle": -4.02,
"curve": [ 0.267, 0, 0.804, 0.99 ]
},
{
"time": 0.1333,
"angle": -13.99,
"curve": [ 0.341, 0, 1, 1 ]
},
{
"time": 0.6666,
"angle": 36.54,
"curve": [ 0.307, 0, 0.787, 0.99 ]
},
{ "time": 1.0666, "angle": -4.02 }
]
},
"right hand": {
"rotate": [
{ "time": 0, "angle": 22.92 },
{ "time": 0.4, "angle": -8.97 },
{ "time": 0.6666, "angle": 0.51 },
{ "time": 1.0666, "angle": 22.92 }
]
},
"left shoulder": {
"rotate": [
{ "time": 0, "angle": -1.47 },
{ "time": 0.1333, "angle": 13.6 },
{ "time": 0.6666, "angle": 280.74 },
{ "time": 1.0666, "angle": -1.47 }
],
"translate": [
{ "time": 0, "x": -1.76, "y": 0.56 },
{ "time": 0.6666, "x": -2.47, "y": 8.14 },
{ "time": 1.0666, "x": -1.76, "y": 0.56 }
]
},
"left hand": {
"rotate": [
{
"time": 0,
"angle": 11.58,
"curve": [ 0.169, 0.37, 0.632, 1.55 ]
},
{
"time": 0.1333,
"angle": 28.13,
"curve": [ 0.692, 0, 0.692, 0.99 ]
},
{
"time": 0.6666,
"angle": -27.42,
"curve": [ 0.117, 0.41, 0.738, 1.76 ]
},
{ "time": 0.8, "angle": -36.32 },
{ "time": 1.0666, "angle": 11.58 }
]
},
"left arm": {
"rotate": [
{ "time": 0, "angle": -8.27 },
{ "time": 0.1333, "angle": 18.43 },
{ "time": 0.6666, "angle": 0.88 },
{ "time": 1.0666, "angle": -8.27 }
]
},
"torso": {
"rotate": [
{ "time": 0, "angle": -10.28 },
{
"time": 0.1333,
"angle": -15.38,
"curve": [ 0.545, 0, 1, 1 ]
},
{
"time": 0.4,
"angle": -9.78,
"curve": [ 0.58, 0.17, 1, 1 ]
},
{ "time": 0.6666, "angle": -15.75 },
{ "time": 0.9333, "angle": -7.06 },
{ "time": 1.0666, "angle": -10.28 }
],
"translate": [
{ "time": 0, "x": -3.67, "y": 1.68 },
{ "time": 0.1333, "x": -3.67, "y": 0.68 },
{ "time": 0.4, "x": -3.67, "y": 1.97 },
{ "time": 0.6666, "x": -3.67, "y": -0.14 },
{ "time": 1.0666, "x": -3.67, "y": 1.68 }
]
},
"right foot": {
"rotate": [
{ "time": 0, "angle": -5.25 },
{ "time": 0.2666, "angle": -4.08 },
{ "time": 0.4, "angle": -6.45 },
{ "time": 0.5333, "angle": -5.39 },
{ "time": 0.8, "angle": -11.68 },
{ "time": 0.9333, "angle": 0.46 },
{ "time": 1.0666, "angle": -5.25 }
]
},
"right lower leg": {
"rotate": [
{ "time": 0, "angle": -3.39 },
{ "time": 0.1333, "angle": -45.53 },
{ "time": 0.2666, "angle": -2.59 },
{ "time": 0.5333, "angle": -19.53 },
{ "time": 0.6666, "angle": -64.8 },
{
"time": 0.8,
"angle": -82.56,
"curve": [ 0.557, 0.18, 1, 1 ]
},
{ "time": 1.0666, "angle": -3.39 }
]
},
"hip": {
"rotate": [
{ "time": 0, "angle": 0, "curve": "stepped" },
{ "time": 1.0666, "angle": 0 }
],
"translate": [
{ "time": 0, "x": 0, "y": 0 },
{
"time": 0.1333,
"x": 0,
"y": -7.61,
"curve": [ 0.272, 0.86, 1, 1 ]
},
{ "time": 0.4, "x": 0, "y": 8.7 },
{ "time": 0.5333, "x": 0, "y": -0.41 },
{
"time": 0.6666,
"x": 0,
"y": -7.05,
"curve": [ 0.235, 0.89, 1, 1 ]
},
{ "time": 0.8, "x": 0, "y": 2.92 },
{ "time": 0.9333, "x": 0, "y": 6.78 },
{ "time": 1.0666, "x": 0, "y": 0 }
]
},
"neck": {
"rotate": [
{ "time": 0, "angle": 3.6 },
{ "time": 0.1333, "angle": 17.49 },
{ "time": 0.2666, "angle": 6.1 },
{ "time": 0.4, "angle": 3.45 },
{ "time": 0.5333, "angle": 5.17 },
{ "time": 0.6666, "angle": 18.36 },
{ "time": 0.8, "angle": 6.09 },
{ "time": 0.9333, "angle": 2.28 },
{ "time": 1.0666, "angle": 3.6 }
]
},
"head": {
"rotate": [
{
"time": 0,
"angle": 3.6,
"curve": [ 0, 0, 0.704, 1.61 ]
},
{ "time": 0.1666, "angle": -0.2 },
{ "time": 0.2666, "angle": 6.1 },
{ "time": 0.4, "angle": 3.45 },
{
"time": 0.5333,
"angle": 5.17,
"curve": [ 0, 0, 0.704, 1.61 ]
},
{ "time": 0.7, "angle": 1.1 },
{ "time": 0.8, "angle": 6.09 },
{ "time": 0.9333, "angle": 2.28 },
{ "time": 1.0666, "angle": 3.6 }
]
}
}
}

View File

@ -0,0 +1,166 @@
spineboy.png
format: RGBA8888
filter: Nearest,Nearest
repeat: none
head
rotate: false
xy: 1, 122
size: 121, 132
orig: 121, 132
offset: 0, 0
index: -1
torso
rotate: false
xy: 1, 28
size: 68, 92
orig: 68, 92
offset: 0, 0
index: -1
left-pant-bottom
rotate: false
xy: 1, 4
size: 44, 22
orig: 44, 22
offset: 0, 0
index: -1
right-pant-bottom
rotate: false
xy: 47, 8
size: 46, 18
orig: 46, 18
offset: 0, 0
index: -1
right-upper-leg
rotate: false
xy: 71, 50
size: 44, 70
orig: 44, 70
offset: 0, 0
index: -1
pelvis
rotate: false
xy: 95, 1
size: 63, 47
orig: 63, 47
offset: 0, 0
index: -1
left-upper-leg
rotate: false
xy: 117, 53
size: 33, 67
orig: 33, 67
offset: 0, 0
index: -1
right-foot
rotate: false
xy: 160, 224
size: 67, 30
orig: 67, 30
offset: 0, 0
index: -1
left-shoulder
rotate: false
xy: 124, 201
size: 34, 53
orig: 34, 53
offset: 0, 0
index: -1
left-ankle
rotate: false
xy: 229, 222
size: 25, 32
orig: 25, 32
offset: 0, 0
index: -1
left-foot
rotate: false
xy: 160, 192
size: 65, 30
orig: 65, 30
offset: 0, 0
index: -1
neck
rotate: false
xy: 124, 171
size: 34, 28
orig: 34, 28
offset: 0, 0
index: -1
right-arm
rotate: false
xy: 124, 124
size: 21, 45
orig: 21, 45
offset: 0, 0
index: -1
right-ankle
rotate: false
xy: 227, 190
size: 25, 30
orig: 25, 30
offset: 0, 0
index: -1
left-hand
rotate: false
xy: 147, 131
size: 35, 38
orig: 35, 38
offset: 0, 0
index: -1
left-arm
rotate: false
xy: 184, 161
size: 35, 29
orig: 35, 29
offset: 0, 0
index: -1
eyes-closed
rotate: false
xy: 221, 161
size: 34, 27
orig: 34, 27
offset: 0, 0
index: -1
right-lower-leg
rotate: false
xy: 152, 65
size: 51, 64
orig: 51, 64
offset: 0, 0
index: -1
right-foot-idle
rotate: false
xy: 184, 131
size: 53, 28
orig: 53, 28
offset: 0, 0
index: -1
left-lower-leg
rotate: false
xy: 205, 65
size: 49, 64
orig: 49, 64
offset: 0, 0
index: -1
right-shoulder
rotate: false
xy: 160, 12
size: 52, 51
orig: 52, 51
offset: 0, 0
index: -1
eyes
rotate: false
xy: 214, 36
size: 34, 27
orig: 34, 27
offset: 0, 0
index: -1
right-hand
rotate: false
xy: 214, 2
size: 32, 32
orig: 32, 32
offset: 0, 0
index: -1

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.