• RuntimesUnity
  • Swapping atlases based on screen resolution

I am trying to find out how I am supposed to swap to different scaled atlases depending on the screen resolution.

I have seen this mentioned several times in the documentation but couldn't find where it is actually explained.

I have exported the atlas from spine in different scales but I'm not sure what the expected route is for swapping those atlases (both on game launch and when the resolution is changed).

Related Discussions
...

All of my spine objects are defined in scenes or prefabs. I don't manually load the spine assets.

Is there a route for doing this in my situation?

Ideally I would like something like the way that Unity does this for its own sprite atlases: the 'SpriteAtlasManager.atlasRequested' callback.

Then I could load the spine atlases in the same way as everything else.

@JDBB Sorry that Spinebot's answer was not helpful. I've deleted it since it was rather misleading.

How are you loading your assets? Do you want to use Asset Bundles, Addressables, or something else?

The SkeletonDataAsset references a SpineAtlasAsset which itself references one Material for each atlas page Texture. Now when your prefab is loaded, everything references is loaded as well, defined by the normal Unity behaviour. If you have some assets in e.g. a different Asset Bundle or Addressable, loading will stop there and you could load the desired bundle and re-assign the Materials at the SpineAtlasAsset, or Textures at the Materials.

We have experimental spine-unity on-demand loading and addressables UPM packages available, which mainly cover delayed loading, see the posting at this issue ticket here:
EsotericSoftware/spine-runtimes1890

  • com.esotericsoftware.spine.on-demand-loading
  • com.esotericsoftware.spine.addressables

We would be very glad if you could evaluate whether these packages fit your needs already, or if you are missing some features to accomplish your goals. Any input helps us making the tools better.

--
Another alternative, mentioning for sake of completeness:
The easiest solution would be to cover different resolutions via Quality Settings, using Half and Quarter texture resolution. I assume that you have decided against this workflow though.

Thanks for the reply.

I embarrassingly wasn't aware of the option to use mipmaps/quality setting to solve this issue. I am looking into that now to see if I can take the easy route. I haven't needed to work with mipmaps before so any advice that you have would be appreciated.

Do you suggest that I use the mipmap bias button for the atlases when using mipmaps like this? Or should I just do that if I see issues?

