• Runtimes
  • Problem with SetSkin() to change skin at runtime (Unity)

  • تم التحرير
Related Discussions
...

I've got a character model with a couple of skins

1) Light (a light armor look)
2) Heavy (a heavy armor look)

I'm using Unity, and the SkeletonAnimation component has an "Initial Skin" option, from which I can select the light or heavy option and the character appears in game correctly with the expected skin.

However, I now want to change the skin at runtime and am having an issue where not all slots are changing to the new skin data. I think this has something to do with the fact that not all slots have data for all skins since slots which have data in all skin sets seem to be switching at runtime properly. In the following example you can see I have two "skirt" nodes in the hierarchy,

1) Waist [ColorSkirtA] - this has an animated mesh skirt in the light skin only
2) Waist Heavy Skirt - this has an animated mesh skirt in the heavy skin only

In the game, I have the initial skin set to "Light" , which displays fine ... but when I set the skin to "Heavy", there are still data from the light skin shown ... in particular the light skirt is easily seen.

Any ideas on what code is the culprit here and not properly taking care of the skin switch properly and what I should do about it? I'm changing the skin at the runtime using

Skeleton.SetSkin( "Heavy" );

hi,

This is intended or some razzmatazz, my personal view is that it should be changed: Skinning there is code 'solutions' in there too. Sorry for bad wording in top post

There is some ambiguity about how Slots and Skins are designed to be used.

In your example, you have a slot specifically for 'Waist Heavy Skirt' which then has a Skin Attachment, also called 'Waist Heavy Skirt'. I don't tend to setup my skeletons like that, if I were to approach this, I would have a Slot for 'Waist' (which you have) and a Slot for 'Skirt'. The Skin attachment would then be the 'Heavy Skirt', and the Light Skin would have a Skin Attachment 'Light Skirt', which would be empty.

Yes, you end up with some empty Skin attachments, but I think this makes sense to me. Having Slots that are Skin-specific rather than generic seems to me to extend a Skin's remit into the skeleton-proper, if that makes sense.

@colinday: At your level, if something mysterious and unexplained happens, a cursory look at the classes and the calls are usually enough to explain things. Not blaming you or anything! Just saying, it's what I've been doing this whole time, and being developers, it's empowering to be able to read and edit the code (as it should be)
https://github.com/EsotericSoftware/spine-runtimes/blob/master/spine-csharp/src/Skeleton.cs#L241

SetSkin does two separate things depending on whether Skeleton.skin is null or not, or if there wasn't a skin assigned already.

If there wasn't a skin assigned, it calls Skin.AttachAll()
At this point, you should know that the Skin object is a dictionary/map of slot+attachment names and attachments.
It will go through all the items in the skin, and changing the slots based on what items exist in the Skin.

If there was already a skin assigned, it iterates through all the slots in that Skeleton.
At this point, it's a bit strange. (And you can absolutely change the code here depending on what you need).
For each slot, it takes the name of the attachment it's supposed to have in Setup Mode. (slot.data.attachmentName)
If the slot is empty in Setup Mode, it'll do nothing.
If the slot has a name in setup mode, it will try to get an attachment from the skin.
If the skin doesn't have an attachment for it, it will do nothing. (at this point, what you want is probably set it to null instead of doing nothing)
If the skin has an attachment for it, it will use that.
Then it will store the reference of the newly set skin object in the skeleton.skin field, so it will be used by animations going forward.

The fix
The change you want is likely on this line: https://github.com/EsotericSoftware/spine-runtimes/blob/master/spine-csharp/src/Skeleton.cs#L252
Just remove the if conditional so it'll change the attachment even if it's null.
Similar logic can be found here: https://github.com/EsotericSoftware/spine-runtimes/blob/master/spine-csharp/src/Skin.cs#L84
But I don't know if you need to change that.

I think what these lines are trying to avoid is clearing an attachment in case you programmatically set that attachment yourself. I've always found the manual low-level setting of attachments at runtime to be flimsy. Any animations you play could easily change them. So I do everything through dynamically generated skins.

Many of the "weird" things Spine does is based on some amount of ambiguity, which is why this feedback is useful if something doesn't make sense in your use case, if you feel like your setup is the most common, or if there could possibly be other setups too. This informs the development process, and fix bugs if it's a bug.

Skins are definitely an area for improvement for both the runtime and the editor: support for mix and match, multiple skin combinations, etc.

