• Runtimes
  • Create SpineTexture in pixi-spine

Hello,
I need help with creating texture in Spine.

LIBRARY: I use @esotericsoftware/spine-pixi

GOAL: My main goal is to create a single html file with all assets.

So, I load all assets with base64 format.
In my atlas file, I create the first line as [IMG] to switch to the loaded asset local URL
this URL has after bundling some hash URLs like fe9ea.png and localhost:3000/fe9ea.png display image

ERROR: When I run atlas.setTextures(assetManager), It throws error with Texture. I assume that Pixi.Texture is not compatible with SpineTexture.

async loadSpine(img: HTMLImageElement) {
        const atlasDecode = atob(atlas_base64).replace('[IMG]', img.src);
        console.log(img.src); // http://localhost:3000/fe9ea6d84ce6ada8c62a385fc4ae8e13.png
        const jsonDecode = atob(json_base64);

        const atlas = new spine.TextureAtlas(atlasDecode);
        const assetManager = new spine.AssetManagerBase((imagePath) => {
            const pixiTexture = PIXI.Texture.from(imagePath);
            return new SpineTexture(pixiTexture);
        });

        assetManager.loadTexture(img.src, (asset) => {
            console.log(asset); // http://localhost:3000/fe9ea6d84ce6ada8c62a385fc4ae8e13.png
            atlas.setTextures(assetManager); //Cannot read properties of undefined (reading 'setFilters')

            const atlasAttachmentLoader = new spine.AtlasAttachmentLoader(atlas);
            const skeletonJson = new spine.SkeletonJson(atlasAttachmentLoader).readSkeletonData(jsonDecode);
            this.baseSpine = new spine.Spine(skeletonJson);
            this.baseSpine.state.setAnimation(0, 'idle', true);
            this.baseSpine.autoUpdate = true;
            this.baseSpine.state.timeScale = 0.5;
            this.baseContainer.addChild(this.baseSpine);
            Game.addToLevel(this.baseContainer);
            this.baseContainer.on('pointerdown', this.lipSync);
        });
    }

I had try with Asset.load base64 images and JSON works fine however atlas cant find img.