I am using addressables in the project so I may need to try out your experimental package later down the line.

    JDBB I embarrassingly wasn't aware of the option to use mipmaps/quality setting to solve this issue. I am looking into that now to see if I can take the easy route. I haven't needed to work with mipmaps before so any advice that you have would be appreciated.

    Glad to hear this opened up a new alternative!

    Do you suggest that I use the mipmap bias button for the atlases when using mipmaps like this? Or should I just do that if I see issues?

    The mip-map bias button (and mip-map bias in general) does not affect loading, it rather changes the rendering of a texture when mip-maps are enabled. With mip-map-bias of e.g. -0.5 it's basically saying "take more of a sharper mipmap" when sampling two levels of the mip-map pyramid. The target resolution (texel density on screen) determines which mip-maps will be used, resulting in e.g. mip-level 1.3.
    1.3 meaning sampling both mip-levels 1 and 2, weighting level 1 by 0.7 and level 2 by 0.3.
    With a bias of -0.5 this would just change from 1.3 to 0.8, now sampling level 0 and level 1 (with weights 0.2 and 0.8). However, all mip-map-levels of the texture are loaded.

    What you could do to avoid loading all mip-levels is by utilizing Unity's mipmap streaming (texture streaming) feature:
    https://docs.unity3d.com/Manual/TextureStreaming.html
    Admittedly I'm not familiar with the details regarding how loading can be controlled.
    Please note that this feature might be limited to some platforms and rendering APIs, so please make sure to e.g. test it on low-end mobile devices as well in case you want to also target these.

    The more "classical" ways to control texture loading in regards to quality would be the Quality Settings
    Global Mipmap Limit or Mipmap Limit Groups workflow, where you just set a limit to e.g. "Half Resolution" which leads to never loading the full-resolution (level 0) mip-map into memory:
    https://docs.unity3d.com/Manual/class-QualitySettings.html
    These quality settings can be changed at runtime, so note that it does not reduce your game's download size, only the loaded size in memory.

    Alternatively, you could also set Texture Importer settings to limit the texture resolution by Texture for different build targets. This might be too coarse for your purposes however.

    Thanks for all the help, much appreciated

    Unfortunately I have been unable to get good results on all resolutions using the mipmap method. I think because the mimpmaps generated by unity are much worse looking than an image exported at the same size.

      JDBB I think because the mimpmaps generated by unity are much worse looking than an image exported at the same size.

      If you didn't hand-draw (manually author) the lower-resolution textures and just used Spine's Atlas Export Scale and Resample feature, then you should have similar capabilities with Unity's mipmap generation or texture resizing (you can select the same filtering modes there), plus actually some additional filtering options.

      So you might want to check your settings at both the export and the mipmap filtering.
      Could you perhaps share some screenshots showing the settings in Spine and Unity? And if possible, the desired result and what you instead get as a result in Unity?

      4 dias depois

      I was unclear about what my problem is. I'm still in the middle of trying to figure out my pipeline so nothing is set in stone yet.

      I was experimenting with the mipmaps in unity thanks to your suggestion but found that the mipmaps that unity auto generates weren't good enough for my needs (nothing to do with spine).

      I need to support a wide range of resolutions, all the way from 4k to 720p (for the switch). On top of that I need to be able to zoom in a fair amount during gameplay, so I really need my sprite assets to be at 8k resolution.

      When scaling an 8k image down to switch sizes I end up with a bad looking sprite, so I have come to the conclusion that I need to supply my own (manually authored) set of sprites at different scales. My sprites are made entirely using vector software (illustrator at the moment) so I have the option to just export sprites at different scales. When I do this I get much better results than letting unity auto scale them down (as expected).

      For a basic unity SpriteRenderer I have written some code that loads the correct resolution sprite (from an atlas) using addressables and makes sure that no direct references to the sprites exist before building. This all seems to be working well.

      There may also be a route where I use some other software to create dds files and so supply my own mipmaps to unity. I haven't looked into this route yet.

      Now I need to figure out how to do the same thing for spine objects but I'm struggling, I'm hoping you can help me with that.

      I have seen that I can use the spine texture packer to make a standalone atlas using my lower resolution sprites but I don't know if I could just swap the textures out as I don't think there is any guarantee that the
      sprites will be packed into the same place in all the different atlases. I'm not sure if it is possible to swap the atlas itself out?

      To summerise:

      • I want to be able to supply multiple sets of sprites that have been exported from illustrator at different scales.
      • I want to include spine objects in prefabs an scenes and have them use addressables to load the textures/atlases/sprites of the requested scale.
      • I want to not have to load one set of textures then only after that load the correct set of textures as that would increase loading time.
      • Ideally I would also like to be able to not include the higher res textures when I build for the switch (not essential).
      • Ideally I would be able to swap the texture scale after the scene/prefab is already on screen (so that the game can adjust when the resolution changes, not essential).

      I am stuck until I can figure this out so any help would be much appreciated.

        JDBB When scaling an 8k image down to switch sizes I end up with a bad looking sprite, so I have come to the conclusion that I need to supply my own (manually authored) set of sprites at different scales. My sprites are made entirely using vector software (illustrator at the moment) so I have the option to just export sprites at different scales. When I do this I get much better results than letting unity auto scale them down (as expected).

        Thanks for the clarification, this makes sense, automatic downsampling (via Unity's mipmap generation as well as via Spine Atlas Export Scale e.g. 0.5 or 0.25) will be inferior to that.

        JDBB I have seen that I can use the spine texture packer to make a standalone atlas using my lower resolution sprites but I don't know if I could just swap the textures out as I don't think there is any guarantee that the
        sprites will be packed into the same place in all the different atlases.

        There is indeed no guarantee if you pack twice with different settings or resolutions.

        JDBB I'm not sure if it is possible to swap the atlas itself out?

        Yes, this is possible and perfectly valid. The AtlasAsset (and the referenced atlas.txt file) contains all the necessary information.

        JDBB I want to be able to supply multiple sets of sprites that have been exported from illustrator at different scales.

        By that I assume you mean that you want to author the attachment images for the Spine project in different resolutions, and then pack them to different sets of atlases, not sharing the same layout (see above).

        JDBB I want to not have to load one set of textures then only after that load the correct set of textures as that would increase loading time.

        If you have a different AtlasAsset for each resolution with different atlas layout, you unfortunately can't use the Spine Addressables Extension on-demand loading module mentioned earlier, as this only replaces textures at materials and does not (yet) support different atlas layouts (i.e. does not support swapping out the atlas asset with a different one).

        I assume this is not an option, but you could load a low-resolution atlas first (with little overhead) and then swap out the low-resolution atlas with the desired target resolution atlas.

        If you need to avoid loading a low-resolution placeholder atlas asset first, you could programmatically at runtime load the SpineAtlasAsset first and then construct the SkeletonDataAsset, as described here:
        https://esotericsoftware.com/spine-unity-main-components#Advanced-Instantiation-at-Runtime

        Alternatively, you could write your own AttachmentLoader to customize loading, as described recently on this forum thread:
        https://esotericsoftware.com/forum/d/26266-how-to-export-texture-just-for-one-skin-with-linked-mesh

        JDBB Ideally I would also like to be able to not include the higher res textures when I build for the switch (not essential).

        You could either always include just the lowest-resolution atlas, or download all atlas assets via addressables on-demand.

        JDBB Ideally I would be able to swap the texture scale after the scene/prefab is already on screen (so that the game can adjust when the resolution changes, not essential).

        You can swap out the atlas assets by assigning a different atlas asset at SkeletonDataAsset.atlasAssets and then call skeletonDataAsset.Clear() to clear the common loaded SkeletonData. Then at all already loaded SkeletonAnimation objects you would need to call skeletonAnimation.Initialize(true); to reload and re-assign the proper skeleton data.

        In general there are multiple ways to achieve your goal. I hope this hasn't raised more questions than it has answered. 🙂

        Thanks, I will work on testing some of this later when I am able.

        When you turn an attachment into a mesh, where is this mesh vertex data stored?

        If I were to export a set of lower resolution sprites into an atlas using the spine texture packer, then manually load the low res atlas in unity and assign it to the skeleton data (that was exported with the high res atlas), would this break any animations that rely on having mesh attachments?

          JDBB When you turn an attachment into a mesh, where is this mesh vertex data stored?

          What do you mean by "when you turn an attachment into a mesh", do you mean in the Spine Editor?

          If you want to know whether mesh attachments directly store atlas texture coordinates: no, they store coords relative to the atlas region boundaries, and the atlas regions are defined by the atlas. Thus the atlas layout can be different and mesh attachments still work fine.

          JDBB If I were to export a set of lower resolution sprites into an atlas using the spine texture packer, then manually load the low res atlas in unity and assign it to the skeleton data (that was exported with the high res atlas), would this break any animations that rely on having mesh attachments?

          It does not break anything.

          Hey, that answers my question, thanks.

          I thought that I had it working by simply setting the 'atlas assets array' to be empty in the 'skeleton data asset'. This allowed the 'skeleton animation' object in my scene to load up fine with no atlases. Then I could load the desired atlas using addressables and assign it as you explained before.

          This worked fine in the editor but failed in a build with the error: "Error reading skeleton JSON file for SkeletonData asset: skeleton_SkeletonData"

          In the inspector for the 'skeleton data asset' it says "AtlasAsset array is empty. Skeleton's attachments will load without being mapped to images.". Which sounds like the exact behavior that I want and also sounds like having an empty atlas array is supported.

          Does this suggest that the error that I'm getting in my build is actually a bug?

          I have also seen references to 'RegionlessAttachmentLoader' in the spine source which also may suggest that having no atlas is supposed to work.

          I tried to look into making a custom attachment loader but I couldn't see how to make the 'skeleton animation' object in my scene use my custom loader.

          Sorry for so many questions, hopefully nearly there. Thanks again for all the help.

          I have done some more work on this and everything seems to be working perfectly in both the build and in the editor, apart from the error that gets thrown when running the build (mentioned above).

          As soon as I have a skeleton data asset with an empty atlas list then I get the error in the build.

          I could just ignore the error but I would much rather fix it.

          Glad to hear you've got a working solution already! Sorry for the troubles with the error message, we will check whether we can remove it safely. I'll get back to you here on the forum once we have a fix ready.

          @JDBB Unfortunately we could not reproduce the issue, we received no error message when entering play mode or building with a scene which uses a skeleton with the SkeletonDataAsset's Atlas Assets size set to 0.

          Which exact version of the spine-unity runtime (name of the unitypackage, also listed in Assets/Spine/version.txt or in the Package Manager) are you using?

          The version of the Unity runtime is: 4.2.67
          The C Sharp runtime is: 4.2.23
          I am using Unity version: 2023.2.20

          I did have the addressables and on demand loading packages installed at one point but have since removed them from the project.

          @Harald

          I have just made a fresh project to test on and I am still seeing the error.

          I can send you the project if you would like.

          Steps that I took:

          1. Made a new universal 2D project in Unity 2023.2.20
          2. Installed the two spine packages via the package manager
          3. Dragged a spine export into unity, this generated the skeleton data asset
          4. Dragged the skeleton data asset into the starting scene and let it generate the skeleton animation game object
          5. In the inspector for the skeleton data asset I set the atlas assets list count to be 0
          6. Built the project

          Hopefully I didn't miss anything.

          In case it helps the full error is:

          Error reading skeleton JSON file for SkeletonData asset: skeleton_SkeletonData
          Error reading attachment: God_Ray_0, skin: default
          at Spine.SkeletonJson.ReadSkeletonData (System.IO.TextReader reader) [0x013d8] in .\Library\PackageCache\com.esotericsoftware.spine.spine-csharp@568e5ef049\SkeletonJson.cs:369
          at Spine.Unity.SkeletonDataAsset.ReadSkeletonData (System.String text, Spine.AttachmentLoader attachmentLoader, System.Single scale) [0x00017] in .\Library\PackageCache\com.esotericsoftware.spine.spine-unity@568e5ef049\Runtime\spine-unity\Asset Types\SkeletonDataAsset.cs:295
          at Spine.Unity.SkeletonDataAsset.GetSkeletonData (System.Boolean quiet) [0x000c4] in .\Library\PackageCache\com.esotericsoftware.spine.spine-unity@568e5ef049\Runtime\spine-unity\Asset Types\SkeletonDataAsset.cs:194
          UnityEngine.Debug:ExtractStackTraceNoAlloc (byte*,int,string)
          UnityEngine.StackTraceUtility:ExtractStackTrace () (at C:/build/output/unity/unity/Runtime/Export/Scripting/StackTrace.cs:37)
          UnityEngine.DebugLogHandler:Internal_Log (UnityEngine.LogType,UnityEngine.LogOption,string,UnityEngine.Object)
          UnityEngine.DebugLogHandler:LogFormat (UnityEngine.LogType,UnityEngine.Object,string,object[])
          UnityEngine.Logger:Log (UnityEngine.LogType,object,UnityEngine.Object)
          UnityEngine.Debug:LogError (object,UnityEngine.Object)
          Spine.Unity.SkeletonDataAsset:GetSkeletonData (bool) (at ./Library/PackageCache/com.esotericsoftware.spine.spine-unity@568e5ef049/Runtime/spine-unity/Asset Types/SkeletonDataAsset.cs:197)
          Spine.Unity.SkeletonRenderer:Initialize (bool,bool) (at ./Library/PackageCache/com.esotericsoftware.spine.spine-unity@568e5ef049/Runtime/spine-unity/Components/SkeletonRenderer.cs:476)
          Spine.Unity.SkeletonAnimation:Initialize (bool,bool) (at ./Library/PackageCache/com.esotericsoftware.spine.spine-unity@568e5ef049/Runtime/spine-unity/Components/SkeletonAnimation.cs:189)
          Spine.Unity.SkeletonRenderer:Awake () (at ./Library/PackageCache/com.esotericsoftware.spine.spine-unity@568e5ef049/Runtime/spine-unity/Components/SkeletonRenderer.cs:408)
          (Filename: ./Library/PackageCache/com.esotericsoftware.spine.spine-unity@568e5ef049/Runtime/spine-unity/Asset Types/SkeletonDataAsset.cs Line: 197)