pixijs icon indicating copy to clipboard operation
pixijs copied to clipboard

Support for Multi Pack assets generated by Texture packer in AnimatedSprite

Open ishaan-puniani opened this issue 3 years ago • 21 comments

Hello, Today, I tried v5.4.0-rc.1 release of Pixijs which contains the feature for loading Multi Pack assets generated by Texture packer.

I figured out that loading is working fine though I was expecting it is working in PIXI.AnimatedSprite out of the box as in the same fashion as normal texture packed assets with animation. (May be I am wrong here and there is an oob way to trigger it in the animation).

I wrote a code snippet which is one of the way to feed sequence of texture collected from multi pack to PIXI.AnimatedSprite. `


// load sprite sheet image + data file, call setup() if completed
PIXI.loader.add(spritesheetname).load(setup);

function setup() {
  const referencedSpriteSheet = PIXI.loader.resources[spritesheetname];
  const multiPack = referencedSpriteSheet.data.meta.related_multi_packs;
  let multiPackTexttureMap = { ...referencedSpriteSheet.spritesheet.textures };
  if (multiPack && multiPack.length > 0) {
    multiPack.forEach((mpack) => {
      const textureName = mpack.replace(".json", ""); // Honestly, I don't like to do this 😞 
      const _mpackSheet = PIXI.loader.resources[textureName].spritesheet;
      if (_mpackSheet.textures) {
        multiPackTexttureMap = Object.assign(
          multiPackTexttureMap,
          _mpackSheet.textures
        );
      }
    });
  }

  const animationSeq = referencedSpriteSheet.data.animations["animation"];
  const animationSeqTextures = animationSeq.map(
    (frame) => multiPackTexttureMap[frame]
  );
  app.stage.scale.x = 3;
  app.stage.scale.y = 3;

  // create an animated sprite
  createRunAnimation(animationSeqTextures);

  runnerAnimation.play();

  // add it to the stage and render!
  app.ticker.add((delta) => {});
}

function createRunAnimation(animationArray) {
  _animation = new PIXI.AnimatedSprite(animationArray);
  _animation.animationSpeed = 0.6;
  _animation.position.set(0, 0); // almost bottom-left corner of the canvas
  _animation.loop = true;
  app.stage.addChild(_animation);
}

`

Please suggest improvements or other optimized way to do it as this utility is one of the base in my application which rich of animations and assets are multi packed because of I want the sprites to be less than 2 mb and there are atleast 50 such sprites.

Thanks in advance.

Environment

  • pixi.js version:v5.4.0-rc.1
  • Browser & Version: Chrome
  • OS & Version: macOS

ishaan-puniani avatar Nov 13 '20 22:11 ishaan-puniani

Good suggestion. Could you provide a simple multipack asset + animation sample that we can use to test drive adding this feature?

bigtimebuddy avatar Nov 13 '20 22:11 bigtimebuddy

Hello @bigtimebuddy , Of course, you can find a quick working sample at https://ishaan-puniani.github.io/demo/index.html I have added assets for this POC at https://github.com/ishaan-puniani/demo/tree/master/docs/small

Let me know if I can help out with something.

-- Thanks

ishaan-puniani avatar Nov 15 '20 01:11 ishaan-puniani

@bigtimebuddy Does it makes sense to add this as proposed feature in next minor of 5.4.x and v6 ?

ishaan-puniani avatar Nov 16 '20 23:11 ishaan-puniani

We can do a patch of 5.4.0 for this. Since it's mostly fixing AnimatedSprite loading with multipacks. Do you care to work on a PR for it?

bigtimebuddy avatar Nov 16 '20 23:11 bigtimebuddy

I would like to work on it. Since I am doing it for the first time so might needs some support from you guys 😄

ishaan-puniani avatar Nov 17 '20 08:11 ishaan-puniani

Happy to help however we can! Thanks for looking into this.

bigtimebuddy avatar Nov 17 '20 11:11 bigtimebuddy

I literally just started a project with a need for this - I can provide an additional example / assets if needed.

Though I'd like to add another complication for consideration: I've packed multiple sprites for multiple animations into one large multi-packed spritesheet.

