Hey Nate,
I've been looking a lot at the code, it's very nice that it's easy to see it, and even Unity's code is easily viewed decompiled in my IDE 🙂 But anyhow, I haven't been able to solve the problem.
Just to explain what I'm trying to do, I'm making a script based on the MixAndMatch demo, to create a custom skin, and set attachments to different sprites. It works when I set sprites, but I can't figure out how to remove one.
Here's what the spine file looks like, in the editor, I click the dot on "backpack_side" to hide that region, and that's what I want to do in code, as you say you can't disable the slot.
In my default skin, my character has a slot called "Backpack". Now when equipping a weapon, I'm making a custom skin, that changes two slots:
- weapon_shotgun (Change weapon)
- Backpack (Change to another backpack)
In the end of this post is the unchanged code from the MixAndMatch.cs example. What I changed is that I loop through a list of slots and what attachments and sprites to change.
There is a commented line like this:
// customSkin.RemoveAttachment(gunSlotIndex, gunKey); // To remove an item.
I'm not sure why i should provide both the index and the key, as I just cached the index using skeleton.FindSlotIndex. Should that be both the index and the name "Backpack".
And if I don't change it in the skin, where do I add a line to set an empty attachment for the slot? Also do I need to apply the changes or update an animation or something to make it work?
I'm sorry for the long question, I'm really not used to how skins work, so that might also be the reason for the misunderstanding. But I'm really stuck and hope you can help... thanks again 🙂
// This is an example script that shows you how to change images on your skeleton using UnityEngine.Sprites.
public class MixAndMatch : 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;
}
}
IEnumerator Start () {
yield return new WaitForSeconds(1f); // Delay for one second before applying. For testing.
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);
// 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.
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
// 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.
// Note: materials and textures returned by GetRepackedSkin() behave like 'new Texture2D()' and need to be destroyed
if (runtimeMaterial)
Destroy(runtimeMaterial);
if (runtimeAtlas)
Destroy(runtimeAtlas);
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();
}
}