I feel like this could be solved in the runtimes by adding an optional bool parameter or an enum of options in the SetSkin function like "SkinMode.StartFresh" or something.

عام واحد لاحقا

@Pharan @Mitch @[محذوف]

Sorry to necro an old thread. But I'm trying to do the same thing. I'm still very noob with coding and I'm getting errors in Unity

NullReferenceException: Object reference not set to an instance of an object

I have 2 skin placeholders in Spine. One called "Hat" and another called "Nohat"
 Loading Image

Here's my code snippet. Can you guys please point out what I'm doing wrong?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Spine;

public class testtest : MonoBehaviour {

   public Skeleton _skin;

   void Awake(){
      _skin = new Skeleton (); //this doesn't work.. get a constructor error  
} void Update () { if(Input.GetKeyDown(KeyCode.Y)) { test (); } } void test(){ _skin.SetSkin ("Hat"); } }

much thanks! :party:


Ok I got it working... to answer my own question after digging around. :whew: :whew:

For those of you that have the same question as myself; here is the way to switch skin placeholders during runtime in Unity.

If your character is using "SkeletonAnimator.cs", then you need to make sure that script is being assigned in your own script. I got confused thinking I needed to use "Skeleton.cs" in my scripts. Here's the code snippet:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Spine.Unity; //you need to call this namespace here, as SkeletonAnimator isn't a monobehavior class

public class testtest : MonoBehaviour {

   public SkeletonAnimator _skel; //the script that lets us change skins during runtime.

   void Awake(){
     _skel = GetComponent<SkeletonAnimator> (); //again make sure your game object is using SkeletonAnimator. If you're using SkeletonAnimation, I assume you change all "SkeletonAnimator" to "SkeletonAnimation". Don't quote my feeble artist brain on this however..
   }

   void Update () {
      if(Input.GetKeyDown(KeyCode.Y))
      {
         test ();
      }
   }

   void test(){
      _skel.skeleton.SetSkin ("Hat"); //SetSkin is a public method from inside Skeleton.CS.. but you can also call it from SkeletonAnimator.. I found that in this thread. [url]http://esotericsoftware.com/forum/Unity3D-Changing-of-Skins-within-Unity3D-4018[/url]
   }
}

And thats about it. Now you can change you skin on the fly in game! Yipee! :party: :party:

Indie love~ Sharing is Carin'!
https://twitter.com/IndieNendoroid

6 أشهر لاحقا
Pharan wrote

The fix
The change you want is likely on this line: https://github.com/EsotericSoftware/spine-runtimes/blob/master/spine-csharp/src/Skeleton.cs#L252
Just remove the if conditional so it'll change the attachment even if it's null.
Similar logic can be found here: https://github.com/EsotericSoftware/spine-runtimes/blob/master/spine-csharp/src/Skin.cs#L84
But I don't know if you need to change that.

@Pharan: Can I get an updated fix for the lines you are mentioning (the links are dead)? I've tried L407 on Skeleton.cs and the L95 in Skin.cs and a few other if (null) lines but I'm getting wonky results. It seems like once the attachment is set to null it will not be set again when switch to another skin. Using Spine-Unity btw if it matters.

Mitch wrote

I feel like this could be solved in the runtimes by adding an optional bool parameter or an enum of options in the SetSkin function like "SkinMode.StartFresh" or something.

I feel like this is an excellent solution from Mitch. Skin swapping, in my opinion, should at least have the option to act like they do within Spine Editor. If I swap skins there, I expect null skin placeholders to become null.

You can call Skeleton setSlotsToSetupPose after setting a new skin.

The solution is actually just this:

skeletonAnimation.Skeleton.SetSkin(theSkinYouWant); // 1. Set your skin.
skeletonAnimation.Skeleton.SetSlotsToSetupPose(); // 2. Make sure it refreshes.
skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton); // 3. Make sure the attachments from your currently playing animation are applied.

You can also cache the references so the code is cleaner.

I can agree those three steps aren't as obvious as they should be, but it also has something to do with Skeleton slot attachments, which can be directly manipulated by the user from code, and Skeleton being decoupled from AnimationState.

So, maybe an extension method that does the same? I'll be sure to add this to the docs though.

I think it's mostly a matter of understanding the API, so docs make sense. An extension method for just 3 simple calls seems a bit overkill. The AnimationState apply call can be omitted if the AnimationState will be applied elsewhere before the next frame render.

Can we add a note about it to the javadoc/inline IDE docs?