Please let me know how to solve it.

    Related Discussions
    ...

    To create a Spine texture in pixi-spine and load all assets in base64 format, you need to ensure that the textures are correctly mapped and compatible with Spine's requirements. Here is a step-by-step approach to achieve this:

    1. Load the Base64 Image: Convert the base64 image to a format that Pixi can use.
    2. Create a Custom Texture Loader: Ensure that the custom loader can handle the base64 image and create a compatible Spine texture.
    3. Set Textures in the Atlas: Properly set the textures in the atlas using the custom loader.

    Here is an example implementation:

    async function loadSpine(imgBase64, atlasBase64, jsonBase64) {
        // Decode the base64 strings
        const atlasDecode = atob(atlasBase64);
        const jsonDecode = atob(jsonBase64);
    
        // Create a new PIXI texture from the base64 image
        const img = new Image();
        img.src = `data:image/png;base64,${imgBase64}`;
        await img.decode();
        const pixiTexture = PIXI.Texture.from(img);
    
        // Create a custom Spine texture from the PIXI texture
        class SpineTexture extends spine.Texture {
            constructor(pixiTexture) {
                super();
                this.pixiTexture = pixiTexture;
            }
    
            setFilters(minFilter, magFilter) {
                this.pixiTexture.baseTexture.scaleMode = minFilter === spine.TextureFilter.Linear ? PIXI.SCALE_MODES.LINEAR : PIXI.SCALE_MODES.NEAREST;
            }
    
            dispose() {
                this.pixiTexture.destroy(true);
            }
        }
    
        // Create a custom asset manager
        const assetManager = new spine.AssetManagerBase((imagePath) => {
            return new SpineTexture(pixiTexture);
        });
    
        // Load the texture into the asset manager
        assetManager.loadTexture(img.src, (asset) => {
            // Create the atlas and set the textures
            const atlas = new spine.TextureAtlas(atlasDecode, (path) => assetManager.get(path));
            atlas.setTextures(assetManager);
    
            // Create the skeleton and set the animation
            const atlasAttachmentLoader = new spine.AtlasAttachmentLoader(atlas);
            const skeletonJson = new spine.SkeletonJson(atlasAttachmentLoader).readSkeletonData(jsonDecode);
            const baseSpine = new spine.Spine(skeletonJson);
            baseSpine.state.setAnimation(0, 'idle', true);
            baseSpine.autoUpdate = true;
            baseSpine.state.timeScale = 0.5;
    
            // Add the spine animation to the container
            const baseContainer = new PIXI.Container();
            baseContainer.addChild(baseSpine);
            Game.addToLevel(baseContainer);
            baseContainer.on('pointerdown', this.lipSync);
        });
    }
    
    // Example usage
    const imgBase64 = '...'; // Your base64 image string
    const atlasBase64 = '...'; // Your base64 atlas string
    const jsonBase64 = '...'; // Your base64 JSON string
    loadSpine(imgBase64, atlasBase64, jsonBase64);

    This code ensures that the base64 image is correctly converted to a Pixi texture and then wrapped in a custom Spine texture. The custom asset manager handles the loading and setting of textures in the atlas.

    I'm using the newest version of the library 4.2.57
    The above code does not use the newest version methods.
    new spine.TextureAtlas - do not have a secound argument.
    class SpineTexture extends spine.Texture { requred parameter in super.
    I changed code to use await img.decode(); and update SpineTexture to provided
    Still the same issue.

    How to write const atlas = new spine.TextureAtlas(atlasDecode, (path) => assetManager.get(path));

    In the current library?

    jkukuryk

    Hello and thanks for reaching us.

    The error you are showing is the following:

    atlas.setTextures(assetManager); //Cannot read properties of undefined (reading 'setFilters')

    That means that the TextureAtlasPage.setTexture (texture: Texture) method is receiving an undefined rather than a texture. Consequently, it fails at its first usage in texture.setFilters.

    You are calling TextureAtlas. setTextures whose code is:

    setTextures (assetManager: AssetManagerBase, pathPrefix: string = "") {
    	for (let page of this.pages)
    		page.setTexture(assetManager.get(pathPrefix + page.name));
    }

    This piece of code is responsible for setting the textures on all the atlas pages. To do that, it searches in the asset manager the respective page. Your error indicates that assetManager.get is not retrieving the texture just loaded.
    The assetManager.assets is the asset manager map that holds the assets.
    Check the page.name for each atlas page and the respective name into the assetManager.assets.

    There is another thing I noticed. SpineTexture constructor should receive a PixiBaseTexture, but you are passing a Pixi.Texture. You should use SpineTexture(pixiTexture.baseTexture).

    @Davide Thank You for Your replay.
    I changed Texture to BaseTexture (I got Texture from the first Spinebot reply).

     const pixiTexture = PIXI.BaseTexture.from(img);
     const assetManager = new spine.AssetManagerBase(() => {
                return new SpineTexture(pixiTexture);
      });
    
    assetManager.loadTexture(img.src, (asset) => {
      // Create the atlas and set the textures
      const atlas = new spine.TextureAtlas(atlasDecode);
    
      const test = new SpineTexture(pixiTexture);
      console.log(test.setFilters(spine.TextureFilter.Linear, spine.TextureFilter.Nearest));
      //it is OK - no errors about setFilter
      atlas.regions.forEach((r) => {
        r.page.setTexture(new SpineTexture(pixiTexture));
      }); //no errors
      console.log('atlas', atlas); // -all regions have SpineTexture
      atlas.setTextures(assetManager); //Cannot read properties of undefined (reading 'setFilters')

      jkukuryk

      I'm sorry, but the code you just provided has several problems. It might work somehow, but you are misusing several methods.

      For example, this piece of code:

      atlas.regions.forEach((r) => {
          r.page.setTexture(new SpineTexture(pixiTexture));
        }); 

      is bad. For each region, it creates a SpineTexture that is passed to the setTexture of the respective page. However, this methods loops over all the regions of the page. So, if you have 10 different regions in a page, you creates 10 different SpineTexture, but you will eventually have only the last one that is actually used.

      Then, you are initializing your asset manager with a texture loader function that does not even take as input the loaded texture, it just uses a globally defined pixiTexture.

      The error on atlas.setTextures(assetManager); is the very same error I explained you in the previous message. Did you check the content of the assetManager assets field?

      My suggestion is to restart from your previous code and follow the suggestions I already gave you.

      May I ask you why are you using our Asset Manager, while PIXI has its own Asset Manager that even spine-pixi uses?
      I think you easily reach you goal with a slight modification of the basic example.

      I provide those atlas.regions loop to just check if Texture has valid methods like setFilter.
      I somewhere on the forum found that your Asset Manager could work.
      When I use Assets from pixi I was unable to make atlas read the base64 file.

      I have tried this solution previously:
      With the same error that lead me to Your Asset manager

        const atlas = btoa(atob(atlasImgBase64).replace('[IMG]', `data:image/png;base64,${sprite_base64}`));
              await Assets.loadBundle(cat);
              Assets.add({
                  alias: 'skeleton',
                  src: `data:application/json;base64,${json_base64}`,
              });
              Assets.add({
                  alias: 'atlas',
                  src: `data:plain/text;base64,${atlas}`,
              });
              await Assets.load(['skeleton', 'atlas']); /// atlas = null
              const catSpine = pixiSpine.Spine.from('skeleton', 'atlas', {
                  autoUpdate: true,
              });

        jkukuryk

        The following code is working for me. I added comments to explain step by step what I did.
        Since you are using base64 assets, you need to do assets loading by yourself.
        The json skeleton can be loaded using the json loader of Pixi.
        The atlas text file has to be loaded as a txt. Consequently, you have to initialize the TextureAtlas by yourself.
        Keep the original page names in the texture atlas and create a map that maps the name to the respective base64 data.
        Load all base64 images as normal images using the Pixi asset manager, together with the two other assets above.
        Then, when you have initialized you Texture Atlas, loop over its pages, get the respective image (previously loaded) and cal setTexture of that page using the image just obtained from the asset cache.

        // create a map that maps the page name to the respective base64 image
        const spriteMap = {
          "page1.png": `data:image/png;base64,${sprite1_base64}`,
          "page2.png": `data:image/png;base64,${sprite2_base64}`
        }
        
        // add the atlas pages to the assets to load
        const spritesToLoad = Object.keys(spriteMap).map(pageName => {
          PIXI.Assets.add({
            alias: pageName,
            src: spriteMap[pageName],
          });
          return pageName;
        })
        
        // add the base64 atlas to the pixi asset manager
        PIXI.Assets.add({
            alias: 'atlasTxt',
            src: `data:plain/text;base64,${atlas}`,
            loadParser: 'loadTxt'
        });
        
        // add the base64 skeleton to the pixi asset manager
        PIXI.Assets.add({
            alias: 'skeleton',
            src: `data:application/json;base64,${json_base64}`,
            loadParser: 'loadJson'
        });
        
        // load the assets
        const { atlasTxt, skeleton } = await PIXI.Assets.load(['atlasTxt', 'skeleton', ...spritesToLoad]);
        
        // initialize texture atlas and add it to the Pixi cache
        const textureAtlas = new spine.TextureAtlas(atlasTxt);
        PIXI.Assets.cache.set('atlas', textureAtlas)
        
        // loop through each atlas page
        for (const page of textureAtlas.pages) {
        
          // get the sprite associated to the name of this page
          const sprite = PIXI.Assets.get(page.name);
        
          // set the sprite to the page and all its regions
          page.setTexture(spine.SpineTexture.from(sprite.baseTexture));
        }
        
        const catSpine = spine.Spine.from('skeleton', 'atlas', {
            autoUpdate: true,
        });

        @Davide Thank You. It works,
        My updated code with replace [IMG] value in atlas:

        async loadSpine() {
                PIXI.Assets.add({
                    alias: 'spineImage',
                    src: `data:image/png;base64,${sprite_base64}`,
                });
                const atlas = btoa(atob(atlas_base64).replace('[IMG]', 'spineImage'));
                PIXI.Assets.add({
                    alias: 'atlasTxt',
                    src: `data:plain/text;base64,${atlas}`,
                    loadParser: 'loadTxt',
                });
        
                PIXI.Assets.add({
                    alias: 'skeleton',
                    src: `data:application/json;base64,${json_base64}`,
                    loadParser: 'loadJson',
                });
        
                const { atlasTxt, skeleton } = await PIXI.Assets.load(['atlasTxt', 'skeleton', 'spineImage']);
        
                const textureAtlas = new spine.TextureAtlas(atlasTxt);
                PIXI.Assets.cache.set('atlas', textureAtlas);
        
                for (const page of textureAtlas.pages) {
                    const sprite = PIXI.Assets.get(page.name);
                    page.setTexture(spine.SpineTexture.from(sprite.baseTexture));
                }
        
                const catSpine = spine.Spine.from('skeleton', 'atlas', {
                    autoUpdate: true,
                });