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

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!

    Related Discussions
    ...

    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.

    • joo respondió a esto

      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.

        • joo respondió a esto

          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 más tarde

            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!

              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.

                • joo respondió a esto