diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs
index cdbca44dc..5beb89dab 100644
--- a/spine-csharp/src/Animation.cs
+++ b/spine-csharp/src/Animation.cs
@@ -743,7 +743,7 @@ namespace Spine {
public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } }
override public int PropertyId {
- get { return ((int)TimelineType.Deform << 24) + slotIndex; }
+ get { return ((int)TimelineType.Deform << 24) + attachment.id + slotIndex; }
}
public DeformTimeline (int frameCount)
diff --git a/spine-csharp/src/Attachments/VertexAttachment.cs b/spine-csharp/src/Attachments/VertexAttachment.cs
index 23c502e9d..a7efaeb3d 100644
--- a/spine-csharp/src/Attachments/VertexAttachment.cs
+++ b/spine-csharp/src/Attachments/VertexAttachment.cs
@@ -33,10 +33,15 @@ using System;
namespace Spine {
/// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices.
public class VertexAttachment : Attachment {
+ static int nextID = 0;
+
+ internal readonly int id = (nextID++ & 65535) << 11;
internal int[] bones;
internal float[] vertices;
internal int worldVerticesLength;
+ /// Gets a unique ID for this attachment.
+ public int Id { get { return id; } }
public int[] Bones { get { return bones; } set { bones = value; } }
public float[] Vertices { get { return vertices; } set { vertices = value; } }
public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } }
diff --git a/spine-unity/Assets/Examples/Other Examples/Mix and Match.unity b/spine-unity/Assets/Examples/Other Examples/Mix and Match.unity
index a69d38cbb..8300345af 100644
--- a/spine-unity/Assets/Examples/Other Examples/Mix and Match.unity
+++ b/spine-unity/Assets/Examples/Other Examples/Mix and Match.unity
@@ -375,6 +375,80 @@ Transform:
m_Father: {fileID: 0}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &694242025
+GameObject:
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ serializedVersion: 5
+ m_Component:
+ - component: {fileID: 694242026}
+ - component: {fileID: 694242028}
+ - component: {fileID: 694242027}
+ m_Layer: 5
+ m_Name: Text (6)
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &694242026
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ m_GameObject: {fileID: 694242025}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1.2500063, y: 1.2500063, z: 1.2500063}
+ m_Children: []
+ m_Father: {fileID: 1958410249}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0.5, y: 0.5}
+ m_AnchorMax: {x: 0.7178893, y: 0.5}
+ m_AnchoredPosition: {x: 285, y: -70}
+ m_SizeDelta: {x: 40, y: 30}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &694242027
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ m_GameObject: {fileID: 694242025}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
+ Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
+ m_FontData:
+ m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
+ m_FontSize: 24
+ m_FontStyle: 0
+ m_BestFit: 0
+ m_MinSize: 0
+ m_MaxSize: 55
+ m_Alignment: 4
+ m_AlignByGeometry: 0
+ m_RichText: 1
+ m_HorizontalOverflow: 1
+ m_VerticalOverflow: 1
+ m_LineSpacing: 1
+ m_Text: SkeletonGraphic Equipped
+--- !u!222 &694242028
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ m_GameObject: {fileID: 694242025}
--- !u!1 &952321879
GameObject:
m_ObjectHideFlags: 0
@@ -550,7 +624,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
- m_AnchoredPosition: {x: 608, y: -253}
+ m_AnchoredPosition: {x: -74, y: 416}
m_SizeDelta: {x: 661, y: 181}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1262477662
@@ -670,6 +744,7 @@ RectTransform:
- {fileID: 1620489274}
- {fileID: 952321880}
- {fileID: 1262477661}
+ - {fileID: 1958410249}
m_Father: {fileID: 0}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
@@ -1018,6 +1093,108 @@ Material:
- _node_3476: 0
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
+--- !u!1 &1958410248
+GameObject:
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ serializedVersion: 5
+ m_Component:
+ - component: {fileID: 1958410249}
+ - component: {fileID: 1958410252}
+ - component: {fileID: 1958410251}
+ - component: {fileID: 1958410250}
+ m_Layer: 0
+ m_Name: SkeletonGraphic (spineboy-unity)
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &1958410249
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ m_GameObject: {fileID: 1958410248}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 0.8, y: 0.8, z: 0.8}
+ m_Children:
+ - {fileID: 694242026}
+ m_Father: {fileID: 1442798444}
+ m_RootOrder: 5
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0.5, y: 0.5}
+ m_AnchorMax: {x: 0.5, y: 0.5}
+ m_AnchoredPosition: {x: 538, y: -522}
+ m_SizeDelta: {x: 551, y: 678}
+ m_Pivot: {x: 0.5294104, y: 0.0076879906}
+--- !u!114 &1958410250
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ m_GameObject: {fileID: 1958410248}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 8e24c5293ec0b444eba7a2680caa925f, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ baseSkinName: base
+ sourceMaterial: {fileID: 2100000, guid: 1455e88fdb81ccc45bdeaedd657bad4d, type: 2}
+ visorSprite: {fileID: 21300000, guid: 4f554405f8f06164db0773d689da243c, type: 3}
+ visorSlot: goggles
+ visorKey: goggles
+ gunSprite: {fileID: 21300000, guid: 02c4cbcce432ae74bb2d965060e64d29, type: 3}
+ gunSlot: gun
+ gunKey: gun
+ repack: 1
+ runtimeAtlas: {fileID: 0}
+ runtimeMaterial: {fileID: 0}
+--- !u!114 &1958410251
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ m_GameObject: {fileID: 1958410248}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: d85b887af7e6c3f45a2e2d2920d641bc, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 2100000, guid: b66cf7a186d13054989b33a5c90044e4, type: 2}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
+ Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
+ skeletonDataAsset: {fileID: 11400000, guid: a467507a4ffb1d542a558739b2fede77, type: 2}
+ initialSkinName: base
+ initialFlipX: 0
+ initialFlipY: 0
+ startingAnimation: run
+ startingLoop: 1
+ timeScale: 1
+ freeze: 0
+ unscaledTime: 0
+ meshGenerator:
+ settings:
+ useClipping: 1
+ zSpacing: 0
+ pmaVertexColors: 1
+ tintBlack: 0
+ calculateTangents: 0
+ addNormals: 0
+ immutableTriangles: 0
+--- !u!222 &1958410252
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ m_GameObject: {fileID: 1958410248}
--- !u!28 &1983722037
Texture2D:
m_ObjectHideFlags: 0
diff --git a/spine-unity/Assets/Examples/Scripts/MixAndMatchGraphic.cs b/spine-unity/Assets/Examples/Scripts/MixAndMatchGraphic.cs
new file mode 100644
index 000000000..530880421
--- /dev/null
+++ b/spine-unity/Assets/Examples/Scripts/MixAndMatchGraphic.cs
@@ -0,0 +1,134 @@
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, Esoteric Software
+ * All rights reserved.
+ *
+ * You are granted a perpetual, non-exclusive, non-sublicensable, and
+ * non-transferable license to use, install, execute, and perform the Spine
+ * Runtimes software and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * or other intellectual property or proprietary rights notices on or in the
+ * Software, including any copy thereof. Redistributions in binary or source
+ * form must include this license and terms.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using UnityEngine;
+using Spine.Unity.Modules.AttachmentTools;
+
+namespace Spine.Unity.Examples {
+
+ // This is an example script that shows you how to change images on your skeleton using UnityEngine.Sprites.
+ public class MixAndMatchGraphic : MonoBehaviour {
+
+ #region Inspector
+ [SpineSkin]
+ public string baseSkinName = "base";
+ public Material sourceMaterial; // This will be used as the basis for shader and material property settings.
+
+ [Header("Visor")]
+ public Sprite visorSprite;
+ [SpineSlot] public string visorSlot;
+ [SpineAttachment(slotField:"visorSlot", skinField:"baseSkinName")] public string visorKey = "goggles";
+
+ [Header("Gun")]
+ public Sprite gunSprite;
+ [SpineSlot] public string gunSlot;
+ [SpineAttachment(slotField:"gunSlot", skinField:"baseSkinName")] public string gunKey = "gun";
+
+ [Header("Runtime Repack Required!!")]
+ public bool repack = true;
+
+ [Header("Do not assign")]
+ public Texture2D runtimeAtlas;
+ public Material runtimeMaterial;
+ #endregion
+
+ Skin customSkin;
+
+ void OnValidate () {
+ if (sourceMaterial == null) {
+ var skeletonGraphic = GetComponent();
+ if (skeletonGraphic != null)
+ sourceMaterial = skeletonGraphic.SkeletonDataAsset.atlasAssets[0].materials[0];
+ }
+ }
+
+ void Start () {
+ Apply();
+ }
+
+ void Apply () {
+ var skeletonGraphic = GetComponent();
+ var skeleton = skeletonGraphic.Skeleton;
+
+ // STEP 0: PREPARE SKINS
+ // Let's prepare a new skin to be our custom skin with equips/customizations. We get a clone so our original skins are unaffected.
+ customSkin = customSkin ?? new Skin("custom skin"); // This requires that all customizations are done with skin placeholders defined in Spine.
+ //customSkin = customSkin ?? skeleton.UnshareSkin(true, false, skeletonAnimation.AnimationState); // use this if you are not customizing on the default skin and don't plan to remove
+ // Next let's
+ var baseSkin = skeleton.Data.FindSkin(baseSkinName);
+
+ // STEP 1: "EQUIP" ITEMS USING SPRITES
+ // STEP 1.1 Find the original attachment.
+ // Step 1.2 Get a clone of the original attachment.
+ // Step 1.3 Apply the Sprite image to it.
+ // Step 1.4 Add the remapped clone to the new custom skin.
+
+ // Let's do this for the visor.
+ int visorSlotIndex = skeleton.FindSlotIndex(visorSlot); // You can access GetAttachment and SetAttachment via string, but caching the slotIndex is faster.
+ Attachment baseAttachment = baseSkin.GetAttachment(visorSlotIndex, visorKey); // STEP 1.1
+ Attachment newAttachment = baseAttachment.GetRemappedClone(visorSprite, sourceMaterial); // STEP 1.2 - 1.3
+ customSkin.SetAttachment(visorSlotIndex, visorKey, newAttachment); // STEP 1.4
+
+ // And now for the gun.
+ int gunSlotIndex = skeleton.FindSlotIndex(gunSlot);
+ Attachment baseGun = baseSkin.GetAttachment(gunSlotIndex, gunKey); // STEP 1.1
+ Attachment newGun = baseGun.GetRemappedClone(gunSprite, sourceMaterial); // STEP 1.2 - 1.3
+ if (newGun != null) customSkin.SetAttachment(gunSlotIndex, gunKey, newGun); // STEP 1.4
+
+ // customSkin.RemoveAttachment(gunSlotIndex, gunKey); // To remove an item.
+ // customSkin.Clear()
+ // Use skin.Clear() To remove all customizations.
+ // Customizations will fall back to the value in the default skin if it was defined there.
+ // To prevent fallback from happening, make sure the key is not defined in the default skin.
+
+ // STEP 3: APPLY AND CLEAN UP.
+ // Recommended: REPACK THE CUSTOM SKIN TO MINIMIZE DRAW CALLS
+ // Repacking requires that you set all source textures/sprites/atlases to be Read/Write enabled in the inspector.
+ // Combine all the attachment sources into one skin. Usually this means the default skin and the custom skin.
+ // call Skin.GetRepackedSkin to get a cloned skin with cloned attachments that all use one texture.
+ // Under the hood, this relies on
+ if (repack) {
+ var repackedSkin = new Skin("repacked skin");
+ repackedSkin.Append(skeleton.Data.DefaultSkin);
+ repackedSkin.Append(customSkin);
+ repackedSkin = repackedSkin.GetRepackedSkin("repacked skin", sourceMaterial, out runtimeMaterial, out runtimeAtlas);
+ skeleton.SetSkin(repackedSkin);
+ } else {
+ skeleton.SetSkin(customSkin);
+ }
+
+ skeleton.SetSlotsToSetupPose();
+ skeletonGraphic.Update(0);
+ skeletonGraphic.OverrideTexture = runtimeAtlas;
+
+ Resources.UnloadUnusedAssets();
+ }
+ }
+}
diff --git a/spine-unity/Assets/Examples/Scripts/MixAndMatchGraphic.cs.meta b/spine-unity/Assets/Examples/Scripts/MixAndMatchGraphic.cs.meta
new file mode 100644
index 000000000..9e06dc832
--- /dev/null
+++ b/spine-unity/Assets/Examples/Scripts/MixAndMatchGraphic.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 8e24c5293ec0b444eba7a2680caa925f
+timeCreated: 1480089275
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs b/spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs
index 14efdeecf..2761a9a3f 100644
--- a/spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs
+++ b/spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs
@@ -73,6 +73,8 @@ namespace Spine.Unity {
public int SlotCount { get { return endSlot - startSlot; } }
}
+ public delegate void MeshGeneratorDelegate (MeshGenerator meshGenerator);
+
[System.Serializable]
public class MeshGenerator {
public Settings settings = Settings.Default;
@@ -115,6 +117,10 @@ namespace Spine.Unity {
[NonSerialized] readonly ExposedList colorBuffer = new ExposedList(4);
[NonSerialized] readonly ExposedList> submeshes = new ExposedList> { new ExposedList(6) }; // start with 1 submesh.
+ public Vector3[] VertexBuffer { get { return this.vertexBuffer.Items; } }
+ public Vector2[] UVBuffer { get { return this.uvBuffer.Items; } }
+ public Color32[] ColorBuffer { get { return this.colorBuffer.Items; } }
+
[NonSerialized] Vector2 meshBoundsMin, meshBoundsMax;
[NonSerialized] float meshBoundsThickness;
[NonSerialized] int submeshIndex = 0;
diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/SkeletonGraphic.cs b/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/SkeletonGraphic.cs
index c774c4e80..5fa0f4a99 100644
--- a/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/SkeletonGraphic.cs
+++ b/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/SkeletonGraphic.cs
@@ -122,9 +122,11 @@ namespace Spine.Unity {
#region Internals
// This is used by the UI system to determine what to put in the MaterialPropertyBlock.
+ public Texture OverrideTexture { get; set; }
public override Texture mainTexture {
get {
// Fail loudly when incorrectly set up.
+ if (OverrideTexture != null) return OverrideTexture;
return skeletonDataAsset == null ? null : skeletonDataAsset.atlasAssets[0].materials[0].mainTexture;
}
}
@@ -197,6 +199,9 @@ namespace Spine.Unity {
public event UpdateBonesDelegate UpdateWorld;
public event UpdateBonesDelegate UpdateComplete;
+ /// Occurs after the vertex data populated every frame, before the vertices are pushed into the mesh.
+ public event Spine.Unity.MeshGeneratorDelegate OnPostProcessVertices;
+
public void Clear () {
skeleton = null;
canvasRenderer.Clear();
@@ -267,11 +272,12 @@ namespace Spine.Unity {
}
if (canvas != null) meshGenerator.ScaleVertexData(canvas.referencePixelsPerUnit);
+ if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator);
var mesh = smartMesh.mesh;
meshGenerator.FillVertexData(mesh);
if (updateTriangles) meshGenerator.FillTrianglesSingle(mesh);
-
+
canvasRenderer.SetMesh(mesh);
smartMesh.instructionUsed.Set(currentInstructions);
diff --git a/spine-unity/Assets/spine-unity/SkeletonRenderer.cs b/spine-unity/Assets/spine-unity/SkeletonRenderer.cs
index f69b2d8bf..cd5c51615 100644
--- a/spine-unity/Assets/spine-unity/SkeletonRenderer.cs
+++ b/spine-unity/Assets/spine-unity/SkeletonRenderer.cs
@@ -41,7 +41,10 @@ namespace Spine.Unity {
public class SkeletonRenderer : MonoBehaviour, ISkeletonComponent {
public delegate void SkeletonRendererDelegate (SkeletonRenderer skeletonRenderer);
- public SkeletonRendererDelegate OnRebuild;
+ public event SkeletonRendererDelegate OnRebuild;
+
+ /// Occurs after the vertex data is populated every frame, before the vertices are pushed into the mesh.
+ public event Spine.Unity.MeshGeneratorDelegate OnPostProcessVertices;
public SkeletonDataAsset skeletonDataAsset;
public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } } // ISkeletonComponent
@@ -280,6 +283,8 @@ namespace Spine.Unity {
meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
}
+ if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator);
+
// STEP 3. Move the mesh data into a UnityEngine.Mesh ===========================================================================
var currentMesh = currentSmartMesh.mesh;
meshGenerator.FillVertexData(currentMesh);