• Runtimes
  • Using the same assets in Pixi and Spine

Hey,
we are using the same assets in Pixi and Spine and we want to reduce the duplication.

Is it better to:

  1. Have the assets in Pixi (packed with TexturePacker) and use slots in Spine to attach them there?
  2. Have the assets in Spine and use them in Pixi from there as well (if that's even possible)?

If having them in Spine atlas is better, do I have to write some script that would parse the atlas and create a Texture from it to Pixi?

    Related Discussions
    ...

    When using the same assets in both Pixi and Spine, you have a couple of options to reduce duplication.

    1. Using Pixi's TexturePacker: You can pack your assets using TexturePacker for Pixi and then use slots in Spine to attach them. This approach allows you to manage your assets primarily in Pixi, which might be beneficial if Pixi is your main rendering engine.

    2. Using Spine's Texture Atlas: You can also use Spine's texture atlas and then load these assets into Pixi. This is possible, but it requires some additional work. You would need to parse the Spine atlas and create textures in Pixi. This involves loading the atlas text file as a text and initializing the TextureAtlas manually. You would then map the page names to the respective base64 data or image files and set the textures accordingly in Pixi.

    If you choose to use Spine's atlas, you will indeed need to write a script to parse the atlas and create textures for Pixi. This involves loading the JSON skeleton with Pixi's JSON loader and the atlas text file as a text, then initializing the TextureAtlas manually and setting the textures for each page.

    michalvadak

    For Spine GameObjects, it's always best to have all character images on a single texture atlas page whenever possible. This ensures the character can be rendered without breaking batching.

    The addSlotObjects method is merely a convenience function and should be avoided when possible. Typically, the Pixi object drawn using addSlotObjects comes from a different texture, which breaks batching. If the slot is in the middle, rendering the character would now require three draw calls.

    Spine atlases are primarily designed for Spine GameObjects, but nothing prevents you from reusing the character’s images for other purposes. However, individual character images are not recreated as Pixi sub-textures from the texture atlas PNG. Instead, meshes and UVs are used to achieve this.

    If you want to reuse an image packed into an atlas page outside of a Spine GameObject, it’s your responsibility to create a Pixi sub-texture for it. You can use the atlas data to determine the image’s position and size within the respective texture atlas page.

    I did a quick experiment with this—here’s the code:

    // load the atlas and automatically the textures
    const atlas = await PIXI.Assets.load("https://esotericsoftware.com/files/examples/latest/spineboy/export/spineboy-pma.atlas");
    
    // util to get a new sprite, given an attachment name
    const getSprite = attachmentName => {
        // find the region with the given attachment name
        const region = atlas.regions.find(element => element.name === attachmentName);
    
        // get the respective page texture
        const originalTexture = region.texture.texture;
    
        // determine the position of the desired attachment, considering rotation
        const frame = {
            x: region.x,
            y: region.y,
            width: region.rotate === undefined ? region.width : region.height,
            height: region.rotate === undefined ? region.height : region.width,
        };
    
        // creating the new sub texture
            const texture = new PIXI.Texture({
            source: originalTexture.baseTexture,
            crop: new PIXI.Rectangle(frame.x, frame.y, frame.width, frame.height),
            frame,
        });
    
        // creating a new sprite
        return new PIXI.Sprite(texture);
    }
    
    const miniSpineBoy = new PIXI.Container();
    
    const footR = getSprite("front-foot");
    footR.x = 85;
    footR.y = 135;
    footR.scale.set(.70)
    miniSpineBoy.addChild(footR);
    
    const head = getSprite("head");
    miniSpineBoy.addChild(head);
    
    const eyeL = getSprite("eye-surprised");
    eyeL.x = 45;
    eyeL.y = 75;
    eyeL.scale.set(.75)
    miniSpineBoy.addChild(eyeL);
    
    const eyeR = getSprite("eye-indifferent");
    eyeR.x = 70;
    eyeR.y = 80;
    eyeR.scale.set(.75)
    miniSpineBoy.addChild(eyeR);
    
    const mouth = getSprite("mouth-oooo");
    mouth.x = 50;
    mouth.y = 120;
    miniSpineBoy.addChild(mouth);
    
    const footL = getSprite("front-foot");
    footL.x = 25;
    footL.y = 135;
    footL.scale.set(.70)
    miniSpineBoy.addChild(footL);
    
    miniSpineBoy.x = window.innerWidth / 2 - miniSpineBoy.width / 2;
    miniSpineBoy.y = window.innerHeight / 2 - miniSpineBoy.height / 2;;
    app.stage.addChild(miniSpineBoy);

    And here the result:

    As you can see, I’m not even creating a Spine GameObject here, just loading the atlas. I haven’t considered any special cases related to how the texture packing is done, but for reusing a few images, this approach should work.

    That's a cool example! Thank you!

    How about I will have multiple skins for my game... but each skin should be its own game instance and the textures for each skin should be packed separately as I don't want to pollute the atlas for skin1 with assets for skin2 as this would increase the size of the entire game.

    Would this be a case where it would be better to do all the spine animations with just slots so Pixi can manage this and only load the necessary assets? Or is there a way to do this in spine without having to create a duplicate of the spine project?

      michalvadak

      I'm not entirely sure I understand how your game and game instances work.
      From what I gather, each game instance uses only one skin. Because of this, you want to pack the skin images into separate atlases. Please correct me if I'm wrong.

      Keep in mind that you don’t need to duplicate your projects to create separate atlas pages.
      If you organize the skin images into different folders, the Spine Texture Packer is smart enough to pack them into separate atlas pages, as explained in this user guide section.

      However, even if you use multiple atlas pages, once you load your atlas file (txt), all atlas pages (pngs) are loaded into memory.
      If you want to load only specific pages into memory, you'll need to modify our atlas loader and implement a deferred loading strategy.
      If in your game instance you never need of other skins, you could also just remove the atlas page info not needed from the atlas text file.

      Additionally, if you have multiple skin variations, you don’t need to add all of them directly into Spine. Instead, create a single dummy version in Spine, and generate the variations externally. Then, you can use the texture packer separately to create different atlas variations. I recommend reading the full Spine Texture Packer user guide for a deeper understanding.

      You can also use the CLI to pack your textures, creating a streamlined pipeline for generating skin variations.

      As a last resort, you could use an external Texture Packer to provide assets to the Spine skeleton.
      If you use addSlotObjects, it will generate a large number of Pixi elements. Alternatively, you can create a custom AttachmentLoader to feed images directly to the skeleton.

        Let me give you some more context.

        Let's say I have 3 pixi games (same game code, just different assets). All of them will use a single Spine project as the game is the same, just with a different skin.

        If I leave all the assets in Spine it will pack them into atlas pages (png) together with info about all in the atlas file (txt). I don't want this as it will increase the size of the games with each skin.

        What I need is to have atlas pages (png) with assets only for game1 and atlas file (txt) with information about assets only for game1 so I can load only that specific game skin.

        Same for game2, game3 etc.

        Davide Additionally, if you have multiple skin variations, you don’t need to add all of them directly into Spine. Instead, create a single dummy version in Spine, and generate the variations externally. Then, you can use the texture packer separately to create different atlas variations. I recommend reading the full Spine Texture Packer user guide for a deeper understanding.

        That would mean I would have a single Spine project with the "base" skin and that would provide me with just the JSON files and I would be able to generate atlas pages (png) and atlas file (txt) for each skin separately using the CLI?

          • Düzenlendi

          michalvadak

          The easiest approach in your case is to export the skeleton three times, enabling the export flag only for the skin you want to export each time. This way, at runtime, your skeleton will require only the regions for the selected skin.

          Next, use the texture packer separately, providing as input a folder containing only the attachments of that skin. For example, to create the atlas for skin1, if your image folder is structured as follows:

          • images/
            • skin1/
            • skin2/
            • skin3/

          You can create a temporary folder containing only skin1:

          • temp/
            • skin1/

          Then, provide this temp/ folder to the standalone texture packer.

          If there are shared elements across skins stored in a separate common/ folder:

          • images/
            • common/
            • skin1/
            • skin2/
            • skin3/

          Copy the common/ folder as well:

          • temp/
            • common/
            • skin1/

          Make sure to select Combine subdirectories so the texture packer doesn’t generate a separate atlas page for each folder.

          Note that, as stated in the export flag documentation:

          If a mesh attachment is not exported, none of its linked meshes will be exported either.

          This means that if you export skin1, and skin1 contains a linked mesh that references a mesh in skin2, you must also export skin2 to preserve the dependency.
          Alternatively, to overcome this limitation, you can export a JSON containing all the skins (or the skins for which there are dependencies). However, you’ll need to create a custom AttachmentLoader that doesn’t throw an error when a region is missing from the atlas.
          In this case, the custom AttachmentLoader would function similarly to the AtlasAttachmentLoader, but with the throw declarations removed.
          If you take this approach, ensure that at runtime, you only set skins that are present in your atlas to avoid errors.
          You probably want to use CLI in this case to automatize this process.