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);