If I pull the first sprite sheet from resrouces, the resource.textures.spritesheet correctly populates, but not with all the animation names (As they're in sprite sheet 3), and not with all the animations in the names listed (as frame 6 of anim 1 is in sheet 4).

This is all coming from the default usage of TexturePacker, and would be great to see an engine update to natively support it, instead of needing the dev to do extra work each time.

darren-outdev avatar Nov 17 '20 14:11 darren-outdev

@darren-outdev , it would be great if you can provide the sample assets. Yeah! I agree we should add support for different outcomes provided by supported TexturePacker (or if there is some other well documented tool) natively.

ishaan-puniani avatar Nov 19 '20 09:11 ishaan-puniani

Sample assets: https://drive.google.com/drive/folders/1eODs4_v-vlweaIGSc0W6lf2O1MaHEMWF?usp=sharing (They're free to use with an open license, I just used TexturePacker to pack the frames)

Note; Im in communication with the TexturePacker team to fix a bug where the "aniamtions" object within a JSON file does not correctly get populated when there is only one frame of an animation on a sheet. You can see the issue when opening all-2.json. You can see that "Glide_009.png" is listed in there, but not inside the animations object.

darren-outdev avatar Nov 19 '20 17:11 darren-outdev

We can indeed add the animations entry for single sprites in the multipack case.

What I currently don't see in the code is how the frames get ordered. TexturePacker might put anim_001 on the first sheet, anim_002 on the 4th and anim_003 on the 2nd. It depends on the available space on the sheets.

Or am I missing this?

CodeAndWeb avatar Nov 20 '20 10:11 CodeAndWeb

@CodeAndWeb Correct - I see TexturePacker almost never orders the frame in the same spritesheet instance (especially if multipack with heuristics is on). I've always leaned on the fact that TexturePacker will always put the frame number on the frame - so currently (without using the animation object), I manually go through all the spritesheets, created a Map based off name and texture, then I have all the anims I need in order.

But that is something PIXI needs to do, not TexturePacker.

What's missing, though, is the convenience of just calling an animation object with all the animations populated and ordered (Which is what this thread is about)

darren-outdev avatar Nov 20 '20 12:11 darren-outdev

Would it not make more sense to simply let TexturePacker put an array in the .json files instead of collecting and sorting the sprites at runtime?

Something like:

animations: {
	'dog': {
		frames: ['dog-1','dog-2','dog-3','dog-4','dog-5','dog-6','dog-7','dog-8','dog-9','dog-10' ]
		sheets: [0,1,3,4,5,3,2,3,3,1]
	},
	'cat': {
		frames: ['cat/01','cat/02','cat/03','cat/04']
		sheets: [4,3,2,5]
	},
}

CodeAndWeb avatar Nov 23 '20 16:11 CodeAndWeb

@bigtimebuddy might want to add their insight, but I think your suggestion is perfect - anything PIXI doesnt have to do just means better performance for the client. I just don't want to suggest anything that might break backward compatibility.

darren-outdev avatar Nov 23 '20 16:11 darren-outdev

Backward compatibility is indeed a bit of an issue here... right now animations is an object of arrays. This makes it an object of objects...

So maybe the sheets should be outside.

animations: {
	'dog': ['dog-1','dog-2','dog-3','dog-4','dog-5','dog-6','dog-7','dog-8','dog-9','dog-10' ],
	'cat': ['cat/01','cat/02','cat/03','cat/04']
},
animations-multipack: {
	'dog': [0,1,3,4,5,3,2,3,3,1],
	'cat': [4,3,2,5]
}

The other question is where to put the array... on the first sheet or on all?

CodeAndWeb avatar Nov 23 '20 16:11 CodeAndWeb

If needed, I think added to all.

However, I just did a small test, and I think I may have a solution which will be backwards compatable. If every spritesheet simply contained the object-array as it is now, then PIXI can use the following function to easily build a fully populated animation object:

const spritesheet1 = {
  animations: {
    dog: ["dog_001", "dog_002", "dog_004", "dog_005"],
  },
};

const spritesheet2 = {
  animations: {
    dog: ["dog_003"],
  },
};

const spritesheet3 = {
  animations: {
    dog: ["dog_006", "dog_007"],
  },
};

const objs = [spritesheet1, spritesheet2, spritesheet3];

const allAnimations = {};

// Simple iteration over each spritesheet, and spread-add all its animations to the `allAnimations` variable
objs.forEach((obj) => {
  for (const key of Object.keys(obj.animations)) {
    allAnimations[key] = allAnimations[key]
      ? [...allAnimations[key], ...obj.animations[key]]
      : obj.animations[key]; // One-liner to either create a new array, or spread-add current values to previous one
    // Add the texture with the key for easier fetching, omitted for demo
  }
});

// Tested this in Quokka on VSCode, output shown below
// Can also optionally sort by name for a quality of life boost for the dev
console.log(allAnimations);
/**
 { dog:  
   [ 'dog_001', 
     'dog_002', 
     'dog_004', 
     'dog_005', 
     'dog_003', 
     'dog_006', 
     'dog_007' ] } 
 */

This means all you need to do is ensure every spritesheet contains an animation object with all frames that relate to the information, *even if its just one frame in the file.

What do you think?

darren-outdev avatar Nov 23 '20 17:11 darren-outdev

As per https://github.com/pixijs/pixi.js/pull/6773/files#diff-33d09fca81cbc23d1765aa6d297814c474a44d2584a41a12a1d20bb50f2eae86R61 it will load the asset if there is related_multi_packs even if it contains a single sprite.

I was just thinking that may be one of the easy implementation is if the root spritesheet contains the animation array. since at the end PIXI is holding the map of the Textures so, it really doesn't matter if it is coming from shee-1.json or sheet-2.json. As in the POC that I shared, https://github.com/ishaan-puniani/demo/blob/master/docs/small/dog_1_splitted-0.json#L39 I myself added the run:[....] animation here. So, something like the root sheet contains all the animations and related mutipacks contains the frames only and do not have anything for animations object at all. This will actually gives a lot of clarity to the developers and artists. As, distributed animation frames makes less sense to me as its hard to read, hard to combine and hard to troubleshoot.

May be we need to give some more thoughts on its implementation.

ishaan-puniani avatar Nov 25 '20 00:11 ishaan-puniani

I currently don't have the resources to dig deeper into Pixi code... sorry. Just tell me what you need when the decision is made - I'll add that functionality to TexturePacker.

CodeAndWeb avatar Nov 27 '20 12:11 CodeAndWeb

@CodeAndWeb Just ensure every sprite sheet from multi pack contains the animation object, even if it is only one frame.

darren-outdev avatar Nov 27 '20 19:11 darren-outdev

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jun 11 '21 01:06 stale[bot]

Was this ever solved? I'm running into this issue right now, multipacking doesn't work with PixiJS when animations are spread across the sprites.

Zenoo avatar Aug 20 '22 22:08 Zenoo

This seems to be because the texture pack changes the packer rule for pixijs, is there any related issue? image

littleboarx avatar Sep 06 '22 05:09 littleboarx

Also can confirm. You need to remove "related_multi_packs" property from the spritesheet json file in order to even load them without crashing/freezing.

Dunnings avatar Sep 26 '22 10:09 Dunnings

Just experimented with it in PixiJS 6.3.2 version.

TexturePacker does the following: It sets the related_multi_packs for each file so that all files in a multipack set can be found. This allows PixiJS to load all related sprite sheets which seems to work.

TexturePacker also writes the animations array to the first sprite sheet - with the whole list of sprites.

Receiving the animations from the first sheet

let sheet = app.loader.resources["spritesheet-0.json"].spritesheet;

contains only the animation frames that can be resolved on this sheet - the others are all undefined - which is obviously not what we expect.

I tried adding the animation entry to all other sprite sheets json files (manually), and receiving them with app.loader also returns animations with undefined. Merging them would most likely lead to a complete animation.

Adding the animation frames to all sprite sheet json files is redundant and only increases the download size with no use for anybody.

It is also bad from the standpoint of a developer: It requires loading all sprite sheets manually and merging the animation frames.

I think that this should really be part of PixiJS - so that loading the first sprite sheet of a set should suffice. After loading the related sprite sheets, PixiJS could easily resolve the animation frames.

CodeAndWeb avatar Sep 26 '22 12:09 CodeAndWeb