• Runtimes
  • [spine-ts] loading png assets as needed for web

joo

spine-webgl uses the AssetManager that extends the spine-core AssetManagerBase.

As you can see, there is the loadTextureAtlas method that takes an atlas file and loads all the atlas .png pages described in the atlas.

Once the atlas and its pages are loaded, a TextureAtlas file is created. This file contains all the png atlas pages loaded and all the regions where the single images are located within the pages.
It indeed provides a convenient findRegion method that takes an attachment path and gives back the respective region.

The TextureAtlas is passed to the AtlasAttachmentLoader that is responsible for creating the attachments of the skeleton. For instance, when it is necessary to create a region attachment, the newRegionAttachment method is called. That uses the findRegion method of the TextureAtlas described above.

Depending on your specific need, you have to create your own AttachmentLoader and your own AssetManager that loads attachments/textures on demand/lazily.

I also suggest you read our runtime guide page on Loading Skeleton Data.
There is also this conversation that might be interesting, even though it is more oriented on attachment lazy loading rather than textures.
It might also be interesting to read the Texture Packing page of the user guide and especially this section to easily split your skins into several atlas pages.

  • Responderam a isso joo.
    Related Discussions
    ...
    9 dias depois

    Davide
    Hi Davide,
    Do you recommend one .atlas file for everything, or one .atlas file per texture png file? So also only loading in the necessary .atlas files as needed. Wondering if one is much more difficult to implement than the other.

    Thank you!

      joo

      If you create your own loader, it really depends on your implementation.
      Here are some possible ideas.

      Probably, having a single .atlas is the easiest way. However, in that case, if you don't want to load the textures all together, you have to write your implementation of loadTextureAtlas that does not load the textures and consequently does not set the textures for the regions.
      This implies that you have to load the textures on demand before they are used.
      To do that, you can use loadTexture from the AssetManager. Then call the setTexture on the respective atlas page you just loaded. You can write a method that takes as input the atlas page for which you want to load the texture and does the two things mentioned above.
      After that, you should be safe to use your animations/skins that use the atlas pages just loaded.

      This is an example of what I'm saying.
      Add a loadTextureAtlasButNoTextures method to AssetManagerBase (you can extend it):

      	loadTextureAtlasButNoTextures (path: string,
      		success: (path: string, atlas: TextureAtlas) => void = () => { },
      		error: (path: string, message: string) => void = () => { },
      		fileAlias?: { [keyword: string]: string }
      	) {
      		path = this.start(path);
      		this.downloader.downloadText(path, (atlasText: string): void => {
      			try {
      				this.success(success, path, new TextureAtlas(atlasText));
      			} catch (e) {
      				this.error(error, path, `Couldn't parse texture atlas ${path}: ${(e as any).message}`);
      			}
      		}, (status: number, responseText: string): void => {
      			this.error(error, path, `Couldn't load texture atlas ${path}: status ${status}, ${responseText}`);
      		});
      	}

      This is the very same code of loadTextureAtlas without the texture loading.

      Then, you can use this for example in a spine-webgl app.

      async loadAssets(canvas) {
      	// Load the skeleton file.
      	canvas.assetManager.loadJson("assets/skeleton.json");
      	// Load the atlas and its pages.
      	// canvas.assetManager.loadTextureAtlas("assets/skeleton.atlas");
      
      	const atlas = await new Promise(resolve => {
      		canvas.assetManager.loadTextureAtlasButNoTextures("assets/skeleton.atlas", (_, atlas) => resolve(atlas));
      	});
      
      	// assuming we have two pages
      	const [page1, page2] = atlas.pages;
      
      	// load first page immeaditely
      	canvas.assetManager.loadTexture(`assets/${page1.name}`, (_, texture) => page1.setTexture(texture));
      
      	// load second page after two seconds
      	setTimeout(() => {
      		canvas.assetManager.loadTexture(`assets/${page2.name}`, (_, texture) => page2.setTexture(texture));
      	}, 2000);
      }

      I've used loadTexture in the loadAssets, but you can use it wherever you want.


      If you want to use multiple .atlas files, the idea should be pretty similar. However, the AtlasAttachmentLoader is capable of using a single TextureAtlas. Consequently, you have to write your own.

      • Responderam a isso joo.

        Davide
        good to know, thanks!

        Hi @Davide ,

        Before digging into the custom loading, I want to make sure I'm extending the class right. Is this the right way to extend the AssetManager?

        class CustomAssetManager extends spine.AssetManager {
          loadTextureAtlasButNoTextures(path, success = () => {}, error = () => {}) {
              ...
           }
        }
        class App {
              constructor() {
                this.canvas = null;
                this.assetManager = new CustomAssetManager();
                this.atlas = null;
                this.skeletonData = null;
                this.skeleton = null;
                this.animationState = null;
                this.lastBounds = {};
              }
        
              loadAssets(canvas) {
                this.assetManager.loadBinary('spine-dynamic/animee.skel');
                this.assetManager.loadTextureAtlas(
                  'spine-dynamic/chibi_04_flipbooktest.atlas',
                );
        
             initialize(canvas) {
                this.canvas = canvas;
        
                let atlas = this.assetManager.require(
                  'spine-dynamic/chibi_04_flipbooktest.atlas',
                );
        ...
        }
        
        const appInstance = new App();
        
        new spine.SpineCanvas(canvas, {
              pathPrefix: '/',
              app: appInstance,
            });

        I'm getting errors because in SpineCanvas's waitForAssets, it calls this.assetManager but that is not using my custom asset manager, so initialize() gets called before loadAssets() can finish. Am I not extending the asset manager properly?

        Thanks!

        got passed the issue by setting the custom asset manager to the spineCanvas instance, but let me know if there is a better practice!

         const appInstance = new App();
        
            const spineCanvas = new spine.SpineCanvas(canvas, {
              pathPrefix: '/',
              app: appInstance,
            });
        
            spineCanvas.assetManager = appInstance.assetManager;

        there are some new errors now that I'm looking into now, so I'm not certain if this patch was correct

          joo

          Oh, sorry, that was my fault. I could have been more precise.
          The SpineCanvas class is just a simple utility to quickly use spine-webgl. It uses the default AssetManager of spine-webgl.
          You can create your own custom SpineCanvas by copying its code, or you can also extract the logic you need from it without creating a dedicated class.

          • Responderam a isso joo.

            Davide

            Ah ok, no worries. Thanks for your patience and bearing with me while I'm still onboarding onto Spine and asking lots of questions 🙏

              got it working 👍

                joo

                joo Ah ok, no worries. Thanks for your patience and bearing with me while I'm still onboarding onto Spine and asking lots of questions 🙏

                We're more than happy to assist riggers, animators and developers in using the editor and the runtimes! Feel free anytime to ask anything that is unclear to you 🙂

                joo got it working 👍

                I'm glad to hear that!

                Hi @Davide,

                I'm seeing if I can do the same with spine-pixi now. I see in spine-pixi's docs that "The individual texture atlas page images are loaded transparently without the need to explicitly load them."

                Do you know if it's still possible to explicitly load them dynamically instead?

                Thanks

                  joo

                  You are right, the spine-pixi atlasLoader loads all atlas pages transparently.
                  This implies you cannot use it to achieve your goal. However, as you can see from the code it just does what our spine-webgl loadTextureAtlas does.

                  I didn't try it, but I guess that if you load the atlas as a txt file, instance the TextureAtlas with the atlas txt data, add the TextureAtlas to the asset cache, and eventually initialize your Spine game object, then you can defer the texture pages loading when you want.

                  Let me know if you need any help with the code 🙂

                  3 meses depois

                  Hi Davide!

                  It has been a while now and I'm revisiting this again, where I only want to load the textures I need depending on what skins are set, and not the full list of textures.

                  I ended up using spine-player instead of spine-webgl because spine-player already has a lot of features I need that is built in. However, I'm not sure how to apply a custom asset manager and custom asset loader with spine-player without forking spine-player and adding my custom asset manager/loader. Would love some advice on how to move forward on this.

                  Thanks!

                    joo

                    Unfortunately spine-player isn't so easy to manipulate without a fork.
                    Which functionalities the player has that the widget does not have?

                    • Responderam a isso joo.

                      Davide

                      It's just that the player has a lot of features already implemented that I need, whereas with spine-webgl, I would need to implement a lot of it from scratch. That's why I was hoping I could just use the player and build on top of it. As for the widget, I'm waiting for it to be officially released before using it in production!

                      Thanks

                        joo

                        We were busy with the pixi-v8 runtime release, but now that we've lanuched it, I'll resume on working on the widget.

                        Regarding the Player customization, if you don't want to fork it, you'll need to monkey patch/extend the AssetManager or the Player overriding the method that loads the texture.
                        I'll give it a try tomorrow morning and if it works, I'll share with you a piece of code that you can use as a base.

                        • Responderam a isso joo.

                          Davide

                          Ok, thanks so much!