• Unity
  • How to rebuild a Skin in the editor?

Related Discussions
...

For our game, we have characters with lots of clothing and body options. This works great at runtime. However, we want to be able to configure the settings in the editor. Our configuration tools all work great, and once the game is running the skins appear as they are supposed to. However, in editor mode, we are unable to get the skins to display at all. This is a feature that used to work about 6 months ago (or so), and no longer appears to function.

This is the method we use to rebuild our skins:

public void RebuildCustomSkin()
 {
     if (skeletonAnimation == null || skeleton == null)
     {
         Debug.Log("

---

 SKIN BUILD ABORT 

---

 " + gameObject.name);
         return;
     }

 customSkin.Clear();
 foreach (PlayerAnimationSkin thisSkin in ActiveSkins)
 {
     Skin appSkin = skeleton.Data.FindSkin(thisSkin.SkinName);

     if (appSkin == null)
     {
         Debug.LogWarning("

---

 No skin named " + thisSkin.SkinName + " found on skeleton of object " + gameObject.name);
             return;
         }
         else
         {
             customSkin.Append(appSkin);

     }
 }

 skeleton.SetSkin(customSkin);
 skeleton.SetToSetupPose();

 animationStateComponent.AnimationState.Apply(skeleton);
 }

The PlayerAnimationSkin object is our own internal object that just stores some internal data about the skins (such as if it is displayed by default or not) - but the key element is that it has the 'SkinName' stored as a string. We have gone as far as making sure this method is called in the Update look of the editor by adding [ExecuteInEditMode] [ExecuteAlways] to the top of the object class, running UnityEditor.EditorApplication.QueuePlayerLoopUpdate(); in OnEnable, and also running UnityEditor.EditorApplication.QueuePlayerLoopUpdate(); in every Update loop to ensure it keeps getting updated.

In addition, what appears to be happening is that the SkeletonAnimation component is always erasing the settings in editor mode, and replacing them with whatever the value of "Initial Skin" is - based on watching the skin flash for a fraction of a second to the values we set, then return to whatever is set in Initial Skin on SkeletonAnimation.

Any pointers would be very helpful as this is a frustration for us.

Thank you!!

  • RedVonix

Which version of the Spine-Unity runtime are you using?
Have you tried the behaviour in the recent 3.8-beta runtime? Some things regarding updating have been changed and fixed in the more recent 3.7 and 3.8-beta commits. You could do a quick test with just a minimal project setup, but note that you have to re-export the Spine assets from the 3.8 Editor (backup/copy your work and don't just upgrade it blindly of course).

2 years later

Hey,

I also have a problem with mix and match in edit mode.
Everything works perfectly in the play mode. See the video attached:

Versions:

Unity 2020.1.7
This Spine-Unity runtime works with data exported from Spine Editor version: 4.0.xx
Package version: spine-unity-4.0-2021-07-14.unitypackage

This is the simple code I'm executing:

void SetSkinTest()
{
   skeletonAnimation = GetComponent<SkeletonAnimation>();
   skeletonData = skeletonAnimation.skeletonDataAsset.GetSkeletonData(true);
   
Skin newSkin = new Skin("newSkin"); newSkin.AddSkin(skeletonData.FindSkin("base")); newSkin.AddSkin(skeletonData.FindSkin("Outfit0/Combined"));
skeletonAnimation.Skeleton.SetSkin(newSkin); // 1. Set your skin. skeletonAnimation.Skeleton.SetSlotsToSetupPose(); // 2. Make sure it refreshes. skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton);
// Debug string mode = EditorApplication.isPlaying ? "Play Mode" : "Edit Mode"; Debug.Log("Skin has been changed (" + mode + ")"); }

What am I missing? What should I change to have the same behavior in the edit as in the play mode?

Thank you.

Hey trafcio, I tested your code and you just need to add this line:

skeletonAnimation.EditorSkipSkinSync = true;

If you haven't fleshed out your skin-setting code yet, here is my code - it has an option to automatically apply the skin configuration in editor-mode (if you use "replace skin" and not "add to skin") and a couple other options. It requires Odin Inspector if you want to directly copy it 😃

using Sirenix.OdinInspector;
using Spine;
using Spine.Unity;
using Spine.Unity.AttachmentTools;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
public class SpineSkinApplicator : MonoBehaviour
{
    public SkeletonAnimation targetSkeletonAnimation;
    public bool ignoreSkinNotFoundError = false;

public enum AutomaticApplicationOption { None, OnStart }
public AutomaticApplicationOption automaticApplyOption = AutomaticApplicationOption.OnStart;

public enum SkinApplicationType { ReplaceSkin, AddToSkin }
public SkinApplicationType applicationType;
[ShowIf("applicationType", SkinApplicationType.ReplaceSkin)][LabelText("Apply In Edit Mode")] public bool replaceSkinApplyInEditMode = true;

[SpineSkin(dataField = "targetSkeletonAnimation")] public List<string> skinEntries = new List<string>(1);

void Start() {
    if (Application.isPlaying == false && applicationType == SkinApplicationType.ReplaceSkin && replaceSkinApplyInEditMode) { Activate(); }
    if (Application.isPlaying == true && automaticApplyOption == AutomaticApplicationOption.OnStart) { Activate(); }
}

private void OnValidate() {

    if (Application.isPlaying) { return; }

    if (targetSkeletonAnimation == null) { targetSkeletonAnimation = GetComponent<SkeletonAnimation>(); }

    if (targetSkeletonAnimation != null) {
        if (applicationType == SkinApplicationType.ReplaceSkin && replaceSkinApplyInEditMode) {
#if UNITY_EDITOR
                targetSkeletonAnimation.EditorSkipSkinSync = true;
#endif
                Activate();
            } else {
#if UNITY_EDITOR
                targetSkeletonAnimation.EditorSkipSkinSync = false;
#endif
            }
        }
    }

private void OnDestroy() {
#if UNITY_EDITOR
        if (targetSkeletonAnimation != null) {
            targetSkeletonAnimation.EditorSkipSkinSync = false;
        }
#endif
    }


public void Activate() {

    if(ValidTargetAndSkeleton() == false) { return; }

    switch (applicationType) {
        case SkinApplicationType.ReplaceSkin:
            ApplyAsReplace();
            break;
        case SkinApplicationType.AddToSkin:
            ApplyAsAddToSkin();
            break;
    }
}


public void ApplyAsReplace() {

    Skin buildSkin = new Skin("buildSkin");

    foreach (var skinName in skinEntries) {
        if (string.IsNullOrEmpty(skinName)) { continue; }
        var skinCur = targetSkeletonAnimation.skeleton.Data.FindSkin(skinName);
        if (skinCur != null) {
            buildSkin.AddSkin(skinCur);
        } else {
            if (ignoreSkinNotFoundError == false) {
                Debug.LogError("Error: skin not found, skinName: " + skinName, gameObject);
            }
        }
    }

    targetSkeletonAnimation.skeleton.SetSkin(buildSkin);
    targetSkeletonAnimation.skeleton.SetSlotsToSetupPose();
}


public void ApplyAsAddToSkin() {

    foreach(string skinName in skinEntries) {
        if (string.IsNullOrEmpty(skinName)) { continue; }
        Skin newSkin = targetSkeletonAnimation.skeleton.Data.FindSkin(skinName);
        if (newSkin != null) {
            Skin currentSkin = targetSkeletonAnimation.skeleton.Skin;
            Skin combinedSkin = new Skin("combinedSkin");
            if (currentSkin != null) { combinedSkin.AddSkin(currentSkin); }
            combinedSkin.AddSkin(newSkin);
            targetSkeletonAnimation.skeleton.SetSkin(combinedSkin);
            targetSkeletonAnimation.skeleton.SetSlotsToSetupPose();
        } else {
            if (ignoreSkinNotFoundError == false) {
                Debug.LogError("Error: skin not found, skinName: " + skinName, gameObject);
            }
        }
    }

}

public bool ValidTargetAndSkeleton() {
    if(targetSkeletonAnimation == null) { return false; }
    return targetSkeletonAnimation.skeleton != null;
}

}
    
Jamez0r wrote

skeletonAnimation.EditorSkipSkinSync = true;

Perfect. That's what I was looking for.
Thank you for that.

We have a pretty complicated customization code that works fine in play mode.
I will look into your code. Maybe we can use something of yours.

Thanks a lot Jamez0r for helping out! 8)