ソースを参照

Initial comment for sequence attachments (frame-by-frame).

EsotericSoftware/spine-editor#9
Nathan Sweet 4 年 前
コミット
457aa3a894

+ 5 - 0
spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTests.java

@@ -47,6 +47,7 @@ import com.esotericsoftware.spine.attachments.MeshAttachment;
 import com.esotericsoftware.spine.attachments.PathAttachment;
 import com.esotericsoftware.spine.attachments.PathAttachment;
 import com.esotericsoftware.spine.attachments.PointAttachment;
 import com.esotericsoftware.spine.attachments.PointAttachment;
 import com.esotericsoftware.spine.attachments.RegionAttachment;
 import com.esotericsoftware.spine.attachments.RegionAttachment;
+import com.esotericsoftware.spine.attachments.SequenceAttachment;
 
 
 public class AnimationStateTests {
 public class AnimationStateTests {
 	final SkeletonJson json = new SkeletonJson(new AttachmentLoader() {
 	final SkeletonJson json = new SkeletonJson(new AttachmentLoader() {
@@ -58,6 +59,10 @@ public class AnimationStateTests {
 			return null;
 			return null;
 		}
 		}
 
 
+		public SequenceAttachment newSequenceAttachment (Skin skin, String name, String path, int frameCount) {
+			return null;
+		}
+
 		public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) {
 		public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) {
 			return null;
 			return null;
 		}
 		}

+ 5 - 0
spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/BonePlotting.java

@@ -40,6 +40,7 @@ import com.esotericsoftware.spine.attachments.MeshAttachment;
 import com.esotericsoftware.spine.attachments.PathAttachment;
 import com.esotericsoftware.spine.attachments.PathAttachment;
 import com.esotericsoftware.spine.attachments.PointAttachment;
 import com.esotericsoftware.spine.attachments.PointAttachment;
 import com.esotericsoftware.spine.attachments.RegionAttachment;
 import com.esotericsoftware.spine.attachments.RegionAttachment;
+import com.esotericsoftware.spine.attachments.SequenceAttachment;
 
 
 public class BonePlotting {
 public class BonePlotting {
 	static public void main (String[] args) throws Exception {
 	static public void main (String[] args) throws Exception {
@@ -53,6 +54,10 @@ public class BonePlotting {
 				return null;
 				return null;
 			}
 			}
 
 
+			public SequenceAttachment newSequenceAttachment (Skin skin, String name, String path, int frameCount) {
+				return null;
+			}
+
 			public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) {
 			public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) {
 				return null;
 				return null;
 			}
 			}

+ 2 - 0
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java

@@ -42,6 +42,7 @@ import com.esotericsoftware.spine.attachments.Attachment;
 import com.esotericsoftware.spine.attachments.MeshAttachment;
 import com.esotericsoftware.spine.attachments.MeshAttachment;
 import com.esotericsoftware.spine.attachments.PathAttachment;
 import com.esotericsoftware.spine.attachments.PathAttachment;
 import com.esotericsoftware.spine.attachments.RegionAttachment;
 import com.esotericsoftware.spine.attachments.RegionAttachment;
+import com.esotericsoftware.spine.attachments.SequenceAttachment;
 
 
 /** Stores the current pose for a skeleton.
 /** Stores the current pose for a skeleton.
  * <p>
  * <p>
@@ -721,6 +722,7 @@ public class Skeleton {
 			int verticesLength = 0;
 			int verticesLength = 0;
 			float[] vertices = null;
 			float[] vertices = null;
 			Attachment attachment = slot.attachment;
 			Attachment attachment = slot.attachment;
+			if (attachment instanceof SequenceAttachment) attachment = ((SequenceAttachment)attachment).updateAttachment(slot);
 			if (attachment instanceof RegionAttachment) {
 			if (attachment instanceof RegionAttachment) {
 				verticesLength = 8;
 				verticesLength = 8;
 				vertices = temp.setSize(8);
 				vertices = temp.setSize(8);

+ 26 - 4
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java

@@ -85,6 +85,9 @@ import com.esotericsoftware.spine.attachments.MeshAttachment;
 import com.esotericsoftware.spine.attachments.PathAttachment;
 import com.esotericsoftware.spine.attachments.PathAttachment;
 import com.esotericsoftware.spine.attachments.PointAttachment;
 import com.esotericsoftware.spine.attachments.PointAttachment;
 import com.esotericsoftware.spine.attachments.RegionAttachment;
 import com.esotericsoftware.spine.attachments.RegionAttachment;
+import com.esotericsoftware.spine.attachments.SequenceAttachment;
+import com.esotericsoftware.spine.attachments.SequenceAttachment.SequenceMode;
+import com.esotericsoftware.spine.attachments.TextureRegionAttachment;
 import com.esotericsoftware.spine.attachments.VertexAttachment;
 import com.esotericsoftware.spine.attachments.VertexAttachment;
 
 
 /** Loads skeleton data in the Spine binary format.
 /** Loads skeleton data in the Spine binary format.
@@ -302,7 +305,7 @@ public class SkeletonBinary extends SkeletonLoader {
 				if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent);
 				if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent);
 				linkedMesh.mesh.setDeformAttachment(linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh);
 				linkedMesh.mesh.setDeformAttachment(linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh);
 				linkedMesh.mesh.setParentMesh((MeshAttachment)parent);
 				linkedMesh.mesh.setParentMesh((MeshAttachment)parent);
-				linkedMesh.mesh.updateUVs();
+				linkedMesh.mesh.updateRegion();
 			}
 			}
 			linkedMeshes.clear();
 			linkedMeshes.clear();
 
 
@@ -408,7 +411,7 @@ public class SkeletonBinary extends SkeletonLoader {
 			region.setWidth(width * scale);
 			region.setWidth(width * scale);
 			region.setHeight(height * scale);
 			region.setHeight(height * scale);
 			Color.rgba8888ToColor(region.getColor(), color);
 			Color.rgba8888ToColor(region.getColor(), color);
-			region.updateOffset();
+			region.updateRegion();
 			return region;
 			return region;
 		}
 		}
 		case boundingbox: {
 		case boundingbox: {
@@ -450,7 +453,7 @@ public class SkeletonBinary extends SkeletonLoader {
 			mesh.setWorldVerticesLength(vertexCount << 1);
 			mesh.setWorldVerticesLength(vertexCount << 1);
 			mesh.setTriangles(triangles);
 			mesh.setTriangles(triangles);
 			mesh.setRegionUVs(uvs);
 			mesh.setRegionUVs(uvs);
-			mesh.updateUVs();
+			mesh.updateRegion();
 			mesh.setHullLength(hullLength << 1);
 			mesh.setHullLength(hullLength << 1);
 			if (nonessential) {
 			if (nonessential) {
 				mesh.setEdges(edges);
 				mesh.setEdges(edges);
@@ -518,7 +521,7 @@ public class SkeletonBinary extends SkeletonLoader {
 			if (nonessential) Color.rgba8888ToColor(point.getColor(), color);
 			if (nonessential) Color.rgba8888ToColor(point.getColor(), color);
 			return point;
 			return point;
 		}
 		}
-		case clipping:
+		case clipping: {
 			int endSlotIndex = input.readInt(true);
 			int endSlotIndex = input.readInt(true);
 			int vertexCount = input.readInt(true);
 			int vertexCount = input.readInt(true);
 			Vertices vertices = readVertices(input, vertexCount);
 			Vertices vertices = readVertices(input, vertexCount);
@@ -533,6 +536,25 @@ public class SkeletonBinary extends SkeletonLoader {
 			if (nonessential) Color.rgba8888ToColor(clip.getColor(), color);
 			if (nonessential) Color.rgba8888ToColor(clip.getColor(), color);
 			return clip;
 			return clip;
 		}
 		}
+		case sequence:
+			Attachment attachment = readAttachment(input, skeletonData, skin, slotIndex, attachmentName, nonessential);
+			int frameCount = input.readInt(true);
+			float frameTime = input.readFloat();
+			SequenceMode mode = SequenceMode.values[input.readInt(true)];
+
+			if (attachment == null) return null;
+			String path = ((TextureRegionAttachment)attachment).getPath();
+
+			SequenceAttachment sequence = attachmentLoader.newSequenceAttachment(skin, name, path, frameCount);
+			if (sequence == null) return null;
+
+			sequence.setAttachment(attachment);
+			sequence.setPath(path);
+			sequence.setFrameCount(frameCount);
+			sequence.setFrameTime(frameTime);
+			sequence.setMode(mode);
+			return sequence;
+		}
 		return null;
 		return null;
 	}
 	}
 
 

+ 3 - 3
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java

@@ -323,7 +323,7 @@ public class SkeletonJson extends SkeletonLoader {
 			if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent);
 			if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent);
 			linkedMesh.mesh.setDeformAttachment(linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh);
 			linkedMesh.mesh.setDeformAttachment(linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh);
 			linkedMesh.mesh.setParentMesh((MeshAttachment)parent);
 			linkedMesh.mesh.setParentMesh((MeshAttachment)parent);
-			linkedMesh.mesh.updateUVs();
+			linkedMesh.mesh.updateRegion();
 		}
 		}
 		linkedMeshes.clear();
 		linkedMeshes.clear();
 
 
@@ -380,7 +380,7 @@ public class SkeletonJson extends SkeletonLoader {
 			String color = map.getString("color", null);
 			String color = map.getString("color", null);
 			if (color != null) Color.valueOf(color, region.getColor());
 			if (color != null) Color.valueOf(color, region.getColor());
 
 
-			region.updateOffset();
+			region.updateRegion();
 			return region;
 			return region;
 		}
 		}
 		case boundingbox: {
 		case boundingbox: {
@@ -416,7 +416,7 @@ public class SkeletonJson extends SkeletonLoader {
 			readVertices(map, mesh, uvs.length);
 			readVertices(map, mesh, uvs.length);
 			mesh.setTriangles(map.require("triangles").asShortArray());
 			mesh.setTriangles(map.require("triangles").asShortArray());
 			mesh.setRegionUVs(uvs);
 			mesh.setRegionUVs(uvs);
-			mesh.updateUVs();
+			mesh.updateRegion();
 
 
 			if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt() << 1);
 			if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt() << 1);
 			if (map.has("edges")) mesh.setEdges(map.require("edges").asShortArray());
 			if (map.has("edges")) mesh.setEdges(map.require("edges").asShortArray());

+ 3 - 2
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SpringConstraint.java

@@ -1,8 +1,8 @@
 /******************************************************************************
 /******************************************************************************
  * Spine Runtimes License Agreement
  * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
+ * Last updated September 24, 2021. Replaces all prior versions.
  *
  *
- * Copyright (c) 2013-2020, Esoteric Software LLC
+ * Copyright (c) 2013-2021, Esoteric Software LLC
  *
  *
  * Integration of the Spine Runtimes into software or otherwise creating
  * Integration of the Spine Runtimes into software or otherwise creating
  * derivative works of the Spine Runtimes is permitted under the terms and
  * derivative works of the Spine Runtimes is permitted under the terms and
@@ -37,6 +37,7 @@ import com.badlogic.gdx.utils.Array;
 public class SpringConstraint implements Updatable {
 public class SpringConstraint implements Updatable {
 	final SpringConstraintData data;
 	final SpringConstraintData data;
 	final Array<Bone> bones;
 	final Array<Bone> bones;
+	// BOZO! - stiffness -> strength. stiffness, damping, rope, stretch -> move to spring.
 	float mix, friction, gravity, wind, stiffness, damping;
 	float mix, friction, gravity, wind, stiffness, damping;
 	boolean rope, stretch;
 	boolean rope, stretch;
 
 

+ 12 - 0
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java

@@ -63,6 +63,18 @@ public class AtlasAttachmentLoader implements AttachmentLoader {
 		return attachment;
 		return attachment;
 	}
 	}
 
 
+	public SequenceAttachment newSequenceAttachment (Skin skin, String name, String path, int frameCount) {
+		AtlasRegion[] regions = new AtlasRegion[frameCount];
+		for (int i = 0; i < frameCount; i++) {
+			AtlasRegion region = atlas.findRegion(path + frameCount); // BOZO - Zero pad?
+			if (region == null)
+				throw new RuntimeException("Region not found in atlas: " + path + frameCount + " (sequence: " + name + ")");
+		}
+		SequenceAttachment sequence = new SequenceAttachment(name);
+		sequence.setRegions(regions);
+		return sequence;
+	}
+
 	public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) {
 	public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) {
 		return new BoundingBoxAttachment(name);
 		return new BoundingBoxAttachment(name);
 	}
 	}

+ 3 - 0
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java

@@ -44,6 +44,9 @@ public interface AttachmentLoader {
 	/** @return May be null to not load the attachment. In that case null should also be returned for child meshes. */
 	/** @return May be null to not load the attachment. In that case null should also be returned for child meshes. */
 	public @Null MeshAttachment newMeshAttachment (Skin skin, String name, String path);
 	public @Null MeshAttachment newMeshAttachment (Skin skin, String name, String path);
 
 
+	/** @return May be null to not load the attachment. */
+	public @Null SequenceAttachment newSequenceAttachment (Skin skin, String name, String path, int frameCount);
+
 	/** @return May be null to not load the attachment. */
 	/** @return May be null to not load the attachment. */
 	public @Null BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name);
 	public @Null BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name);
 
 

+ 1 - 1
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentType.java

@@ -30,7 +30,7 @@
 package com.esotericsoftware.spine.attachments;
 package com.esotericsoftware.spine.attachments;
 
 
 public enum AttachmentType {
 public enum AttachmentType {
-	region, boundingbox, mesh, linkedmesh, path, point, clipping;
+	region, boundingbox, mesh, linkedmesh, path, point, clipping, sequence;
 
 
 	static public final AttachmentType[] values = values();
 	static public final AttachmentType[] values = values();
 }
 }

+ 6 - 8
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java

@@ -40,7 +40,7 @@ import com.badlogic.gdx.utils.Null;
  * supported. Each vertex has UVs (texture coordinates) and triangles are used to map an image on to the mesh.
  * supported. Each vertex has UVs (texture coordinates) and triangles are used to map an image on to the mesh.
  * <p>
  * <p>
  * See <a href="http://esotericsoftware.com/spine-meshes">Mesh attachments</a> in the Spine User Guide. */
  * See <a href="http://esotericsoftware.com/spine-meshes">Mesh attachments</a> in the Spine User Guide. */
-public class MeshAttachment extends VertexAttachment {
+public class MeshAttachment extends VertexAttachment implements TextureRegionAttachment {
 	private TextureRegion region;
 	private TextureRegion region;
 	private String path;
 	private String path;
 	private float[] regionUVs, uvs;
 	private float[] regionUVs, uvs;
@@ -67,9 +67,9 @@ public class MeshAttachment extends VertexAttachment {
 		return region;
 		return region;
 	}
 	}
 
 
-	/** Calculates {@link #uvs} using {@link #regionUVs} and the {@link #region}. Must be called after changing the region UVs or
-	 * region. */
-	public void updateUVs () {
+	/** Calculates {@link #uvs} using {@link #regionUVs} and the {@link #region}. Must be called after changing the region or the
+	 * region's properties. */
+	public void updateRegion () {
 		float[] regionUVs = this.regionUVs;
 		float[] regionUVs = this.regionUVs;
 		if (this.uvs == null || this.uvs.length != regionUVs.length) this.uvs = new float[regionUVs.length];
 		if (this.uvs == null || this.uvs.length != regionUVs.length) this.uvs = new float[regionUVs.length];
 		float[] uvs = this.uvs;
 		float[] uvs = this.uvs;
@@ -152,7 +152,7 @@ public class MeshAttachment extends VertexAttachment {
 
 
 	/** The UV pair for each vertex, normalized within the entire texture.
 	/** The UV pair for each vertex, normalized within the entire texture.
 	 * <p>
 	 * <p>
-	 * See {@link #updateUVs}. */
+	 * See {@link #updateRegion()}. */
 	public float[] getUVs () {
 	public float[] getUVs () {
 		return uvs;
 		return uvs;
 	}
 	}
@@ -161,12 +161,10 @@ public class MeshAttachment extends VertexAttachment {
 		this.uvs = uvs;
 		this.uvs = uvs;
 	}
 	}
 
 
-	/** The color to tint the mesh. */
 	public Color getColor () {
 	public Color getColor () {
 		return color;
 		return color;
 	}
 	}
 
 
-	/** The name of the texture region for this attachment. */
 	public String getPath () {
 	public String getPath () {
 		return path;
 		return path;
 	}
 	}
@@ -269,7 +267,7 @@ public class MeshAttachment extends VertexAttachment {
 		mesh.color.set(color);
 		mesh.color.set(color);
 		mesh.deformAttachment = deformAttachment;
 		mesh.deformAttachment = deformAttachment;
 		mesh.setParentMesh(parentMesh != null ? parentMesh : this);
 		mesh.setParentMesh(parentMesh != null ? parentMesh : this);
-		mesh.updateUVs();
+		mesh.updateRegion();
 		return mesh;
 		return mesh;
 	}
 	}
 }
 }

+ 6 - 7
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java

@@ -40,7 +40,7 @@ import com.esotericsoftware.spine.Bone;
 /** An attachment that displays a textured quadrilateral.
 /** An attachment that displays a textured quadrilateral.
  * <p>
  * <p>
  * See <a href="http://esotericsoftware.com/spine-regions">Region attachments</a> in the Spine User Guide. */
  * See <a href="http://esotericsoftware.com/spine-regions">Region attachments</a> in the Spine User Guide. */
-public class RegionAttachment extends Attachment {
+public class RegionAttachment extends Attachment implements TextureRegionAttachment {
 	static public final int BLX = 0;
 	static public final int BLX = 0;
 	static public final int BLY = 1;
 	static public final int BLY = 1;
 	static public final int ULX = 2;
 	static public final int ULX = 2;
@@ -61,8 +61,9 @@ public class RegionAttachment extends Attachment {
 		super(name);
 		super(name);
 	}
 	}
 
 
-	/** Calculates the {@link #offset} using the region settings. Must be called after changing region settings. */
-	public void updateOffset () {
+	/** Calculates the {@link #offset} using the {@link #region}. Must be called after changing the region or the region's
+	 * properties. */
+	public void updateRegion () {
 		float width = getWidth();
 		float width = getWidth();
 		float height = getHeight();
 		float height = getHeight();
 		float localX2 = width / 2;
 		float localX2 = width / 2;
@@ -137,7 +138,7 @@ public class RegionAttachment extends Attachment {
 	}
 	}
 
 
 	public TextureRegion getRegion () {
 	public TextureRegion getRegion () {
-		if (region == null) throw new IllegalStateException("Region has not been set: " + this);
+		if (region == null) throw new IllegalStateException("Region has not been set: " + name);
 		return region;
 		return region;
 	}
 	}
 
 
@@ -180,7 +181,7 @@ public class RegionAttachment extends Attachment {
 
 
 	/** For each of the 4 vertices, a pair of <code>x,y</code> values that is the local position of the vertex.
 	/** For each of the 4 vertices, a pair of <code>x,y</code> values that is the local position of the vertex.
 	 * <p>
 	 * <p>
-	 * See {@link #updateOffset()}. */
+	 * See {@link #updateRegion()}. */
 	public float[] getOffset () {
 	public float[] getOffset () {
 		return offset;
 		return offset;
 	}
 	}
@@ -252,12 +253,10 @@ public class RegionAttachment extends Attachment {
 		this.height = height;
 		this.height = height;
 	}
 	}
 
 
-	/** The color to tint the region attachment. */
 	public Color getColor () {
 	public Color getColor () {
 		return color;
 		return color;
 	}
 	}
 
 
-	/** The name of the texture region for this attachment. */
 	public String getPath () {
 	public String getPath () {
 		return path;
 		return path;
 	}
 	}

+ 151 - 0
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/SequenceAttachment.java

@@ -0,0 +1,151 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated September 24, 2021. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2021, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+package com.esotericsoftware.spine.attachments;
+
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.math.MathUtils;
+
+import com.esotericsoftware.spine.Slot;
+
+/** An attachment that applies a sequence of texture atlas regions to a region or mesh attachment.
+ * <p>
+ * See <a href="http://esotericsoftware.com/spine-sequences">Sequence attachments</a> in the Spine User Guide. */
+public class SequenceAttachment<T extends Attachment & TextureRegionAttachment> extends Attachment {
+	private T attachment;
+	private String path;
+	private int frameCount;
+	private float frameTime;
+	private SequenceMode mode;
+	private TextureRegion[] regions;
+
+	public SequenceAttachment (String name) {
+		super(name);
+	}
+
+	/** Updates the {@link #attachment} with the {@link #regions region} for the slot's {@link Slot#getAttachmentTime()} and
+	 * returns it. */
+	public T updateAttachment (Slot slot) {
+		int frameIndex = (int)(slot.getAttachmentTime() / frameTime);
+		switch (mode) {
+		case forward:
+			frameIndex = Math.min(frameCount - 1, frameIndex);
+			break;
+		case backward:
+			frameIndex = Math.max(frameCount - frameIndex - 1, 0);
+			break;
+		case forwardLoop:
+			frameIndex = frameIndex % frameCount;
+			break;
+		case backwardLoop:
+			frameIndex = frameCount - (frameIndex % frameCount) - 1;
+			break;
+		case pingPong:
+			frameIndex = frameIndex % (frameCount << 1);
+			if (frameIndex >= frameCount) frameIndex = frameCount - 1 - (frameIndex - frameCount);
+			break;
+		case random:
+			frameIndex = MathUtils.random(frameCount - 1);
+		}
+		attachment.setRegion(regions[frameIndex]);
+		attachment.updateRegion();
+		return attachment;
+	}
+
+	public void setAttachment (T attachment) {
+		this.attachment = attachment;
+	}
+
+	public T getAttachment () {
+		return attachment;
+	}
+
+	/** The prefix used to find the {@link #regions} for this attachment. */
+	public String getPath () {
+		return path;
+	}
+
+	public void setPath (String path) {
+		this.path = path;
+	}
+
+	public SequenceMode getMode () {
+		return mode;
+	}
+
+	public void setMode (SequenceMode mode) {
+		if (mode == null) throw new IllegalArgumentException("mode cannot be null.");
+		this.mode = mode;
+	}
+
+	public int getFrameCount () {
+		return frameCount;
+	}
+
+	public void setFrameCount (int frameCount) {
+		this.frameCount = frameCount;
+	}
+
+	/** The time in seconds each frame is shown. */
+	public float getFrameTime () {
+		return frameTime;
+	}
+
+	public void setFrameTime (float frameTime) {
+		this.frameTime = frameTime;
+	}
+
+	public TextureRegion[] getRegions () {
+		if (regions == null) throw new IllegalStateException("Regions have not been set: " + name);
+		return regions;
+	}
+
+	public void setRegions (TextureRegion[] regions) {
+		if (regions == null) throw new IllegalArgumentException("regions cannot be null.");
+		this.regions = regions;
+	}
+
+	public Attachment copy () {
+		SequenceAttachment copy = new SequenceAttachment(name);
+		copy.attachment = attachment.copy();
+		copy.path = path;
+		copy.frameCount = frameCount;
+		copy.frameTime = frameTime;
+		copy.frameTime = frameTime;
+		copy.mode = mode;
+		copy.regions = regions;
+		return copy;
+	}
+
+	static public enum SequenceMode {
+		forward, backward, forwardLoop, backwardLoop, pingPong, random;
+
+		static public final SequenceMode[] values = values();
+	}
+}

+ 25 - 0
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/TextureRegionAttachment.java

@@ -0,0 +1,25 @@
+
+package com.esotericsoftware.spine.attachments;
+
+import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+
+public interface TextureRegionAttachment {
+	/** Sets the region used to draw the attachment. If the region or its properties are changed, {@link #updateRegion()} must be
+	 * called. */
+	public void setRegion (TextureRegion region);
+
+	public TextureRegion getRegion ();
+
+	/** Updates any values the attachment calculates using the {@link #getRegion()}. Must be called after changing the region or
+	 * the region's properties. */
+	public void updateRegion ();
+
+	/** The color to tint the attachment. */
+	public Color getColor ();
+
+	/** The name used to find the {@link #getRegion()}. */
+	public String getPath ();
+
+	public void setPath (String path);
+}