I'm dynamically creating skins via sprites attachments and applying them within Unity during runtime. (exactly like the Mix and Match example)
One issue I found was that I can only see these changes when I play the game. So I attempted to update the character during onValidate() (Similar to this example http://esotericsoftware.com/forum/How-can-I-save-code-changed-spine-skeleton-prefab-8589)
I also run the script in [ExecuteAlways].
The problem is that the changes apply when I change out out a attachment, but then instantly go back to the previous none skin state.
What's weird is that because of me running in ExecuteAlways the Start method fires off after I save a change to my script, and updates the skin correctly.
Tho as soon as I click it goes back to default skin.
I'm not entirely sure what's going on here. When I Debug.Log the Apply() function it shows that it only fires once on the onValidate()
So I'm curious if I'm doing something else wrong or if there may be an issue with spine.
Modified Mix and Match for example purposes.
/******************************************************************************
* 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;
using System.Collections;
using Spine;
using Spine.Unity;
namespace Anisoft
{
// This is an example script that shows you how to change images on your skeleton using UnityEngine.Sprites.
[ExecuteAlways]
public class MixAndMatchEdit : MonoBehaviour
{
#region Inspector
[SpineSkin]
public string templateAttachmentsSkin = "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")]
public bool repack = true;
public BoundingBoxFollower bbFollower;
[Header("Do not assign")]
public Texture2D runtimeAtlas;
public Material runtimeMaterial;
#endregion
Skin customSkin;
void OnValidate()
{
if (sourceMaterial == null)
{
var skeletonAnimation = GetComponent<SkeletonAnimation>();
if (skeletonAnimation != null)
sourceMaterial = skeletonAnimation.SkeletonDataAsset.atlasAssets[0].PrimaryMaterial;
}
Apply();
}
private void Start()
{
Apply();
}
void Apply()
{
var skeletonAnimation = GetComponent<SkeletonAnimation>();
var skeleton = skeletonAnimation.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.
var templateSkin = skeleton.Data.FindSkin(templateAttachmentsSkin);
customSkin.AddAttachments(templateSkin);
// STEP 1: "EQUIP" ITEMS USING SPRITES
// STEP 1.1 Find the original/template attachment.
// Step 1.2 Get a clone of the original/template attachment.
// Step 1.3 Apply the Sprite image to the clone.
// Step 1.4 Add the remapped clone to the new custom skin.
// Let's do this for the visor.
if (visorSprite != null)
{
int visorSlotIndex = skeleton.FindSlotIndex(visorSlot); // You can access GetAttachment and SetAttachment via string, but caching the slotIndex is faster.
Attachment templateAttachment = templateSkin.GetAttachment(visorSlotIndex, visorKey); // STEP 1.1
Attachment newAttachment = templateAttachment.GetRemappedClone(visorSprite, sourceMaterial); // STEP 1.2 - 1.3
customSkin.SetAttachment(visorSlotIndex, visorKey, newAttachment); // STEP 1.4
}
if (gunSprite != null)
{
// And now for the gun.
int gunSlotIndex = skeleton.FindSlotIndex(gunSlot);
Attachment templateGun = templateSkin.GetAttachment(gunSlotIndex, gunKey); // STEP 1.1
Attachment newGun = templateGun.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, preferably at level-load-time: REPACK THE CUSTOM SKIN TO MINIMIZE DRAW CALLS
// IMPORTANT NOTE: the GetRepackedSkin() operation is expensive - if multiple characters
// need to call it every few seconds the overhead will outweigh the draw call benefits.
//
// 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.
if (repack)
{
var repackedSkin = new Skin("repacked skin");
repackedSkin.AddAttachments(skeleton.Data.DefaultSkin); // Include the "default" skin. (everything outside of skin placeholders)
repackedSkin.AddAttachments(customSkin); // Include your new custom skin.
repackedSkin = repackedSkin.GetRepackedSkin("repacked skin", sourceMaterial, out runtimeMaterial, out runtimeAtlas); // Pack all the items in the skin.
skeleton.SetSkin(repackedSkin); // Assign the repacked skin to your Skeleton.
if (bbFollower != null) bbFollower.Initialize(true);
}
else
{
skeleton.SetSkin(customSkin); // Just use the custom skin directly.
}
skeleton.SetSlotsToSetupPose(); // Use the pose from setup pose.
skeletonAnimation.Update(0); // Use the pose in the currently active animation.
Resources.UnloadUnusedAssets();
}
}
}
Thank you for taking the time to look at my issue.