From afab54aaa1441e12bedf6e91aa2e57f461380b6a Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Wed, 22 Apr 2020 15:36:41 +0200 Subject: [PATCH 01/14] Javadocs. --- .../src/com/esotericsoftware/spine/SkeletonLoader.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonLoader.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonLoader.java index d8538cdf0..305ec863d 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonLoader.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonLoader.java @@ -9,7 +9,10 @@ import com.esotericsoftware.spine.SkeletonJson.LinkedMesh; import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader; import com.esotericsoftware.spine.attachments.AttachmentLoader; -/** Base class for loading skeleton data from a file. */ +/** Base class for loading skeleton data from a file. + *

+ * See JSON and binary data in the + * Spine Runtimes Guide. */ abstract public class SkeletonLoader { final AttachmentLoader attachmentLoader; float scale = 1; From 201f0bd7d58393e54b324ffa0b600e7d81bb83fd Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Wed, 22 Apr 2020 16:31:27 +0200 Subject: [PATCH 02/14] [unity] Fixed an out of bounds access in SkeletonMecanim when multiple interruptions occur. Closes #1661. --- .../Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs index a74d09875..fb075070a 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs @@ -417,9 +417,8 @@ namespace Spine.Unity { var interruptingStateInfo = animator.GetNextAnimatorStateInfo(layer); layerInfos.isLastFrameOfInterruption = interruptingStateInfo.fullPathHash == 0; if (!layerInfos.isLastFrameOfInterruption) { - layerInfos.interruptingClipInfoCount = interruptingClipInfos.Count; - animator.GetNextAnimatorClipInfo(layer, interruptingClipInfos); + layerInfos.interruptingClipInfoCount = interruptingClipInfos.Count; float oldTime = layerInfos.interruptingStateInfo.normalizedTime; float newTime = interruptingStateInfo.normalizedTime; layerInfos.interruptingClipTimeAddition = newTime - oldTime; From dea3e3594ca5d7a97d4e61bc776efbf526d66f67 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Wed, 22 Apr 2020 21:17:40 +0200 Subject: [PATCH 03/14] Moved @Null for method return values to the same line. --- .../spine/AnimationState.java | 15 +++---- .../src/com/esotericsoftware/spine/Bone.java | 3 +- .../com/esotericsoftware/spine/BoneData.java | 3 +- .../com/esotericsoftware/spine/Skeleton.java | 24 ++++------- .../spine/SkeletonBinary.java | 6 +-- .../spine/SkeletonBounds.java | 9 ++-- .../esotericsoftware/spine/SkeletonData.java | 42 +++++++------------ .../spine/SkeletonRenderer.java | 3 +- .../src/com/esotericsoftware/spine/Skin.java | 3 +- .../src/com/esotericsoftware/spine/Slot.java | 6 +-- .../com/esotericsoftware/spine/SlotData.java | 6 +-- .../spine/attachments/AttachmentLoader.java | 18 +++----- .../spine/attachments/VertexAttachment.java | 6 +-- 13 files changed, 48 insertions(+), 96 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java index 79ef400fe..8a23933f6 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -792,8 +792,7 @@ public class AnimationState { } /** Returns the track entry for the animation currently playing on the track, or null if no animation is currently playing. */ - @Null - public TrackEntry getCurrent (int trackIndex) { + public @Null TrackEntry getCurrent (int trackIndex) { if (trackIndex < 0) throw new IllegalArgumentException("trackIndex must be >= 0."); if (trackIndex >= tracks.size) return null; return tracks.get(trackIndex); @@ -1031,8 +1030,7 @@ public class AnimationState { *

* A track entry returned from {@link AnimationState#setAnimation(int, Animation, boolean)} is already the current animation * for the track, so the track entry listener {@link AnimationStateListener#start(TrackEntry)} will not be called. */ - @Null - public AnimationStateListener getListener () { + public @Null AnimationStateListener getListener () { return listener; } @@ -1087,8 +1085,7 @@ public class AnimationState { } /** The animation queued to start after this animation, or null. next makes up a linked list. */ - @Null - public TrackEntry getNext () { + public @Null TrackEntry getNext () { return next; } @@ -1147,15 +1144,13 @@ public class AnimationState { /** The track entry for the previous animation when mixing from the previous animation to this animation, or null if no * mixing is currently occuring. When mixing from multiple animations, mixingFrom makes up a linked list. */ - @Null - public TrackEntry getMixingFrom () { + public @Null TrackEntry getMixingFrom () { return mixingFrom; } /** The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is * currently occuring. When mixing to multiple animations, mixingTo makes up a linked list. */ - @Null - public TrackEntry getMixingTo () { + public @Null TrackEntry getMixingTo () { return mixingTo; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java index 490423a35..a0f3317ca 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java @@ -225,8 +225,7 @@ public class Bone implements Updatable { } /** The parent bone, or null if this is the root bone. */ - @Null - public Bone getParent () { + public @Null Bone getParent () { return parent; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java index 2d73b457e..94bb5a0b6 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java @@ -79,8 +79,7 @@ public class BoneData { return name; } - @Null - public BoneData getParent () { + public @Null BoneData getParent () { return parent; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java index 301af5bd1..b6b111045 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -484,8 +484,7 @@ public class Skeleton { /** Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it * repeatedly. */ - @Null - public Bone findBone (String boneName) { + public @Null Bone findBone (String boneName) { if (boneName == null) throw new IllegalArgumentException("boneName cannot be null."); Object[] bones = this.bones.items; for (int i = 0, n = this.bones.size; i < n; i++) { @@ -502,8 +501,7 @@ public class Skeleton { /** Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it * repeatedly. */ - @Null - public Slot findSlot (String slotName) { + public @Null Slot findSlot (String slotName) { if (slotName == null) throw new IllegalArgumentException("slotName cannot be null."); Object[] slots = this.slots.items; for (int i = 0, n = this.slots.size; i < n; i++) { @@ -524,8 +522,7 @@ public class Skeleton { } /** The skeleton's current skin. */ - @Null - public Skin getSkin () { + public @Null Skin getSkin () { return skin; } @@ -573,8 +570,7 @@ public class Skeleton { * name. *

* See {@link #getAttachment(int, String)}. */ - @Null - public Attachment getAttachment (String slotName, String attachmentName) { + public @Null Attachment getAttachment (String slotName, String attachmentName) { SlotData slot = data.findSlot(slotName); if (slot == null) throw new IllegalArgumentException("Slot not found: " + slotName); return getAttachment(slot.getIndex(), attachmentName); @@ -584,8 +580,7 @@ public class Skeleton { * attachment name. First the skin is checked and if the attachment was not found, the default skin is checked. *

* See Runtime skins in the Spine Runtimes Guide. */ - @Null - public Attachment getAttachment (int slotIndex, String attachmentName) { + public @Null Attachment getAttachment (int slotIndex, String attachmentName) { if (attachmentName == null) throw new IllegalArgumentException("attachmentName cannot be null."); if (skin != null) { Attachment attachment = skin.getAttachment(slotIndex, attachmentName); @@ -618,8 +613,7 @@ public class Skeleton { /** Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method * than to call it repeatedly. */ - @Null - public IkConstraint findIkConstraint (String constraintName) { + public @Null IkConstraint findIkConstraint (String constraintName) { if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null."); Object[] ikConstraints = this.ikConstraints.items; for (int i = 0, n = this.ikConstraints.size; i < n; i++) { @@ -636,8 +630,7 @@ public class Skeleton { /** Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of * this method than to call it repeatedly. */ - @Null - public TransformConstraint findTransformConstraint (String constraintName) { + public @Null TransformConstraint findTransformConstraint (String constraintName) { if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null."); Object[] transformConstraints = this.transformConstraints.items; for (int i = 0, n = this.transformConstraints.size; i < n; i++) { @@ -654,8 +647,7 @@ public class Skeleton { /** Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method * than to call it repeatedly. */ - @Null - public PathConstraint findPathConstraint (String constraintName) { + public @Null PathConstraint findPathConstraint (String constraintName) { if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null."); Object[] pathConstraints = this.pathConstraints.items; for (int i = 0, n = this.pathConstraints.size; i < n; i++) { diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java index bb67841bc..b2959d7c3 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -312,8 +312,7 @@ public class SkeletonBinary extends SkeletonLoader { return skeletonData; } - @Null - private Skin readSkin (SkeletonInput input, SkeletonData skeletonData, boolean defaultSkin, boolean nonessential) + private @Null Skin readSkin (SkeletonInput input, SkeletonData skeletonData, boolean defaultSkin, boolean nonessential) throws IOException { Skin skin; @@ -920,8 +919,7 @@ public class SkeletonBinary extends SkeletonLoader { super(file.read(512)); } - @Null - public String readStringRef () throws IOException { + public @Null String readStringRef () throws IOException { int index = readInt(true); return index == 0 ? null : strings[index - 1]; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBounds.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBounds.java index f15729812..7b56406cd 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBounds.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBounds.java @@ -143,8 +143,7 @@ public class SkeletonBounds { /** Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more * efficient to only call this method if {@link #aabbContainsPoint(float, float)} returns true. */ - @Null - public BoundingBoxAttachment containsPoint (float x, float y) { + public @Null BoundingBoxAttachment containsPoint (float x, float y) { Object[] polygons = this.polygons.items; for (int i = 0, n = this.polygons.size; i < n; i++) if (containsPoint((FloatArray)polygons[i], x, y)) return boundingBoxes.get(i); @@ -174,8 +173,7 @@ public class SkeletonBounds { /** Returns the first bounding box attachment that contains any part of the line segment, or null. When doing many checks, it * is usually more efficient to only call this method if {@link #aabbIntersectsSegment(float, float, float, float)} returns * true. */ - @Null - public BoundingBoxAttachment intersectsSegment (float x1, float y1, float x2, float y2) { + public @Null BoundingBoxAttachment intersectsSegment (float x1, float y1, float x2, float y2) { Object[] polygons = this.polygons.items; for (int i = 0, n = this.polygons.size; i < n; i++) if (intersectsSegment((FloatArray)polygons[i], x1, y1, x2, y2)) return boundingBoxes.get(i); @@ -248,8 +246,7 @@ public class SkeletonBounds { } /** Returns the polygon for the specified bounding box, or null. */ - @Null - public FloatArray getPolygon (BoundingBoxAttachment boundingBox) { + public @Null FloatArray getPolygon (BoundingBoxAttachment boundingBox) { if (boundingBox == null) throw new IllegalArgumentException("boundingBox cannot be null."); int index = boundingBoxes.indexOf(boundingBox, true); return index == -1 ? null : polygons.get(index); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java index 4ef6ab341..8319d57be 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java @@ -67,8 +67,7 @@ public class SkeletonData { /** Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it * multiple times. */ - @Null - public BoneData findBone (String boneName) { + public @Null BoneData findBone (String boneName) { if (boneName == null) throw new IllegalArgumentException("boneName cannot be null."); Object[] bones = this.bones.items; for (int i = 0, n = this.bones.size; i < n; i++) { @@ -87,8 +86,7 @@ public class SkeletonData { /** Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it * multiple times. */ - @Null - public SlotData findSlot (String slotName) { + public @Null SlotData findSlot (String slotName) { if (slotName == null) throw new IllegalArgumentException("slotName cannot be null."); Object[] slots = this.slots.items; for (int i = 0, n = this.slots.size; i < n; i++) { @@ -103,8 +101,7 @@ public class SkeletonData { /** The skeleton's default skin. By default this skin contains all attachments that were not in a skin in Spine. *

* See {@link Skeleton#getAttachment(int, String)}. */ - @Null - public Skin getDefaultSkin () { + public @Null Skin getDefaultSkin () { return defaultSkin; } @@ -114,8 +111,7 @@ public class SkeletonData { /** Finds a skin by comparing each skin's name. It is more efficient to cache the results of this method than to call it * multiple times. */ - @Null - public Skin findSkin (String skinName) { + public @Null 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; @@ -131,8 +127,7 @@ public class SkeletonData { /** Finds an event by comparing each events's name. It is more efficient to cache the results of this method than to call it * multiple times. */ - @Null - public EventData findEvent (String eventDataName) { + public @Null EventData findEvent (String eventDataName) { if (eventDataName == null) throw new IllegalArgumentException("eventDataName cannot be null."); for (EventData eventData : events) if (eventData.name.equals(eventDataName)) return eventData; @@ -153,8 +148,7 @@ public class SkeletonData { /** Finds an animation by comparing each animation's name. It is more efficient to cache the results of this method than to * call it multiple times. */ - @Null - public Animation findAnimation (String animationName) { + public @Null Animation findAnimation (String animationName) { if (animationName == null) throw new IllegalArgumentException("animationName cannot be null."); Object[] animations = this.animations.items; for (int i = 0, n = this.animations.size; i < n; i++) { @@ -173,8 +167,7 @@ public class SkeletonData { /** Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method * than to call it multiple times. */ - @Null - public IkConstraintData findIkConstraint (String constraintName) { + public @Null IkConstraintData findIkConstraint (String constraintName) { if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null."); Object[] ikConstraints = this.ikConstraints.items; for (int i = 0, n = this.ikConstraints.size; i < n; i++) { @@ -193,8 +186,7 @@ public class SkeletonData { /** Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of * this method than to call it multiple times. */ - @Null - public TransformConstraintData findTransformConstraint (String constraintName) { + public @Null TransformConstraintData findTransformConstraint (String constraintName) { if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null."); Object[] transformConstraints = this.transformConstraints.items; for (int i = 0, n = this.transformConstraints.size; i < n; i++) { @@ -213,8 +205,7 @@ public class SkeletonData { /** Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method * than to call it multiple times. */ - @Null - public PathConstraintData findPathConstraint (String constraintName) { + public @Null PathConstraintData findPathConstraint (String constraintName) { if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null."); Object[] pathConstraints = this.pathConstraints.items; for (int i = 0, n = this.pathConstraints.size; i < n; i++) { @@ -227,8 +218,7 @@ public class SkeletonData { // --- /** The skeleton's name, which by default is the name of the skeleton data file, if possible. */ - @Null - public String getName () { + public @Null String getName () { return name; } @@ -273,8 +263,7 @@ public class SkeletonData { } /** The Spine version used to export the skeleton data, or null. */ - @Null - public String getVersion () { + public @Null String getVersion () { return version; } @@ -283,8 +272,7 @@ public class SkeletonData { } /** The skeleton data hash. This value will change if any of the skeleton data has changed. */ - @Null - public String getHash () { + public @Null String getHash () { return hash; } @@ -293,8 +281,7 @@ public class SkeletonData { } /** The path to the images directory as defined in Spine. Available only when nonessential data was exported. */ - @Null - public String getImagesPath () { + public @Null String getImagesPath () { return imagesPath; } @@ -303,8 +290,7 @@ public class SkeletonData { } /** The path to the audio directory as defined in Spine. Available only when nonessential data was exported. */ - @Null - public String getAudioPath () { + public @Null String getAudioPath () { return audioPath; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java index 862ddd20c..231f5489c 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java @@ -448,8 +448,7 @@ public class SkeletonRenderer { this.premultipliedAlpha = premultipliedAlpha; } - @Null - public VertexEffect getVertexEffect () { + public @Null VertexEffect getVertexEffect () { return vertexEffect; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skin.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skin.java index d52feaa16..7b65396d9 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skin.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skin.java @@ -94,8 +94,7 @@ public class Skin { } /** Returns the attachment for the specified slot index and name, or null. */ - @Null - public Attachment getAttachment (int slotIndex, String name) { + public @Null Attachment getAttachment (int slotIndex, String name) { lookup.set(slotIndex, name); SkinEntry entry = attachments.get(lookup); return entry != null ? entry.attachment : null; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java index 622d771e8..a71a9fa2d 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java @@ -95,14 +95,12 @@ public class Slot { /** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark * color's alpha is not used. */ - @Null - public Color getDarkColor () { + public @Null Color getDarkColor () { return darkColor; } /** The current attachment for the slot, or null if the slot has no attachment. */ - @Null - public Attachment getAttachment () { + public @Null Attachment getAttachment () { return attachment; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java index 8497f6849..f002cf454 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java @@ -74,8 +74,7 @@ public class SlotData { /** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark * color's alpha is not used. */ - @Null - public Color getDarkColor () { + public @Null Color getDarkColor () { return darkColor; } @@ -88,8 +87,7 @@ public class SlotData { } /** The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. */ - @Null - public String getAttachmentName () { + public @Null String getAttachmentName () { return attachmentName; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java index 74e2fdf0f..53d59fcba 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java @@ -39,26 +39,20 @@ import com.esotericsoftware.spine.Skin; * Runtimes Guide. */ public interface AttachmentLoader { /** @return May be null to not load the attachment. */ - @Null - public RegionAttachment newRegionAttachment (Skin skin, String name, String path); + public @Null RegionAttachment newRegionAttachment (Skin skin, String name, String path); /** @return May be null to not load the attachment. */ - @Null - public 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. */ - @Null - public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name); + public @Null BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name); /** @return May be null to not load the attachment. */ - @Null - public ClippingAttachment newClippingAttachment (Skin skin, String name); + public @Null ClippingAttachment newClippingAttachment (Skin skin, String name); /** @return May be null to not load the attachment. */ - @Null - public PathAttachment newPathAttachment (Skin skin, String name); + public @Null PathAttachment newPathAttachment (Skin skin, String name); /** @return May be null to not load the attachment. */ - @Null - public PointAttachment newPointAttachment (Skin skin, String name); + public @Null PointAttachment newPointAttachment (Skin skin, String name); } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java index 835edd629..608ec44a1 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java @@ -123,8 +123,7 @@ abstract public class VertexAttachment extends Attachment { /** Deform keys for the deform attachment are also applied to this attachment. * @return May be null if no deform keys should be applied. */ - @Null - public VertexAttachment getDeformAttachment () { + public @Null VertexAttachment getDeformAttachment () { return deformAttachment; } @@ -136,8 +135,7 @@ abstract public class VertexAttachment extends Attachment { /** The bones which affect the {@link #getVertices()}. The array entries are, for each vertex, the number of bones affecting * the vertex followed by that many bone indices, which is the index of the bone in {@link Skeleton#getBones()}. Will be null * if this attachment has no weights. */ - @Null - public int[] getBones () { + public @Null int[] getBones () { return bones; } From a0c0db0f5a6b50bf7faa12930b39e483042ce9fa Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Fri, 24 Apr 2020 16:56:53 +0200 Subject: [PATCH 04/14] [csharp] Fixed ColorTimeline and TwoColorTimeline result colors not being clamped. Closes #1664. --- spine-csharp/src/Animation.cs | 11 +++++++++++ spine-csharp/src/Slot.cs | 13 +++++++++++++ 2 files changed, 24 insertions(+) diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs index e6d172aad..8fff49ba0 100644 --- a/spine-csharp/src/Animation.cs +++ b/spine-csharp/src/Animation.cs @@ -725,6 +725,7 @@ namespace Spine { slot.g += (slotData.g - slot.g) * alpha; slot.b += (slotData.b - slot.b) * alpha; slot.a += (slotData.a - slot.a) * alpha; + slot.ClampColor(); return; } return; @@ -758,6 +759,7 @@ namespace Spine { slot.g = g; slot.b = b; slot.a = a; + slot.ClampColor(); } else { float br, bg, bb, ba; if (blend == MixBlend.Setup) { @@ -775,6 +777,7 @@ namespace Spine { slot.g = bg + ((g - bg) * alpha); slot.b = bb + ((b - bb) * alpha); slot.a = ba + ((a - ba) * alpha); + slot.ClampColor(); } } } @@ -839,18 +842,22 @@ namespace Spine { slot.g = slotData.g; slot.b = slotData.b; slot.a = slotData.a; + slot.ClampColor(); slot.r2 = slotData.r2; slot.g2 = slotData.g2; slot.b2 = slotData.b2; + slot.ClampSecondColor(); return; case MixBlend.First: slot.r += (slot.r - slotData.r) * alpha; slot.g += (slot.g - slotData.g) * alpha; slot.b += (slot.b - slotData.b) * alpha; slot.a += (slot.a - slotData.a) * alpha; + slot.ClampColor(); slot.r2 += (slot.r2 - slotData.r2) * alpha; slot.g2 += (slot.g2 - slotData.g2) * alpha; slot.b2 += (slot.b2 - slotData.b2) * alpha; + slot.ClampSecondColor(); return; } return; @@ -893,9 +900,11 @@ namespace Spine { slot.g = g; slot.b = b; slot.a = a; + slot.ClampColor(); slot.r2 = r2; slot.g2 = g2; slot.b2 = b2; + slot.ClampSecondColor(); } else { float br, bg, bb, ba, br2, bg2, bb2; if (blend == MixBlend.Setup) { @@ -919,9 +928,11 @@ namespace Spine { slot.g = bg + ((g - bg) * alpha); slot.b = bb + ((b - bb) * alpha); slot.a = ba + ((a - ba) * alpha); + slot.ClampColor(); slot.r2 = br2 + ((r2 - br2) * alpha); slot.g2 = bg2 + ((g2 - bg2) * alpha); slot.b2 = bb2 + ((b2 - bb2) * alpha); + slot.ClampSecondColor(); } } diff --git a/spine-csharp/src/Slot.cs b/spine-csharp/src/Slot.cs index 1c1c1fb34..c7e45a886 100644 --- a/spine-csharp/src/Slot.cs +++ b/spine-csharp/src/Slot.cs @@ -106,6 +106,13 @@ namespace Spine { /// color tinting. public float A { get { return a; } set { a = value; } } + public void ClampColor() { + r = MathUtils.Clamp(r, 0, 1); + g = MathUtils.Clamp(g, 0, 1); + b = MathUtils.Clamp(b, 0, 1); + a = MathUtils.Clamp(a, 0, 1); + } + ///

The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. /// public float R2 { get { return r2; } set { r2 = value; } } @@ -118,6 +125,12 @@ namespace Spine { /// Whether R2 G2 B2 are used to tint the slot's attachment for two color tinting. False if two color tinting is not used. public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } + public void ClampSecondColor () { + r2 = MathUtils.Clamp(r2, 0, 1); + g2 = MathUtils.Clamp(g2, 0, 1); + b2 = MathUtils.Clamp(b2, 0, 1); + } + public Attachment Attachment { /// The current attachment for the slot, or null if the slot has no attachment. get { return attachment; } From 37d569414de7393c660066bad7e26fa79768c053 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Wed, 29 Apr 2020 16:08:29 +0200 Subject: [PATCH 05/14] [csharp] Minor cleanup: removed unused variable, added null tests to legacy SpriteAttacher class. --- spine-csharp/src/Animation.cs | 1 - .../Scripts/Sample Components/Legacy/SpriteAttacher.cs | 7 ++++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs index 8fff49ba0..e0107fb84 100644 --- a/spine-csharp/src/Animation.cs +++ b/spine-csharp/src/Animation.cs @@ -981,7 +981,6 @@ namespace Spine { public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, MixDirection direction) { - string attachmentName; Slot slot = skeleton.slots.Items[slotIndex]; if (!slot.bone.active) return; if (direction == MixDirection.Out) { diff --git a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs index 7334d8599..40a9856ca 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs @@ -60,6 +60,8 @@ namespace Spine.Unity.Examples { if (applyPMA) { try { + if (sprite == null) + return; sprite.texture.GetPixel(0, 0); } catch (UnityException e) { Debug.LogFormat("Texture of {0} ({1}) is not read/write enabled. SpriteAttacher requires this in order to work with a SkeletonRenderer that renders premultiplied alpha. Please check the texture settings.", sprite.name, sprite.texture.name); @@ -124,7 +126,10 @@ namespace Spine.Unity.Examples { spineSlot = spineSlot ?? skeletonComponent.Skeleton.FindSlot(slot); Shader attachmentShader = applyPMA ? Shader.Find(DefaultPMAShader) : Shader.Find(DefaultStraightAlphaShader); - attachment = applyPMA ? sprite.ToRegionAttachmentPMAClone(attachmentShader) : sprite.ToRegionAttachment(SpriteAttacher.GetPageFor(sprite.texture, attachmentShader)); + if (sprite == null) + attachment = null; + else + attachment = applyPMA ? sprite.ToRegionAttachmentPMAClone(attachmentShader) : sprite.ToRegionAttachment(SpriteAttacher.GetPageFor(sprite.texture, attachmentShader)); } } From 96bb3630f28682aced3fe7631dd699df053b1e3c Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Fri, 17 Apr 2020 19:25:55 +0200 Subject: [PATCH 06/14] [unity] Fixed a bug where _STRAIGHT_ALPHA_INPUT shader keyword was not set when default import settings are applied automatically (bool parameter was set, but the keyword would only be added when the Inspector is active). --- .../Spine/Runtime/spine-unity/Utility/MaterialChecks.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/MaterialChecks.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/MaterialChecks.cs index 3d34346d5..ce905ea55 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/MaterialChecks.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/MaterialChecks.cs @@ -38,6 +38,7 @@ namespace Spine.Unity { static readonly int STRAIGHT_ALPHA_PARAM_ID = Shader.PropertyToID("_StraightAlphaInput"); static readonly string ALPHAPREMULTIPLY_ON_KEYWORD = "_ALPHAPREMULTIPLY_ON"; + static readonly string STRAIGHT_ALPHA_KEYWORD = "_STRAIGHT_ALPHA_INPUT"; public static readonly string kPMANotSupportedLinearMessage = "Warning: Premultiply-alpha atlas textures not supported in Linear color space!\n\nPlease\n" @@ -134,6 +135,10 @@ namespace Spine.Unity { public static void EnablePMAAtMaterial (Material material, bool enablePMA) { if (material.HasProperty(STRAIGHT_ALPHA_PARAM_ID)) { material.SetInt(STRAIGHT_ALPHA_PARAM_ID, enablePMA ? 0 : 1); + if (enablePMA) + material.DisableKeyword(STRAIGHT_ALPHA_KEYWORD); + else + material.EnableKeyword(STRAIGHT_ALPHA_KEYWORD); } else { if (enablePMA) From d35550d4f42fdff3d6f913d8dd5b7ce21d970627 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Fri, 17 Apr 2020 19:31:28 +0200 Subject: [PATCH 07/14] [unity] Now supporting Unity's SpriteAtlas as atlas provider (instead of `.atlas.txt` and `.png` files) alongside a skeleton data file. Accessible via a new tool window "Window - Spine - SpriteAtlas Import". --- CHANGELOG.md | 1 + .../SpineSpriteAtlasAssetInspector.cs | 153 +++++++ .../SpineSpriteAtlasAssetInspector.cs.meta | 11 + .../Editor/Utility/AssetUtility.cs | 190 ++++++++- .../Editor/Windows/SpriteAtlasImportWindow.cs | 170 ++++++++ .../Windows/SpriteAtlasImportWindow.cs.meta | 11 + .../Asset Types/SpineSpriteAtlasAsset.cs | 394 ++++++++++++++++++ .../Asset Types/SpineSpriteAtlasAsset.cs.meta | 11 + .../spine-unity/Utility/AtlasUtilities.cs | 21 +- 9 files changed, 948 insertions(+), 14 deletions(-) create mode 100644 spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SpineSpriteAtlasAssetInspector.cs create mode 100644 spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SpineSpriteAtlasAssetInspector.cs.meta create mode 100644 spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpriteAtlasImportWindow.cs create mode 100644 spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpriteAtlasImportWindow.cs.meta create mode 100644 spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SpineSpriteAtlasAsset.cs create mode 100644 spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SpineSpriteAtlasAsset.cs.meta diff --git a/CHANGELOG.md b/CHANGELOG.md index 99250d5cc..187422cf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -227,6 +227,7 @@ 3) Copy the original material, add *_Outline* to its name and set the shader to `Universal Render Pipeline/Spine/Outline/Skeleton-OutlineOnly`. 4) Assign this *_Outline* material at the `RenderExistingMesh` component under *Replacement Materials*. * Added `Outline Shaders URP` example scene to URP extension module to demonstrate the above additions. + * Added support for Unity's [`SpriteAtlas`](https://docs.unity3d.com/Manual/class-SpriteAtlas.html) as atlas provider (as an alternative to `.atlas.txt` and `.png` files) alongside a skeleton data file. There is now an additional `Spine SpriteAtlas Import` tool window accessible via `Window - Spine - SpriteAtlas Import`. Additional information can be found in a new section on the [spine-unity documentation page](http://esotericsoftware.com/spine-unity#Advanced---Using-Unity-SpriteAtlas-as-Atlas-Provider). * **Changes of default values** * `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`. diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SpineSpriteAtlasAssetInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SpineSpriteAtlasAssetInspector.cs new file mode 100644 index 000000000..14451ee0b --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SpineSpriteAtlasAssetInspector.cs @@ -0,0 +1,153 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, 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. + *****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEditor; +using UnityEngine; +using Spine; + +namespace Spine.Unity.Editor { + using Event = UnityEngine.Event; + + [CustomEditor(typeof(SpineSpriteAtlasAsset)), CanEditMultipleObjects] + public class SpineSpriteAtlasAssetInspector : UnityEditor.Editor { + SerializedProperty atlasFile, materials; + SpineSpriteAtlasAsset atlasAsset; + + static List GetRegions (Atlas atlas) { + FieldInfo regionsField = SpineInspectorUtility.GetNonPublicField(typeof(Atlas), "regions"); + return (List)regionsField.GetValue(atlas); + } + + void OnEnable () { + SpineEditorUtilities.ConfirmInitialization(); + atlasFile = serializedObject.FindProperty("spriteAtlasFile"); + materials = serializedObject.FindProperty("materials"); + materials.isExpanded = true; + atlasAsset = (SpineSpriteAtlasAsset)target; + + if (!SpineSpriteAtlasAsset.AnySpriteAtlasNeedsRegionsLoaded()) + return; + EditorApplication.update -= SpineSpriteAtlasAsset.UpdateWhenEditorPlayModeStarted; + EditorApplication.update += SpineSpriteAtlasAsset.UpdateWhenEditorPlayModeStarted; + } + + void OnDisable () { + EditorApplication.update -= SpineSpriteAtlasAsset.UpdateWhenEditorPlayModeStarted; + } + + override public void OnInspectorGUI () { + if (serializedObject.isEditingMultipleObjects) { + DrawDefaultInspector(); + return; + } + + serializedObject.Update(); + atlasAsset = atlasAsset ?? (SpineSpriteAtlasAsset)target; + + if (atlasAsset.RegionsNeedLoading) { + if (GUILayout.Button(SpineInspectorUtility.TempContent("Load regions by entering Play mode"), GUILayout.Height(20))) { + EditorApplication.isPlaying = true; + } + } + + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(atlasFile); + EditorGUILayout.PropertyField(materials, true); + if (EditorGUI.EndChangeCheck()) { + serializedObject.ApplyModifiedProperties(); + atlasAsset.Clear(); + atlasAsset.GetAtlas(); + atlasAsset.updateRegionsInPlayMode = true; + } + + if (materials.arraySize == 0) { + EditorGUILayout.HelpBox("No materials", MessageType.Error); + return; + } + + for (int i = 0; i < materials.arraySize; i++) { + SerializedProperty prop = materials.GetArrayElementAtIndex(i); + var material = (Material)prop.objectReferenceValue; + if (material == null) { + EditorGUILayout.HelpBox("Materials cannot be null.", MessageType.Error); + return; + } + } + + if (atlasFile.objectReferenceValue != null) { + int baseIndent = EditorGUI.indentLevel; + + var regions = SpineSpriteAtlasAssetInspector.GetRegions(atlasAsset.GetAtlas()); + int regionsCount = regions.Count; + using (new EditorGUILayout.HorizontalScope()) { + EditorGUILayout.LabelField("Atlas Regions", EditorStyles.boldLabel); + EditorGUILayout.LabelField(string.Format("{0} regions total", regionsCount)); + } + AtlasPage lastPage = null; + for (int i = 0; i < regionsCount; i++) { + if (lastPage != regions[i].page) { + if (lastPage != null) { + EditorGUILayout.Separator(); + EditorGUILayout.Separator(); + } + lastPage = regions[i].page; + Material mat = ((Material)lastPage.rendererObject); + if (mat != null) { + EditorGUI.indentLevel = baseIndent; + using (new GUILayout.HorizontalScope()) + using (new EditorGUI.DisabledGroupScope(true)) + EditorGUILayout.ObjectField(mat, typeof(Material), false, GUILayout.Width(250)); + EditorGUI.indentLevel = baseIndent + 1; + } else { + EditorGUILayout.HelpBox("Page missing material!", MessageType.Warning); + } + } + + string regionName = regions[i].name; + Texture2D icon = SpineEditorUtilities.Icons.image; + if (regionName.EndsWith(" ")) { + regionName = string.Format("'{0}'", regions[i].name); + icon = SpineEditorUtilities.Icons.warning; + EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(regionName, icon, "Region name ends with whitespace. This may cause errors. Please check your source image filenames.")); + } else { + EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(regionName, icon)); + } + } + EditorGUI.indentLevel = baseIndent; + } + + if (serializedObject.ApplyModifiedProperties() || SpineInspectorUtility.UndoRedoPerformed(Event.current)) + atlasAsset.Clear(); + } + } + +} diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SpineSpriteAtlasAssetInspector.cs.meta b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SpineSpriteAtlasAssetInspector.cs.meta new file mode 100644 index 000000000..238789e90 --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SpineSpriteAtlasAssetInspector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f063dc5ff6881db4a9ee2e059812cba2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs index a32a5d4b7..d95dfb0f2 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs @@ -43,6 +43,10 @@ #define NEW_PREFERENCES_SETTINGS_PROVIDER #endif +#if UNITY_2018_2_OR_NEWER +#define EXPOSES_SPRITE_ATLAS_UTILITIES +#endif + using UnityEngine; using UnityEditor; using System.Collections.Generic; @@ -60,6 +64,7 @@ namespace Spine.Unity.Editor { public const string SkeletonDataSuffix = "_SkeletonData"; public const string AtlasSuffix = "_Atlas"; + public const string SpriteAtlasSuffix = "_SpriteAtlas"; /// HACK: This list keeps the asset reference temporarily during importing. /// @@ -251,6 +256,7 @@ namespace Spine.Unity.Editor { bool reimport = false) { var atlasPaths = new List(); + var spriteAtlasPaths = new List(); var imagePaths = new List(); var skeletonPaths = new List(); CompatibilityProblemInfo compatibilityProblemInfo = null; @@ -267,6 +273,9 @@ namespace Spine.Unity.Editor { if (str.EndsWith(".atlas.txt", System.StringComparison.Ordinal)) atlasPaths.Add(str); break; + case ".spriteatlas": + spriteAtlasPaths.Add(str); + break; case ".png": case ".jpg": imagePaths.Add(str); @@ -294,7 +303,6 @@ namespace Spine.Unity.Editor { AtlasAssetBase atlas = IngestSpineAtlas(atlasText, texturesWithoutMetaFile); atlases.Add(atlas); } - AddDependentSkeletonIfAtlasChanged(skeletonPaths, atlasPaths); // Import skeletons and match them with atlases. @@ -575,6 +583,164 @@ namespace Spine.Unity.Editor { return (AtlasAssetBase)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(AtlasAssetBase)); } + public static bool SpriteAtlasSettingsNeedAdjustment (UnityEngine.U2D.SpriteAtlas spriteAtlas) { + #if EXPOSES_SPRITE_ATLAS_UTILITIES + UnityEditor.U2D.SpriteAtlasPackingSettings packingSettings = UnityEditor.U2D.SpriteAtlasExtensions.GetPackingSettings(spriteAtlas); + UnityEditor.U2D.SpriteAtlasTextureSettings textureSettings = UnityEditor.U2D.SpriteAtlasExtensions.GetTextureSettings(spriteAtlas); + + bool areSettingsAsDesired = + packingSettings.enableRotation == true && + packingSettings.enableTightPacking == false && + textureSettings.readable == true && + textureSettings.generateMipMaps == false; + // note: platformSettings.textureCompression is always providing "Compressed", so we have to skip it. + return !areSettingsAsDesired; + #else + return false; + #endif + } + + public static bool AdjustSpriteAtlasSettings (UnityEngine.U2D.SpriteAtlas spriteAtlas) { + #if EXPOSES_SPRITE_ATLAS_UTILITIES + UnityEditor.U2D.SpriteAtlasPackingSettings packingSettings = UnityEditor.U2D.SpriteAtlasExtensions.GetPackingSettings(spriteAtlas); + UnityEditor.U2D.SpriteAtlasTextureSettings textureSettings = UnityEditor.U2D.SpriteAtlasExtensions.GetTextureSettings(spriteAtlas); + + packingSettings.enableRotation = true; + packingSettings.enableTightPacking = false; + UnityEditor.U2D.SpriteAtlasExtensions.SetPackingSettings(spriteAtlas, packingSettings); + + textureSettings.readable = true; + textureSettings.generateMipMaps = false; + UnityEditor.U2D.SpriteAtlasExtensions.SetTextureSettings(spriteAtlas, textureSettings); + + TextureImporterPlatformSettings platformSettings = new TextureImporterPlatformSettings(); + platformSettings.textureCompression = TextureImporterCompression.Uncompressed; + platformSettings.crunchedCompression = false; + UnityEditor.U2D.SpriteAtlasExtensions.SetPlatformSettings(spriteAtlas, platformSettings); + + string atlasPath = AssetDatabase.GetAssetPath(spriteAtlas); + Debug.Log(string.Format("Adjusted unsuitable SpriteAtlas settings '{0}'", atlasPath), spriteAtlas); + return false; + #else + return true; + #endif + } + + public static bool GeneratePngFromSpriteAtlas (UnityEngine.U2D.SpriteAtlas spriteAtlas, out string texturePath) { + texturePath = System.IO.Path.ChangeExtension(AssetDatabase.GetAssetPath(spriteAtlas), ".png"); + if (spriteAtlas == null) + return false; + + Texture2D tempTexture = SpineSpriteAtlasAsset.AccessPackedTextureEditor(spriteAtlas); + if (tempTexture == null) + return false; + + byte[] bytes = null; + try { + bytes = tempTexture.EncodeToPNG(); + } + catch (System.Exception) { + // handled below + } + if (bytes == null || bytes.Length == 0) { + Debug.LogError("Could not read Compressed SpriteAtlas. Please enable 'Read/Write Enabled' and ensure 'Compression' is set to 'None' in Inspector.", spriteAtlas); + return false; + } + System.IO.File.WriteAllBytes(texturePath, bytes); + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + return System.IO.File.Exists(texturePath); + } + + public static AtlasAssetBase IngestSpriteAtlas (UnityEngine.U2D.SpriteAtlas spriteAtlas, List texturesWithoutMetaFile) { + if (spriteAtlas == null) { + Debug.LogWarning("SpriteAtlas source cannot be null!"); + return null; + } + + if (SpriteAtlasSettingsNeedAdjustment(spriteAtlas)) { + // settings need to be adjusted via the 'Spine SpriteAtlas Import' window if you want to use it as a Spine atlas. + return null; + } + + Texture2D texture = null; + { // only one page file + string texturePath; + GeneratePngFromSpriteAtlas(spriteAtlas, out texturePath); + texture = AssetDatabase.LoadAssetAtPath(texturePath); + if (texture == null && System.IO.File.Exists(texturePath)) { + EditorUtility.SetDirty(spriteAtlas); + return null; // next iteration will load the texture as well. + } + } + + string primaryName = spriteAtlas.name; + string assetPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(spriteAtlas)).Replace('\\', '/'); + + string atlasPath = assetPath + "/" + primaryName + SpriteAtlasSuffix + ".asset"; + + SpineSpriteAtlasAsset atlasAsset = AssetDatabase.LoadAssetAtPath(atlasPath); + + List vestigialMaterials = new List(); + + if (atlasAsset == null) + atlasAsset = SpineSpriteAtlasAsset.CreateInstance(); + else { + foreach (Material m in atlasAsset.materials) + vestigialMaterials.Add(m); + } + + protectFromStackGarbageCollection.Add(atlasAsset); + atlasAsset.spriteAtlasFile = spriteAtlas; + + int pagesCount = 1; + var populatingMaterials = new List(pagesCount); + + { + string pageName = "SpriteAtlas"; + + string materialPath = assetPath + "/" + primaryName + "_" + pageName + ".mat"; + Material mat = AssetDatabase.LoadAssetAtPath(materialPath); + + if (mat == null) { + mat = new Material(Shader.Find(SpineEditorUtilities.Preferences.defaultShader)); + ApplyPMAOrStraightAlphaSettings(mat, SpineEditorUtilities.Preferences.textureSettingsReference); + AssetDatabase.CreateAsset(mat, materialPath); + } + else { + vestigialMaterials.Remove(mat); + } + + if (texture != null) + mat.mainTexture = texture; + + EditorUtility.SetDirty(mat); + // note: don't call AssetDatabase.SaveAssets() since this would trigger OnPostprocessAllAssets() every time unnecessarily. + populatingMaterials.Add(mat); //atlasAsset.materials[i] = mat; + } + + atlasAsset.materials = populatingMaterials.ToArray(); + + for (int i = 0; i < vestigialMaterials.Count; i++) + AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(vestigialMaterials[i])); + + if (AssetDatabase.GetAssetPath(atlasAsset) == "") + AssetDatabase.CreateAsset(atlasAsset, atlasPath); + else + atlasAsset.Clear(); + + atlasAsset.GetAtlas(); + atlasAsset.updateRegionsInPlayMode = true; + + EditorUtility.SetDirty(atlasAsset); + AssetDatabase.SaveAssets(); + + Debug.Log(string.Format("{0} :: Imported with {1} material", atlasAsset.name, atlasAsset.materials.Length), atlasAsset); + + protectFromStackGarbageCollection.Remove(atlasAsset); + return (AtlasAssetBase)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(AtlasAssetBase)); + } + static bool SetDefaultTextureSettings (string texturePath, SpineAtlasAsset atlasAsset) { TextureImporter texImporter = (TextureImporter)TextureImporter.GetAtPath(texturePath); if (texImporter == null) { @@ -595,7 +761,7 @@ namespace Spine.Unity.Editor { return true; } - #if NEW_PREFERENCES_SETTINGS_PROVIDER +#if NEW_PREFERENCES_SETTINGS_PROVIDER static bool SetReferenceTextureSettings (string texturePath, SpineAtlasAsset atlasAsset, string referenceAssetPath) { var texturePreset = AssetDatabase.LoadAssetAtPath(referenceAssetPath); bool isTexturePreset = texturePreset != null && texturePreset.GetTargetTypeName() == "TextureImporter"; @@ -613,7 +779,7 @@ namespace Spine.Unity.Editor { AssetDatabase.SaveAssets(); return true; } - #else +#else static bool SetReferenceTextureSettings (string texturePath, SpineAtlasAsset atlasAsset, string referenceAssetPath) { TextureImporter reference = TextureImporter.GetAtPath(referenceAssetPath) as TextureImporter; if (reference == null) @@ -642,7 +808,7 @@ namespace Spine.Unity.Editor { AssetDatabase.SaveAssets(); return true; } - #endif +#endif static void ApplyPMAOrStraightAlphaSettings (Material material, string referenceTextureSettings) { bool isUsingPMAWorkflow = string.IsNullOrEmpty(referenceTextureSettings) || @@ -650,9 +816,9 @@ namespace Spine.Unity.Editor { MaterialChecks.EnablePMAAtMaterial(material, isUsingPMAWorkflow); } - #endregion +#endregion - #region Import SkeletonData (json or binary) +#region Import SkeletonData (json or binary) internal static string GetSkeletonDataAssetFilePath(TextAsset spineJson) { string primaryName = Path.GetFileNameWithoutExtension(spineJson.name); string assetPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(spineJson)).Replace('\\', '/'); @@ -996,7 +1162,7 @@ namespace Spine.Unity.Editor { return null; } - string spineGameObjectName = string.Format("Spine GameObject ({0})", skeletonDataAsset.name.Replace("_SkeletonData", "")); + string spineGameObjectName = string.Format("Spine GameObject ({0})", skeletonDataAsset.name.Replace(AssetUtility.SkeletonDataSuffix, "")); GameObject go = EditorInstantiation.NewGameObject(spineGameObjectName, useObjectFactory, typeof(MeshFilter), typeof(MeshRenderer), typeof(SkeletonAnimation)); SkeletonAnimation newSkeletonAnimation = go.GetComponent(); @@ -1025,19 +1191,19 @@ namespace Spine.Unity.Editor { /// Handles creating a new GameObject in the Unity Editor. This uses the new ObjectFactory API where applicable. public static GameObject NewGameObject (string name, bool useObjectFactory) { - #if NEW_PREFAB_SYSTEM +#if NEW_PREFAB_SYSTEM if (useObjectFactory) return ObjectFactory.CreateGameObject(name); - #endif +#endif return new GameObject(name); } /// Handles creating a new GameObject in the Unity Editor. This uses the new ObjectFactory API where applicable. public static GameObject NewGameObject (string name, bool useObjectFactory, params System.Type[] components) { - #if NEW_PREFAB_SYSTEM +#if NEW_PREFAB_SYSTEM if (useObjectFactory) return ObjectFactory.CreateGameObject(name, components); - #endif +#endif return new GameObject(name, components); } @@ -1075,7 +1241,7 @@ namespace Spine.Unity.Editor { return null; } - string spineGameObjectName = string.Format("Spine Mecanim GameObject ({0})", skeletonDataAsset.name.Replace("_SkeletonData", "")); + string spineGameObjectName = string.Format("Spine Mecanim GameObject ({0})", skeletonDataAsset.name.Replace(AssetUtility.SkeletonDataSuffix, "")); GameObject go = EditorInstantiation.NewGameObject(spineGameObjectName, useObjectFactory, typeof(MeshFilter), typeof(MeshRenderer), typeof(Animator), typeof(SkeletonMecanim)); diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpriteAtlasImportWindow.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpriteAtlasImportWindow.cs new file mode 100644 index 000000000..3e6d86620 --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpriteAtlasImportWindow.cs @@ -0,0 +1,170 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, 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. + *****************************************************************************/ + +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +namespace Spine.Unity.Editor { + + using Editor = UnityEditor.Editor; + using Icons = SpineEditorUtilities.Icons; + + public class SpriteAtlasImportWindow : EditorWindow { + const bool IsUtilityWindow = false; + + [MenuItem("Window/Spine/SpriteAtlas Import", false, 5000)] + public static void Init (MenuCommand command) { + var window = EditorWindow.GetWindow(IsUtilityWindow); + window.minSize = new Vector2(284f, 256f); + window.maxSize = new Vector2(500f, 256f); + window.titleContent = new GUIContent("Spine SpriteAtlas Import", Icons.spine); + window.Show(); + } + + public UnityEngine.U2D.SpriteAtlas spriteAtlasAsset; + public TextAsset skeletonDataFile; + public SpineSpriteAtlasAsset spineSpriteAtlasAsset; + + SerializedObject so; + + void OnEnable () { + if (!SpineSpriteAtlasAsset.AnySpriteAtlasNeedsRegionsLoaded()) + return; + EditorApplication.update -= SpineSpriteAtlasAsset.UpdateWhenEditorPlayModeStarted; + EditorApplication.update += SpineSpriteAtlasAsset.UpdateWhenEditorPlayModeStarted; + } + + void OnDisable () { + EditorApplication.update -= SpineSpriteAtlasAsset.UpdateWhenEditorPlayModeStarted; + } + + void OnGUI () { + so = so ?? new SerializedObject(this); + + EditorGUIUtility.wideMode = true; + EditorGUILayout.LabelField("Spine SpriteAtlas Import", EditorStyles.boldLabel); + + using (new SpineInspectorUtility.BoxScope()) { + EditorGUI.BeginChangeCheck(); + var spriteAtlasAssetProperty = so.FindProperty("spriteAtlasAsset"); + EditorGUILayout.PropertyField(spriteAtlasAssetProperty, new GUIContent("SpriteAtlas", EditorGUIUtility.IconContent("SpriteAtlas Icon").image)); + if (EditorGUI.EndChangeCheck()) { + so.ApplyModifiedProperties(); + if (spriteAtlasAsset != null) { + if (AssetUtility.SpriteAtlasSettingsNeedAdjustment(spriteAtlasAsset)) { + AssetUtility.AdjustSpriteAtlasSettings(spriteAtlasAsset); + } + GenerateAssetsFromSpriteAtlas(spriteAtlasAsset); + } + } + + var spineSpriteAtlasAssetProperty = so.FindProperty("spineSpriteAtlasAsset"); + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(spineSpriteAtlasAssetProperty, new GUIContent("SpineSpriteAtlasAsset", EditorGUIUtility.IconContent("ScriptableObject Icon").image)); + if (spineSpriteAtlasAssetProperty.objectReferenceValue == null) { + spineSpriteAtlasAssetProperty.objectReferenceValue = spineSpriteAtlasAsset = FindSpineSpriteAtlasAsset(spriteAtlasAsset); + } + if (EditorGUI.EndChangeCheck()) { + so.ApplyModifiedProperties(); + } + EditorGUILayout.Space(); + + using (new EditorGUI.DisabledScope(spineSpriteAtlasAsset == null)) { + if (SpineInspectorUtility.LargeCenteredButton(new GUIContent("Load regions by entering Play mode"))) { + GenerateAssetsFromSpriteAtlas(spriteAtlasAsset); + SpineSpriteAtlasAsset.UpdateByStartingEditorPlayMode(); + } + } + + using (new SpineInspectorUtility.BoxScope()) { + if (spriteAtlasAsset == null) { + EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Please assign SpriteAtlas file.", Icons.warning), GUILayout.Height(46)); + } + else if (spineSpriteAtlasAsset == null || spineSpriteAtlasAsset.RegionsNeedLoading) { + EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Please hit 'Load regions ..' to load\nregion info. Play mode is started\nand stopped automatically.", Icons.warning), GUILayout.Height(54)); + } + else { + EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("SpriteAtlas imported\nsuccessfully.", Icons.spine), GUILayout.Height(46)); + } + } + } + + bool isAtlasComplete = (spineSpriteAtlasAsset != null && !spineSpriteAtlasAsset.RegionsNeedLoading); + bool canImportSkeleton = (spriteAtlasAsset != null && skeletonDataFile != null); + using (new SpineInspectorUtility.BoxScope()) { + + using (new EditorGUI.DisabledScope(!isAtlasComplete)) { + var skeletonDataAssetProperty = so.FindProperty("skeletonDataFile"); + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(skeletonDataAssetProperty, SpineInspectorUtility.TempContent("Skeleton json/skel file", Icons.spine)); + if (EditorGUI.EndChangeCheck()) { + so.ApplyModifiedProperties(); + } + EditorGUILayout.Space(); + } + using (new EditorGUI.DisabledScope(!canImportSkeleton)) { + if (SpineInspectorUtility.LargeCenteredButton(new GUIContent("Import Skeleton"))) { + //AssetUtility.IngestSpriteAtlas(spriteAtlasAsset, null); + string skeletonPath = AssetDatabase.GetAssetPath(skeletonDataFile); + string[] skeletons = new string[] { skeletonPath }; + AssetUtility.ImportSpineContent(skeletons, null); + } + } + } + } + + void GenerateAssetsFromSpriteAtlas (UnityEngine.U2D.SpriteAtlas spriteAtlasAsset) { + AssetUtility.IngestSpriteAtlas(spriteAtlasAsset, null); + string texturePath; + if (AssetUtility.GeneratePngFromSpriteAtlas(spriteAtlasAsset, out texturePath)) { + Debug.Log(string.Format("Generated SpriteAtlas texture '{0}'", texturePath), spriteAtlasAsset); + } + } + + SpineSpriteAtlasAsset FindSpineSpriteAtlasAsset (UnityEngine.U2D.SpriteAtlas spriteAtlasAsset) { + string path = AssetDatabase.GetAssetPath(spriteAtlasAsset).Replace(".spriteatlas", AssetUtility.SpriteAtlasSuffix + ".asset"); + if (System.IO.File.Exists(path)) { + return AssetDatabase.LoadAssetAtPath(path); + } + return null; + } + + SkeletonDataAsset FindSkeletonDataAsset (TextAsset skeletonDataFile) { + string path = AssetDatabase.GetAssetPath(skeletonDataFile); + path = path.Replace(".json", AssetUtility.SkeletonDataSuffix + ".asset"); + path = path.Replace(".skel.bytes", AssetUtility.SkeletonDataSuffix + ".asset"); + if (System.IO.File.Exists(path)) { + return AssetDatabase.LoadAssetAtPath(path); + } + return null; + } + } +} diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpriteAtlasImportWindow.cs.meta b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpriteAtlasImportWindow.cs.meta new file mode 100644 index 000000000..2eb64bdcf --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpriteAtlasImportWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a5b99b091defeef439a0cb8c99fd8a51 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SpineSpriteAtlasAsset.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SpineSpriteAtlasAsset.cs new file mode 100644 index 000000000..71c057ff7 --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SpineSpriteAtlasAsset.cs @@ -0,0 +1,394 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, 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. + *****************************************************************************/ + +#if UNITY_2018_2_OR_NEWER +#define EXPOSES_SPRITE_ATLAS_UTILITIES +#endif + +using System; +using System.Collections.Generic; +using System.IO; +using UnityEngine; +using Spine; +using UnityEngine.U2D; + +#if UNITY_EDITOR +using UnityEditor; +using System.Reflection; +#endif + +namespace Spine.Unity { + /// Loads and stores a Spine atlas and list of materials. + [CreateAssetMenu(fileName = "New Spine SpriteAtlas Asset", menuName = "Spine/Spine SpriteAtlas Asset")] + public class SpineSpriteAtlasAsset : AtlasAssetBase { + public SpriteAtlas spriteAtlasFile; + public Material[] materials; + protected Atlas atlas; + public bool updateRegionsInPlayMode; + + [System.Serializable] + protected class SavedRegionInfo { + public float x, y, width, height; + public SpritePackingRotation packingRotation; + } + [SerializeField] protected SavedRegionInfo[] savedRegions; + + public override bool IsLoaded { get { return this.atlas != null; } } + + public override IEnumerable Materials { get { return materials; } } + public override int MaterialCount { get { return materials == null ? 0 : materials.Length; } } + public override Material PrimaryMaterial { get { return materials[0]; } } + + #if UNITY_EDITOR + static MethodInfo GetPackedSpritesMethod, GetPreviewTexturesMethod; + #if !EXPOSES_SPRITE_ATLAS_UTILITIES + static MethodInfo PackAtlasesMethod; + #endif + #endif + + #region Runtime Instantiation + /// + /// Creates a runtime AtlasAsset + public static SpineSpriteAtlasAsset CreateRuntimeInstance (SpriteAtlas spriteAtlasFile, Material[] materials, bool initialize) { + SpineSpriteAtlasAsset atlasAsset = ScriptableObject.CreateInstance(); + atlasAsset.Reset(); + atlasAsset.spriteAtlasFile = spriteAtlasFile; + atlasAsset.materials = materials; + + if (initialize) + atlasAsset.GetAtlas(); + + return atlasAsset; + } + #endregion + + void Reset () { + Clear(); + } + + public override void Clear () { + atlas = null; + } + + /// The atlas or null if it could not be loaded. + public override Atlas GetAtlas () { + if (spriteAtlasFile == null) { + Debug.LogError("SpriteAtlas file not set for SpineSpriteAtlasAsset: " + name, this); + Clear(); + return null; + } + + if (materials == null || materials.Length == 0) { + Debug.LogError("Materials not set for SpineSpriteAtlasAsset: " + name, this); + Clear(); + return null; + } + + if (atlas != null) return atlas; + + try { + atlas = LoadAtlas(spriteAtlasFile); + return atlas; + } catch (Exception ex) { + Debug.LogError("Error analyzing SpriteAtlas for SpineSpriteAtlasAsset: " + name + "\n" + ex.Message + "\n" + ex.StackTrace, this); + return null; + } + } + + protected void AssignRegionsFromSavedRegions (Sprite[] sprites, Atlas usedAtlas) { + + if (savedRegions == null || savedRegions.Length != sprites.Length) + return; + + int i = 0; + foreach (var region in usedAtlas) { + var savedRegion = savedRegions[i]; + var page = region.page; + + region.degrees = savedRegion.packingRotation == SpritePackingRotation.None ? 0 : 90; + region.rotate = region.degrees != 0; + + float x = savedRegion.x; + float y = savedRegion.y; + float width = savedRegion.width; + float height = savedRegion.height; + + region.u = x / (float)page.width; + region.v = y / (float)page.height; + if (region.rotate) { + region.u2 = (x + height) / (float)page.width; + region.v2 = (y + width) / (float)page.height; + } + else { + region.u2 = (x + width) / (float)page.width; + region.v2 = (y + height) / (float)page.height; + } + region.x = (int)x; + region.y = (int)y; + region.width = Math.Abs((int)width); + region.height = Math.Abs((int)height); + + // flip upside down + var temp = region.v; + region.v = region.v2; + region.v2 = temp; + + region.originalWidth = (int)width; + region.originalHeight = (int)height; + + // note: currently sprite pivot offsets are ignored. + // var sprite = sprites[i]; + region.offsetX = 0;//sprite.pivot.x; + region.offsetY = 0;//sprite.pivot.y; + + ++i; + } + } + + private Atlas LoadAtlas (UnityEngine.U2D.SpriteAtlas spriteAtlas) { + + List pages = new List(); + List regions = new List(); + + Sprite[] sprites = new UnityEngine.Sprite[spriteAtlas.spriteCount]; + spriteAtlas.GetSprites(sprites); + if (sprites.Length == 0) + return new Atlas(pages, regions); + + Texture2D texture = null; + #if UNITY_EDITOR + if (!Application.isPlaying) + texture = AccessPackedTextureEditor(spriteAtlas); + else + #endif + texture = AccessPackedTexture(sprites); + + Material material = materials[0]; + + Spine.AtlasPage page = new AtlasPage(); + page.name = spriteAtlas.name; + page.width = texture.width; + page.height = texture.height; + page.format = Spine.Format.RGBA8888; + + page.minFilter = TextureFilter.Linear; + page.magFilter = TextureFilter.Linear; + page.uWrap = TextureWrap.ClampToEdge; + page.vWrap = TextureWrap.ClampToEdge; + page.rendererObject = material; + pages.Add(page); + + sprites = AccessPackedSprites(spriteAtlas); + + int i = 0; + for ( ; i < sprites.Length; ++i) { + var sprite = sprites[i]; + AtlasRegion region = new AtlasRegion(); + region.name = sprite.name.Replace("(Clone)", ""); + region.page = page; + region.degrees = sprite.packingRotation == SpritePackingRotation.None ? 0 : 90; + region.rotate = region.degrees != 0; + + region.u2 = 1; + region.v2 = 1; + region.width = page.width; + region.height = page.height; + region.originalWidth = page.width; + region.originalHeight = page.height; + + region.index = i; + regions.Add(region); + } + + var atlas = new Atlas(pages, regions); + AssignRegionsFromSavedRegions(sprites, atlas); + + return atlas; + } + +#if UNITY_EDITOR + public static void UpdateByStartingEditorPlayMode () { + EditorApplication.isPlaying = true; + } + + public static bool AnySpriteAtlasNeedsRegionsLoaded () { + string[] guids = UnityEditor.AssetDatabase.FindAssets("t:SpineSpriteAtlasAsset"); + foreach (var guid in guids) { + string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid); + if (!string.IsNullOrEmpty(path)) { + var atlasAsset = UnityEditor.AssetDatabase.LoadAssetAtPath(path); + if (atlasAsset) { + if (atlasAsset.RegionsNeedLoading) + return true; + } + } + } + return false; + } + + public static void UpdateWhenEditorPlayModeStarted () { + if (!EditorApplication.isPlaying) + return; + + EditorApplication.update -= UpdateWhenEditorPlayModeStarted; + string[] guids = UnityEditor.AssetDatabase.FindAssets("t:SpineSpriteAtlasAsset"); + if (guids.Length == 0) + return; + + Debug.Log("Updating SpineSpriteAtlasAssets"); + foreach (var guid in guids) { + string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid); + if (!string.IsNullOrEmpty(path)) { + var atlasAsset = UnityEditor.AssetDatabase.LoadAssetAtPath(path); + if (atlasAsset) { + atlasAsset.atlas = atlasAsset.LoadAtlas(atlasAsset.spriteAtlasFile); + atlasAsset.LoadRegionsInEditorPlayMode(); + Debug.Log(string.Format("Updated regions of '{0}'", atlasAsset.name), atlasAsset); + } + } + } + + EditorApplication.isPlaying = false; + } + + public bool RegionsNeedLoading { + get { return savedRegions == null || savedRegions.Length == 0 || updateRegionsInPlayMode; } + } + + public void LoadRegionsInEditorPlayMode () { + + Sprite[] sprites = null; + System.Type T = Type.GetType("UnityEditor.U2D.SpriteAtlasExtensions,UnityEditor"); + var method = T.GetMethod("GetPackedSprites", BindingFlags.NonPublic | BindingFlags.Static); + if (method != null) { + object retval = method.Invoke(null, new object[] { spriteAtlasFile }); + var spritesArray = retval as Sprite[]; + if (spritesArray != null && spritesArray.Length > 0) { + sprites = spritesArray; + } + } + if (sprites == null) { + sprites = new UnityEngine.Sprite[spriteAtlasFile.spriteCount]; + spriteAtlasFile.GetSprites(sprites); + } + if (sprites.Length == 0) { + Debug.LogWarning(string.Format("SpriteAtlas '{0}' contains no sprites. Please make sure all assigned images are set to import type 'Sprite'.", spriteAtlasFile.name), spriteAtlasFile); + return; + } + else if (sprites[0].packingMode == SpritePackingMode.Tight) { + Debug.LogError(string.Format("SpriteAtlas '{0}': Tight packing is not supported. Please disable 'Tight Packing' in the SpriteAtlas Inspector.", spriteAtlasFile.name), spriteAtlasFile); + return; + } + + if (savedRegions == null || savedRegions.Length != sprites.Length) + savedRegions = new SavedRegionInfo[sprites.Length]; + + int i = 0; + foreach (var region in atlas) { + var sprite = sprites[i]; + var rect = sprite.textureRect; + float x = rect.min.x; + float y = rect.min.y; + float width = rect.width; + float height = rect.height; + + var savedRegion = new SavedRegionInfo(); + savedRegion.x = x; + savedRegion.y = y; + savedRegion.width = width; + savedRegion.height = height; + savedRegion.packingRotation = sprite.packingRotation; + savedRegions[i] = savedRegion; + + ++i; + } + updateRegionsInPlayMode = false; + AssignRegionsFromSavedRegions(sprites, atlas); + EditorUtility.SetDirty(this); + AssetDatabase.SaveAssets(); + } + + public static Texture2D AccessPackedTextureEditor (SpriteAtlas spriteAtlas) { + #if EXPOSES_SPRITE_ATLAS_UTILITIES + UnityEditor.U2D.SpriteAtlasUtility.PackAtlases(new SpriteAtlas[] { spriteAtlas }, EditorUserBuildSettings.activeBuildTarget); + #else + /*if (PackAtlasesMethod == null) { + System.Type T = Type.GetType("UnityEditor.U2D.SpriteAtlasUtility,UnityEditor"); + PackAtlasesMethod = T.GetMethod("PackAtlases", BindingFlags.NonPublic | BindingFlags.Static); + } + if (PackAtlasesMethod != null) { + PackAtlasesMethod.Invoke(null, new object[] { new SpriteAtlas[] { spriteAtlas }, EditorUserBuildSettings.activeBuildTarget }); + }*/ + #endif + if (GetPreviewTexturesMethod == null) { + System.Type T = Type.GetType("UnityEditor.U2D.SpriteAtlasExtensions,UnityEditor"); + GetPreviewTexturesMethod = T.GetMethod("GetPreviewTextures", BindingFlags.NonPublic | BindingFlags.Static); + } + if (GetPreviewTexturesMethod != null) { + object retval = GetPreviewTexturesMethod.Invoke(null, new object[] { spriteAtlas }); + var textures = retval as Texture2D[]; + if (textures.Length > 0) + return textures[0]; + } + return null; + } +#endif + public static Texture2D AccessPackedTexture (Sprite[] sprites) { + return sprites[0].texture; + } + + + public static Sprite[] AccessPackedSprites (UnityEngine.U2D.SpriteAtlas spriteAtlas) { + Sprite[] sprites = null; +#if UNITY_EDITOR + if (!Application.isPlaying) { + + if (GetPackedSpritesMethod == null) { + System.Type T = Type.GetType("UnityEditor.U2D.SpriteAtlasExtensions,UnityEditor"); + GetPackedSpritesMethod = T.GetMethod("GetPackedSprites", BindingFlags.NonPublic | BindingFlags.Static); + } + if (GetPackedSpritesMethod != null) { + object retval = GetPackedSpritesMethod.Invoke(null, new object[] { spriteAtlas }); + var spritesArray = retval as Sprite[]; + if (spritesArray != null && spritesArray.Length > 0) { + sprites = spritesArray; + } + } + } +#endif + if (sprites == null) { + sprites = new UnityEngine.Sprite[spriteAtlas.spriteCount]; + spriteAtlas.GetSprites(sprites); + if (sprites.Length == 0) + return null; + } + return sprites; + } + } +} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SpineSpriteAtlasAsset.cs.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SpineSpriteAtlasAsset.cs.meta new file mode 100644 index 000000000..e61e24e07 --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SpineSpriteAtlasAsset.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ce59897dd7e6cbc4690a05ebaf975dff +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs index ad7b2e42f..d65480ff9 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs @@ -29,7 +29,7 @@ using UnityEngine; using System.Collections.Generic; -using System.Collections; +using System; namespace Spine.Unity.AttachmentTools { @@ -531,7 +531,24 @@ namespace Spine.Unity.AttachmentTools { bool mipmaps = UseMipMaps, bool linear = false, bool applyPMA = false) { var spriteTexture = s.texture; - var r = s.textureRect; + Rect r; + if (!s.packed || s.packingMode == SpritePackingMode.Rectangle) { + r = s.textureRect; + } + else { + r = new Rect(); + r.xMin = Math.Min(s.uv[0].x, s.uv[1].x) * spriteTexture.width; + r.xMax = Math.Max(s.uv[0].x, s.uv[1].x) * spriteTexture.width; + r.yMin = Math.Min(s.uv[0].y, s.uv[2].y) * spriteTexture.height; + r.yMax = Math.Max(s.uv[0].y, s.uv[2].y) * spriteTexture.height; + #if UNITY_EDITOR + if (s.uv.Length > 4) { + Debug.LogError("When using a tightly packed SpriteAtlas with Spine, you may only access Sprites that are packed as 'FullRect' from it! " + + "You can either disable 'Tight Packing' at the whole SpriteAtlas, or change the single Sprite's TextureImporter Setting 'MeshType' to 'Full Rect'." + + "Sprite Asset: " + s.name, s); + } + #endif + } var newTexture = new Texture2D((int)r.width, (int)r.height, textureFormat, mipmaps, linear); newTexture.CopyTextureAttributesFrom(spriteTexture); if (applyPMA) From 22c5144bdede7736e56d7c9ebc58fc54b282d9f0 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Wed, 29 Apr 2020 20:27:38 +0200 Subject: [PATCH 08/14] [unity] Fixed a build error (build to platform only) in 'Spine Examples' after package.json structures and asmdef files have been added. --- .../Editor/spine-unity-examples-editor.asmdef | 18 ++++++++++++++++++ .../spine-unity-examples-editor.asmdef.meta | 7 +++++++ 2 files changed, 25 insertions(+) create mode 100644 spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/Editor/spine-unity-examples-editor.asmdef create mode 100644 spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/Editor/spine-unity-examples-editor.asmdef.meta diff --git a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/Editor/spine-unity-examples-editor.asmdef b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/Editor/spine-unity-examples-editor.asmdef new file mode 100644 index 000000000..f7b1ac0ec --- /dev/null +++ b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/Editor/spine-unity-examples-editor.asmdef @@ -0,0 +1,18 @@ +{ + "name": "spine-unity-examples-editor", + "references": [ + "spine-unity", + "spine-unity-examples" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/Editor/spine-unity-examples-editor.asmdef.meta b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/Editor/spine-unity-examples-editor.asmdef.meta new file mode 100644 index 000000000..8b616dccc --- /dev/null +++ b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/Editor/spine-unity-examples-editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 39599136c72c0b64b925d3ff2885aecb +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From d499dba7c4bd184a3a56b017b59d9faf5230327c Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Wed, 29 Apr 2020 20:35:47 +0200 Subject: [PATCH 09/14] [unity] Unity SpriteAtlas support: Upon build to platform, the internally generated sprite atlas texture is used instead of the png file generated for the editor (to reduce additional memory usage). This completes Unity SpriteAtlas support, together with previous commit d35550d. Closes #940. --- .../Runtime/spine-unity/Asset Types/SpineSpriteAtlasAsset.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SpineSpriteAtlasAsset.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SpineSpriteAtlasAsset.cs index 71c057ff7..12b1a7005 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SpineSpriteAtlasAsset.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SpineSpriteAtlasAsset.cs @@ -190,6 +190,9 @@ namespace Spine.Unity { texture = AccessPackedTexture(sprites); Material material = materials[0]; + #if !UNITY_EDITOR + material.mainTexture = texture; + #endif Spine.AtlasPage page = new AtlasPage(); page.name = spriteAtlas.name; From e649ef05b7fc552c9063e47ecda76dc93064000d Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Fri, 1 May 2020 20:48:29 +0200 Subject: [PATCH 10/14] Added AnimationState listener tests for looping and queuing a second animation. --- .../spine/AnimationStateTests.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTests.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTests.java index 7860c9f16..9d8b0f586 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTests.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTests.java @@ -797,6 +797,48 @@ public class AnimationStateTests { System.exit(0); } + setup("looping", // 28 + expect(0, "start", 0, 0), // + expect(0, "event 0", 0, 0), // + expect(0, "event 14", 0.5f, 0.5f), // + expect(0, "event 30", 1, 1), // + expect(0, "complete", 1, 1), // + expect(0, "event 0", 1, 1), // + expect(0, "event 14", 1.5f, 1.5f), // + expect(0, "event 30", 2, 2), // + expect(0, "complete", 2, 2), // + expect(0, "event 0", 2, 2), // + expect(0, "event 14", 2.5f, 2.5f), // + expect(0, "end", 2.6f, 2.7f), // + expect(0, "dispose", 2.6f, 2.7f) // + ); + state.setAnimation(0, "events0", true).setTrackEnd(2.6f); + run(0.1f, 1000, null); + + setup("set next", // 29 + expect(0, "start", 0, 0), // + expect(0, "event 0", 0, 0), // + expect(0, "event 14", 0.5f, 0.5f), // + expect(0, "event 30", 1, 1), // + expect(0, "complete", 1, 1), // + expect(0, "interrupt", 1.1f, 1.1f), // + + expect(1, "start", 0.1f, 1.1f), // + expect(1, "event 0", 0.1f, 1.1f), // + + expect(0, "end", 1.1f, 1.2f), // + expect(0, "dispose", 1.1f, 1.2f), // + + expect(1, "event 14", 0.5f, 1.5f), // + expect(1, "event 30", 1, 2), // + expect(1, "complete", 1, 2), // + expect(1, "end", 1, 2.1f), // + expect(1, "dispose", 1, 2.1f) // + ); + state.setAnimation(0, "events0", false); + state.addAnimation(0, "events1", false, 0).setTrackEnd(1); + run(0.1f, 1000, null); + System.out.println("AnimationState tests passed."); } From 742d11f65ff15ad53a26acabfbd83389eb48253a Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Sat, 2 May 2020 17:42:18 +0200 Subject: [PATCH 11/14] [libgdx] SkeletonViewer, try to disable reflective access warning. --- .../com/esotericsoftware/spine/SkeletonViewer.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java index 5d70cd4de..c01933f5d 100644 --- a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java +++ b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java @@ -34,6 +34,7 @@ import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*; import java.io.File; import java.lang.Thread.UncaughtExceptionHandler; +import java.lang.reflect.Field; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; @@ -1110,6 +1111,19 @@ public class SkeletonViewer extends ApplicationAdapter { } static public void main (String[] args) throws Exception { + try { // Try to turn off illegal access log messages. + Class loggerClass = Class.forName("jdk.internal.module.IllegalAccessLogger"); + Field loggerField = loggerClass.getDeclaredField("logger"); + Class unsafeClass = Class.forName("sun.misc.Unsafe"); + Field unsafeField = unsafeClass.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + Object unsafe = unsafeField.get(null); + Long offset = (Long)unsafeClass.getMethod("staticFieldOffset", Field.class).invoke(unsafe, loggerField); + unsafeClass.getMethod("putObjectVolatile", Object.class, long.class, Object.class) // + .invoke(unsafe, loggerClass, offset, null); + } catch (Throwable ex) { + } + SkeletonViewer.args = args; String os = System.getProperty("os.name"); From de203d57155bc3edc936e67ed2d8ae802c25734c Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Tue, 5 May 2020 14:44:22 +0200 Subject: [PATCH 12/14] Fixed TransformMode.noRotationOrReflection applying skeleton scale twice. This transform mode inherits scale from the parent, which will have skeleton scale, so should not have it applied again. closes #1668 --- .../spine-libgdx/src/com/esotericsoftware/spine/Bone.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java index e6ded39db..a789ad32d 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java @@ -169,7 +169,7 @@ public class Bone implements Updatable { b = pa * lb - pb * ld; c = pc * la + pd * lc; d = pc * lb + pd * ld; - break; + return; } case noScale: case noScaleOrReflection: { From 94e6e415184cc95df319de67f2d0302354247025 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Tue, 5 May 2020 15:34:07 +0200 Subject: [PATCH 13/14] Added AnimationState setNext and clearNext. http://esotericsoftware.com/forum/Keep-track-of-added-animations-13896 --- .../com/esotericsoftware/spine/AnimationState.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java index 8a23933f6..b8c37ce0e 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -522,6 +522,11 @@ public class AnimationState { queue.drain(); } + /** Removes the {@link TrackEntry#getNext() next entry} and all entries after it for the specified entry. */ + public void clearNext (TrackEntry entry) { + disposeNext(entry.next); + } + private void setCurrent (int index, TrackEntry current, boolean interrupt) { TrackEntry from = expandToIndex(index); tracks.set(index, current); @@ -1084,11 +1089,17 @@ public class AnimationState { this.drawOrderThreshold = drawOrderThreshold; } - /** The animation queued to start after this animation, or null. next makes up a linked list. */ + /** The animation queued to start after this animation, or null if there is none. next makes up a linked list. + * It cannot be set to null, use {@link AnimationState#clearNext(TrackEntry)} instead. */ public @Null TrackEntry getNext () { return next; } + public void setNext (TrackEntry next) { + if (next == null) throw new IllegalArgumentException("next cannot be null."); + this.next = next; + } + /** Returns true if at least one loop has been completed. *

* See {@link AnimationStateListener#complete(TrackEntry)}. */ From 03d5155d6b58ed90747f39acbc65664bea9d8d53 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Tue, 5 May 2020 20:03:42 +0200 Subject: [PATCH 14/14] [libgdx] Fixed AnimationStateTests 24 (setAnimation during AnimationStateListener). --- .../src/com/esotericsoftware/spine/AnimationState.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java index b8c37ce0e..77d9686ed 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -1250,11 +1250,10 @@ public class AnimationState { if (drainDisabled) return; // Not reentrant. drainDisabled = true; - Object[] objects = this.objects.items; SnapshotArray listenersArray = AnimationState.this.listeners; for (int i = 0; i < this.objects.size; i += 2) { - EventType type = (EventType)objects[i]; - TrackEntry entry = (TrackEntry)objects[i + 1]; + EventType type = (EventType)objects.get(i); + TrackEntry entry = (TrackEntry)objects.get(i + 1); int listenersCount = listenersArray.size; Object[] listeners = listenersArray.begin(); switch (type) { @@ -1285,7 +1284,7 @@ public class AnimationState { ((AnimationStateListener)listeners[ii]).complete(entry); break; case event: - Event event = (Event)objects[i++ + 2]; + Event event = (Event)objects.get(i++ + 2); if (entry.listener != null) entry.listener.event(entry, event); for (int ii = 0; ii < listenersCount; ii++) ((AnimationStateListener)listeners[ii]).event(entry, event);