• RuntimesUnity
  • How to Export Skins in their Own Skeletons

Harald In general it's always highly recommended to automate your exports using bash (or batch) scripts.

@Harald, Spine doesn't support partial export, so they can't export a skeleton per skin from the CLI. They could export to JSON and then write a script to hack that up, but I don't recommend that.

  • Harald が「いいね」しました。
Related Discussions
...

Thanks for the feedback!

The solution to having all textures in separate folders still have the following problems:
1) Shared attachments must be either duplicated per Skin (😢) or added to a separate folder. This will generate extra draw calls per skeleton, which will require multiple materials.

2) A solution to item #1 is repacking the texture in runtime after loading, which does not work well for Addressables module (I tried it and it mess up the skin)

I have a few questions about the Addressable module:
1) If all skin attachments are contained in a single separate directory, will only that skin be loaded from the Addressable?
2) What happens when we change skins? Will the unused skin be unloaded from memory?
3) What happens if multiple characters sharing the same skeleton use different Skins?

I will conduct more tests here and will get back to you.

  • Harald がこの投稿に返信しました。

    Answering #1:
    Based in my own tests using Unity's Memory Profiler, I can see that loaded Addressables are not unloaded.

    This should not be very hard to fix, but it is a problem that must be considered.

    It would also be very nice if Skin.GetRepackedSkin worked with Addressables module:

    • Non-Repacked Skin (build):

    • Repacked Skin (build):

    • Repacked Skin (Unity Editor):

    • Harald がこの投稿に返信しました。

      leonardo.bilck The solution to having all textures in separate folders still have the following problems:
      1) Shared attachments must be either duplicated per Skin (😢) or added to a separate folder. This will generate extra draw calls per skeleton, which will require multiple materials.

      That's unfortunately a general theoretical tradeoff.

      Your screenshot shows a very heavily alternating sequence of used atlas textures though. Please consider whether it's an option to rearrange your draw-order to have fewer switches between grouped shared attachments from one atlas page vs unique attachments from another.

      leonardo.bilck 2) A solution to item #1 is repacking the texture in runtime after loading,

      Yes, runtime repacking is the recommended solution if you can't re-arrange your draw order to have few atlas texture switches.

      leonardo.bilck which does not work well for Addressables module (I tried it and it mess up the skin)

      How did you perform runtime repacking with addressables?
      Could you please share some reproduction steps and code so that we can understand what's going wrong?
      Did you wait for the newly activated skin textures to be completely downloaded before repacking the skins? Coud you show the code how you are switching skins, waiting for the download and performing the repack operation?

      leonardo.bilck I have a few questions about the Addressable module:
      1) If all skin attachments are contained in a single separate directory, will only that skin be loaded from the Addressable?

      I'm not quite sure I understand what you mean by that. You're still responsible for assigning the high-resolution textures to whatever Addressable packages and groups that you think fits your scenario. The Addressables module only performs downloading and replacement of low-resolution placeholder textures at active materials with the high resolution texture versions. Whenever a low-res placeholder texture is found, the high-res version is downloaded.

      leonardo.bilck 2) What happens when we change skins? Will the unused skin be unloaded from memory?

      Note that it's never about skins, but about textures. The AddressableTextureLoader asset (suffix _Addressable) Inspector provides a property "Unload After Seconds Unused". Did you try setting this property according to your needs? If you don't want automatic unloading, you can trigger the unload earlier whenever you need.

      leonardo.bilck 3) What happens if multiple characters sharing the same skeleton use different Skins?

      It's about textures. The textures of active skins are loaded in their high-res version via Addressables.

      leonardo.bilck Answering #1:
      Based in my own tests using Unity's Memory Profiler, I can see that loaded Addressables are not unloaded.

      This should not be very hard to fix, but it is a problem that must be considered.

      See my posting above, did you check "Unload After Seconds Unused" or trigger an unload yourself?
      Of course it's by default not unloading currently unused textures immediately after you've switched to a different skin. Imagine switching between two weapon skins frequently, having to re-download the alternating skin's textures all the time would be terrible.

      leonardo.bilck It would also be very nice if Skin.GetRepackedSkin worked with Addressables module:

      Please see my questions in my previous posting above.

      I have posted the repository and a Windows build here.

      The main class for this POC is SpineSkinChangerScreen.

      I'm repacking on demand (check the button on the UI) through this method:

      private void RepackSkin()
          {
              const string repackedSuffix = "-repacked";
              string skinName = skeletonAnimation.skeleton.Skin.Name;
              
              if(skinName.EndsWith(repackedSuffix, StringComparison.Ordinal)) return;
              
              if(repackedSkinDictionaries.TryGetValue(skinName, out var existingSkin))
              {
                  skeletonAnimation.Skeleton.Skin = existingSkin;
                  skeletonAnimation.Skeleton.SetSlotsToSetupPose();
                  skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton);
                  return;
              }
              
              // https://esotericsoftware.com/spine-unity-main-components#Runtime-Repacking
              
              Skin skin = skeletonAnimation.skeleton.Skin;
              
              // Create a repacked skin.
              Skin repackedSkin = skin.GetRepackedSkin($"{skinName}{repackedSuffix}", skeletonAnimation.SkeletonDataAsset.atlasAssets[0].PrimaryMaterial, out var runtimeMaterial, out var runtimeAtlas);
              // Use the repacked skin.
              skeletonAnimation.Skeleton.Skin = repackedSkin;
              skeletonAnimation.Skeleton.SetSlotsToSetupPose();
              skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton); // skeletonMecanim.Update() for SkeletonMecanim
      
              // You can optionally clear the cache after multiple repack operations.
              AtlasUtilities.ClearCache();
          }

      More info coming in the next post.

      I have profiled the memory from the build, follow along:

      1) Using the default skin, only the texture Aria_27 is loaded.

      2) Then I load another skin, which loads an additional texture: Aria_28. Please note that Aria_27 is used by most skins.

      3) I load another skin, which loads Aria_15 texture.

      4) I wait quite a while (I had the app opened while I write this) and the Aria_28 texture is still in memory.

      5) Bonus: I thought maybe in some way my repack of skin in item #2 (I did it to test repacking, it failed) might have hold a texture reference, but I changed Skin again, waited a few minutes and all textures are still in memory.

      @leonardo.bilck Thanks for sharing a reproduction project. We'll have a look at it and will get back to you as soon as we've figured out what's going wrong.

      Thanks, looking forward to your feedback!

      @leonardo.bilck The problems with your project setup were as follows:

      Addressables not unloading
      The Addressable Textures were not unloaded because you've added all Textures to the same Addressable group (i.e. the same asset bundle) and in the Addressable Groups settings of this group set Bundle Mode to Pack Together. Thus the asset bundle will only be unloaded once none of the group are used, which does not happen since you keep using Textures from the same group.

      So to fix this: Assign them to separete groups, or change Bundle Mode to Pack Separately or Pack Together by Label and assign the respective labels in the Addressable Groups panel. Once I've changed it to Pack Separately the profiler showed the desired outcome after switching forward and backward through your skins in the built executable:

      Please note that this is not specific to the spine-unity runtime but general Unity behaviour. Please make sure to be familiar with Unity Addressables before adding a layer of complexity by using the spine-unity runtime with it:
      https://docs.unity3d.com/Packages/com.unity.addressables@1.20/manual/AddressableAssetsDevelopmentCycle.html
      https://docs.unity3d.com/Packages/com.unity.addressables@1.20/manual/PackingGroupsAsBundles.html
      You might also find the following thirdparty writeup on asset unloading helpful:
      https://thegamedev.guru/unity-performance/memory-management-unloading/

      Repacking skins failing with white rectangles

      The problem's cause is Read/Write is disabled and can be found in the top of the Runtime Repacking documentation, in the box Important Note: If repacking fails or creates unexpected results, it is most likely due to any of the following causes".
      It's also listed in the FAQ with text "Why does my repacked skin display in the Editor but shows white polygons in the built executable?" and an image showing the same issue
      https://esotericsoftware.com/spine-unity-faq#Visual.
      Once I've enabled Read/Write and rebuilt Addressables, the issue was resolved.
      Please make sure to first check the FAQ and the general documenation when encountering any issues, likely you will find helpful information there.

      6日 後

      @Harald Thanks for your tips!

      It worked nicely in our project.
      I also noticed that the texture also gets unloaded when you repack a skin, which is quite nice (probably because the original Skin is not in use anymore, right?).

      One last question about memory optimization: to use compressed atlases for our Spine objects and to support repacking, is it enough to:

      • Change the target texture format when repacking
      • Ensure that all attachments follow the rules required by the target compression. For example: all with dimensions multiple of 4.

      Is that it?

      • Harald がこの投稿に返信しました。

        Very glad to hear you've got it worked out, thanks for letting us know!

        leonardo.bilck One last question about memory optimization: to use compressed atlases for our Spine objects and to support repacking, is it enough to:
        Change the target texture format when repacking
        Ensure that all attachments follow the rules required by the target compression. For example: all with dimensions multiple of 4.

        Is that it?

        An additional requirement is unfortunately that the attachment region would also need to be on multiples of 4 texels to be copyable. So your attachments each need to be of size multiple of 4, and each must be placed aligned at the 4x4 grid inside the atlas texture. That's due to the copy operation only being able to copy 4x4 grid cells as a whole from source to destination texture.

        I'm not 100% sure I got this:

        each must be placed aligned at the 4x4 grid inside the atlas texture.

        How can I guarantee that when exporting our Skeleton from Spine?

        5日 後

        @leonardo.bilck Sorry for the late reply. The alignment will automatically end up at the 4x4 grid if the attachment size is multiples of 4x4 and packing mode is not Polygons.

        So to let your attachments be located on the 4x4 grid, the following must be met:

        • All your attachments need to be of size multiple of 4
        • Strip whitespace x/y` must be disabled (so that the size is not reduced to anything other than 4x4)
        • Region Padding must be set to 0 or a multiple of 4.
        • Packing mode needs to be Rectangles (or you could also use Grid, but this would be quite wastful most of the time).