Grouping Sprites
Groups let you create game scenes, and manage similar sprites together as single units. Pixi has an object called a Container
that lets you do this. Let’s find out how it works.
Imagine that you want to display three sprites: a cat, hedgehog and tiger. Create them, and set their positions - but don’t add them to the stage.
//The cat
let cat = new Sprite(id["cat.png"]);
cat.position.set(16, 16);
//The hedgehog
let hedgehog = new Sprite(id["hedgehog.png"]);
hedgehog.position.set(32, 32);
//The tiger
let tiger = new Sprite(id["tiger.png"]);
tiger.position.set(64, 64);
Next, create an animals
container to group them all together like this:
let animals = new PIXI.Container();
Then use addChild
to add the sprites to the group.
animals.addChild(cat);
animals.addChild(hedgehog);
animals.addChild(tiger);
Finally add the group to the stage.
app.stage.addChild(animals);
(As you know, the stage
object is also a Container
. It’s the root container for all Pixi sprites.)
Here’s what this code produces:
What you can’t see in that image is the invisible animals
group that’s containing the sprites.
You can now treat the animals
group as a single unit. You can think of a Container
as a special kind of sprite that doesn’t have a texture.
If you need a list of all the child sprites that animals
contains, use its children
array to find out.
console.log(animals.children)
//Displays: Array [Object, Object, Object]
This tells you that animals
has three sprites as children.
Because the animals
group is just like any other sprite, you can change its x
and y
values, alpha
, scale
and all the other sprite properties. Any property value you change on the parent container will affect the child sprites in a relative way. So if you set the group’s x
and y
position, all the child sprites will be repositioned relative to the group’s top left corner. What would happen if you set the animals
‘s x
and y
position to 64?
animals.position.set(64, 64);
The whole group of sprites will move 64 pixels right and 64 pixels to the down.
The animals
group also has its own dimensions, which is based on the area occupied by the containing sprites. You can find its width
and height
values like this:
console.log(animals.width);
//Displays: 112
console.log(animals.height);
//Displays: 112
What happens if you change a group’s width or height?
animals.width = 200;
animals.height = 200;
All the child sprites will scale to match that change.
You can nest as many Container
s inside other Container
s as you like, to create deep hierarchies if you need to. However, a DisplayObject
(like a Sprite
or another Container
) can only belong to one parent at a time. If you use addChild
to make a sprite the child of another object, Pixi will automatically remove it from its current parent. That’s a useful bit of management that you don’t have to worry about.
Local and global positions
When you add a sprite to a Container
, its x
and y
position is relative to the group’s top left corner. That’s the sprite’s local position For example, what do you think the cat’s position is in this image?
Let’s find out:
console.log(cat.x);
//Displays: 16
16? Yes! That’s because the cat is offset by only 16 pixel’s from the group’s top left corner. 16 is the cat’s local position.
Sprites also have a global position. The global position is the distance from the top left corner of the stage, to the sprite’s anchor point (usually the sprite’s top left corner.) You can find a sprite’s global position with the help of the toGlobal
method. Here’s how:
parentSprite.toGlobal(childSprite.position)
That means you can find the cat’s global position inside the animals
group like this:
console.log(animals.toGlobal(cat.position));
//Displays: Object {x: 80, y: 80...};
That gives you an x
and y
position of 80. That’s exactly the cat’s global position relative to the top left corner of the stage.
What if you want to find the global position of a sprite, but don’t know what the sprite’s parent container is? Every sprite has a property called parent
that will tell you what the sprite’s parent is. If you add a sprite directly to the stage
, then stage
will be the sprite’s parent. In the example above, the cat
‘s parent is animals
. That means you can alternatively get the cat’s global position by writing code like this:
cat.parent.toGlobal(cat.position);
And it will work even if you don’t know what the cat’s parent container currently is.
There’s one more way to calculate the global position! And, it’s actually the best way, so listen up! If you want to know the distance from the top left corner of the canvas to the sprite, and don’t know or care what the sprite’s parent containers are, use the getGlobalPosition
method. Here’s how to use it to find the tiger’s global position:
tiger.getGlobalPosition().x
tiger.getGlobalPosition().y
This will give you x
and y
values of 128 in the example that we’ve been using. The special thing about getGlobalPosition
is that it’s highly precise: it will give you the sprite’s accurate global position as soon as its local position changes. I asked the Pixi development team to add this feature specifically for accurate collision detection for games. (Thanks, Matt and the rest of the team for adding it!)
What if you want to convert a global position to a local position? you can use the toLocal
method. It works in a similar way, but uses this general format:
sprite.toLocal(sprite.position, anyOtherSprite)
Use toLocal
to find the distance between a sprite and any other sprite. Here’s how you could find out the tiger’s local position, relative to the hedgehog.
tiger.toLocal(tiger.position, hedgehog).x
tiger.toLocal(tiger.position, hedgehog).y
This gives you an x
value of 32 and a y
value of 32. You can see in the example images that the tiger’s top left corner is 32 pixels down and to the left of the hedgehog’s top left corner.
Using a ParticleContainer to group sprites
Pixi has an alternative, high-performance way to group sprites called a ParticleContainer
(PIXI.particles.ParticleContainer
). Any sprites inside a ParticleContainer
will render 2 to 5 times faster than they would if they were in a regular Container
. It’s a great performance boost for games.
Create a ParticleContainer
like this:
let superFastSprites = new PIXI.particles.ParticleContainer();
Then use addChild
to add sprites to it, just like you would with any ordinary Container
.
You have to make some compromises if you decide to use a ParticleContainer
. Sprites inside a ParticleContainer
only have a few basic properties: x
, y
, width
, height
, scale
, pivot
, alpha
, visible
– and that’s about it. Also, the sprites that it contains can’t have nested children of their own. A ParticleContainer
also can’t use Pixi’s advanced visual effects like filters and blend modes. Each ParticleContainer
can use only one texture (so you’ll have to use a spritesheet if you want Sprites with different appearances). But for the huge performance boost that you get, those compromises are usually worth it. And you can use Container
s and ParticleContainer
s simultaneously in the same project, so you can fine-tune your optimization.
Why are sprites in a Particle Container
so fast? Because the positions of the sprites are being calculated directly on the GPU. The Pixi development team is working to offload as much sprite processing as possible on the GPU, so it’s likely that the latest version of Pixi that you’re using will have much more feature-rich ParticleContainer
than what I’ve described here. Check the current ParticleContainer
documentation for details.
Where you create a ParticleContainer
, there are four optional arguments you can provide: size
, properties
, batchSize
and autoResize
.
let superFastSprites = new ParticleContainer(maxSize, properties, batchSize, autoResize);
The default value for maxSize
is 1500. So, if you need to contain more sprites, set it to a higher number. The properties
argument is an object with 5 Boolean values you can set: scale
, position
, rotation
, uvs
and alphaAndTint
. The default value of position
is true
, but all the others are set to false
. That means that if you want to change the rotation
, scale
, tint
, or uvs
of sprite in the ParticleContainer
, you have to set those properties to true
, like this:
let superFastSprites = new ParticleContainer(
size,
{
rotation: true,
alphaAndtint: true,
scale: true,
uvs: true
}
);
But, if you don’t think you’ll need to use these properties, keep them set to false
to squeeze out the maximum amount of performance.
What’s the uvs
property? Only set it to true
if you have particles which change their textures while they’re being animated. (All the sprite’s textures will also need to be on the same tileset image for this to work.)
(Note: UV mapping is a 3D graphics display term that refers to the x
and y
coordinates of the texture (the image) that is being mapped onto a 3D surface. U
is the x
axis and V
is the y
axis. WebGL already uses x
, y
and z
for 3D spatial positioning, so U
and V
were chosen to represent x
and y
for 2D image textures.)
(I’m not sure what exactly what those last two optional arguments, batchSize
and autoResize
, so if anyone knows, please us know in the Issues!)