Game Objects - samme/phaser3-faq GitHub Wiki

Game objects are semipermanent objects you add to a game scene. Most are "sprites" of some sort.

The scene keeps a display list for rendering game objects and an update list for updating them. A game object may go in one or both lists, depending on its type.

Display List Update List
Bitmap Text
Blitter
Container
DOM Element
Extern Extern
Graphics
Group
Image
Layer
Mesh Mesh
Nine Slice
Particle Emitter Particle Emitter
Path Follower Path Follower
Point Light
Render Texture
Rope Rope
Shader
Shape
Sprite Sprite
Text
Tile Sprite
Tilemap Layer
Video Video
Zone

And some quasi-game objects:

Strategy

  • Create and add game objects in the scene's create() method, usually
  • Toggle a game object's active and visible properties to enable or disable it
  • Destroy a game object to permanently remove it
  • Avoid creating or destroying game objects very frequently

Create a game object

Usually, create and add game objects in the scene's create() method.

The add methods create and add at once:

const sprite = this.add.sprite(0, 0, 'mummy');

The make methods do the same, when given add: true:

const sprite = this.make.sprite({ x: 0, y: 0, key: 'mummy', add: true });
// OR
const sprite = this.make.sprite({ x: 0, y: 0, key: 'mummy' }, true);

Game objects can be instantiated and then added manually:

const sprite = this.add.existing(new Phaser.GameObjects.Sprite(this, 0, 0, 'mummy'));

Game objects can be created in a disabled or invisible state:

const sprite = this.add.sprite(0, 0, 'mummy').setActive(false).setVisible(false);

With add: false, the make methods do not add the new game object to the scene display list:

const sprite = this.make.sprite({ x: 0, y: 0, key: 'mummy', add: false });
// OR
const sprite = this.make.sprite({ x: 0, y: 0, key: 'mummy' }, false);

These game objects aren't displayed at all.

Game objects instantiated directly are not added to the scene display list or update list:

const sprite = new Phaser.GameObjects.Sprite(this, 0, 0, 'mummy');

The update list will also update any object with active: true and a preUpdate method:

this.sys.updateList.add({
  active: true,
  preUpdate: () => { console.count(); }
});

Remove a game object

Temporarily remove a game object:

sprite.setActive(false).setVisible(false);

Permanently remove a game object:

sprite.destroy();

Destroyed game objects can't be reused. You can detect a destroyed game object by gameObject.scene === undefined. (It has active === false and visible === false as well.)

All game objects are destroyed when the scene is stopped, so you don't need to do that yourself.

The Display List

Any game object that can be displayed is on the scene display list either at the root level or as a descendant of a Container or Layer. A game object's displayList holds the display list or Layer it belongs to and its parentContainer holds the Container it belongs to, if the Container is exclusive.

You can print the contents of the display list:

// Open the browser console first to see `console.table()`.
// You can also use `console.log()` instead.
console.table(
  this.sys.displayList.getChildren().map(o => ({ name: o.name, type: o.type, x: o.x, y: o.y, depth: o.depth }))
);

If you want to see the results of depth sorting, wrap this in

this.events.once('render', () => {/* … */});

Textures

Certain game objects hold a texture and texture frame:

const sprite = this.add.sprite(0, 0, 'mummy', 1);

console.log(sprite.texture.key); // → 'mummy'
console.log(sprite.frame.name); // → 1

The texture argument ('mummy') is the key (name) of the texture to load into the sprite when creating it. It has no other significance.

These game objects can't be created without a texture. Instead they will receive the "default" texture and frame (32 × 32 transparent):

const sprite = this.add.sprite(0, 0);
console.log(sprite.texture.key); // → '__DEFAULT'
console.log(sprite.displayWidth, sprite.displayHeight); // → 32, 32

Use an invisible sprite instead, or a Zone.

Change texture frames:

// Spritesheet-style frame names
sprite.setFrame(2);
sprite.setFrame('2');

// Atlas-style frame names
sprite.setFrame('walkLeft');

Change texture:

const sprite = this.add.sprite(0, 0, 'mummy');

sprite.setTexture('bat');
console.log(sprite.texture.key); // → 'bat'

Loading or playing an animation can also change a Sprite's texture:

const sprite = this.add.sprite(0, 0, 'mummy');

sprite.play('snakeAttack');
console.log(sprite.texture.key); // → 'snake'

Position, origin, and size

x and y are the game object's local position (if within a Container) or local and world position (if not within a Container). z and w aren't used by Phaser, so you could use them yourself. z is not depth.

width and height are a game object's intrinsic or unscaled size. For frame-based objects (Image, Sprite), these are the size of the current texture frame and you shouldn't set them. For others (Container, TileSprite), you can set an intrinsic size this way.

originX and originY are the game object's visual origin, in normalized coordinates: (0, 0) is the top-left and (1, 1) is the bottom-right. Rotation and scaling happen from the origin; flipping happens across the center. Most game objects have a default origin of (0.5, 0.5), the center. Render Texture, Text, and Bitmap Text game objects have a default origin of (0, 0), the top-left. Container game objects have a nonconfigurable origin of (0.5, 0.5) for a few special purposes. Blitter and Graphics game objects have a nominal origin of (0, 0), as they're dimensionless.

displayOriginX and displayOriginY are the game object's origin in unscaled pixel coordinates:

(displayOriginX, displayOriginY) == (width, height) * (originX, originY)

They are unrelated to displayWidth and displayHeight, since they refer to the intrinsic size.

displayWidth and displayHeight are the game object's scaled dimensions, in pixel coordinates:

(displayWidth, displayHeight) == (width, height) * (scaleX, scaleY)

You can get or set a game object's visual size from these. There's no need to set both displayWidth and scaleX or displayHeight and scaleY, because both properties are doing the same thing.

Frame-based game objects (Image, Sprite) may change size when switching frames or textures.

When a texture frame has a custom pivot set (usually in the texture atlas), the game object origin is updated automatically when changing frames.

angle (degrees, -180 to 180) and rotation (radians, -π to π) are the same attribute, in different units. In its initial position, a game object has angle and rotation 0.

Local position and bounds

For game objects not in Containers, local coordinates are also world coordinates.

The local center is gameObject.x, gameObject.y for origin (0.5, 0.5) or gameObject.getCenter() for any origin.

The local, unrotated, unscaled bounds are given by

const rect = Phaser.Display.Bounds.GetBounds(gameObject);
// → Rectangle { x, y, width, height, left, top, right, bottom … }

This is a rectangle with dimensions identical to the game object's width and height, positioned by its origin.

You can get the edge coordinates separately:

const left = Phaser.Display.Bounds.GetLeft(gameObject);

And you can set them:

Phaser.Display.Bounds.SetLeft(gameObject, 0);

You can calculate the local, unrotated, scaled bounds yourself:

const left = gameObject.x - gameObject.originX * gameObject.displayWidth;
const top = gameObject.y - gameObject.originY * gameObject.displayHeight;

The local, rotated, scaled coordinates of the four corners and four edge midpoints are given by getTopLeft(), getLeftCenter(), etc.

The getLocalPoint() method converts world coordinates into a game object's own coordinate space (including transforms). For an image, these are also texture coordinates. For a shape, these may be similar to the geometry coordinates. For a container, these are the child object's local coordinates.

World position and bounds

These are for game objects in Containers. (For game objects not in Containers, world coordinates are also local coordinates.)

The game object's position in world coordinates is

const { tx, ty } = gameObject.getWorldTransformMatrix();

The bounds in world coordinates are

const bounds = gameObject.getBounds();
// → Rectangle { x, y, width, height, left, top, right, bottom, centerX, centerY, … }

This is the smallest axis-aligned rectangle containing the rotated, scaled corners of the game object.

The transformed corners in world coordinates are

const { x, y } = gameObject.getTopLeft(undefined, true); // etc.

And the center in world coordinates is

const { x, y } = gameObject.prepareBoundsOutput(gameObject.getCenter(), true);

or (Phaser v3.60)

const { x, y } = gameObject.getCenter(undefined, true);

Geometry

You can convert most game objects to a Geom point, circle, or rectangle for some calculations, such as intersection checks.

Crop

Crop clips the visible area of a game object's texture frame.

image.setCrop(0, 0, 64, 32);

The values are in texture coordinates, where (0, 0) is the top-left of the texture.

Cropping doesn't change the game object's actual bounds or its input hit area.

Input

Use setInteractive() to let a game object receive input events. Any game object can be made interactive, but only some have an automatic hit area. For the others you need to provide a hit area and hit test function.

For game objects with a texture frame (frame) or a nonzero width and height, setInteractive() with no arguments creates a rectangular hit area of the same size:

const sprite = this.add.sprite(0, 0, 'mummy').setInteractive();

console.log(sprite.input.hitArea); // → Rectangle { x: 0, y: 0, width: 32, height: 48 }

hitArea is in local coordinates, where (0, 0) is the top-left of the game object, regardless of origin.

You can construct the same hit area manually. You must pass a hit area and hit test function:

sprite.setInteractive(
  new Phaser.Geom.Rectangle(0, 0, sprite.frame.realWidth, sprite.frame.realHeight),
  Phaser.Geom.Rectangle.Contains
);

You can use any geometry shape, with the corresponding Contains function:

sprite.setInteractive(
  new Phaser.Geom.Circle(sprite.displayOriginX, sprite.displayOriginY, sprite.width),
  Phaser.Geom.Circle.Contains
);

You can use your own hit area and test function:

sprite.setInteractive({
  hitArea: [
    new Phaser.Geom.Circle(0, 0, 32),
    new Phaser.Geom.Circle(64, 64, 32),
    new Phaser.Geom.Circle(128, 128, 32)
  ],

  hitAreaCallback: (hitArea, x, y, gameObject) => {
    return hitArea[0].contains(x, y) ||
        hitArea[1].contains(x, y) ||
        hitArea[2].contains(x, y);
  }
});

Alpha

Game objects with alpha === 0 are invisible.

In WebGL mode the four corners of a texture can have different alpha values.

Tint

Tint is a dye-like color effect on a game object's texture. It's WebGL only.

sprite.setTint(0xff4136);

Mathematically, tint multiplies each texture pixel by the tint color, so a tinted pixel is never brighter than an untinted one. White tint (0xffffff) has no effect and black tint (0x000000) makes all pixels black. In the texture, white pixels tint completely and black pixels not at all. So white images are good for tintable shapes or bitmap text.

Tint fill is an opaque fill, like a paint bucket fill:

sprite.setTintFill(0x01ff70);

clearTint() removes both kinds of tint.

The four corners of a game object's texture can be tinted separately, forming gradients:

sprite.setTint(0xff4136, 0xffdc00, 0x2ecc40, 0x0074d9);
// OR
sprite.tintTopLeft = 0xff4136; // etc.

Depth and render order

Game objects are rendered by their position in the display list, start to end (or back to front). By default this is the order you added them to the scene in.

You can move them within the display list:

this.children.bringToTop(gameObject);

this.children.sendToBack(gameObject);

this.children.moveUp(gameObject);
this.children.moveDown(gameObject);
this.children.moveTo(gameObject, 2);

(this.children is the scene display list, also found at this.sys.displayList.)

Game objects in containers can be moved the same way by the container's methods:

container.bringToTop(gameObject);
// etc.

You can assign a depth-sort order (z-index):

gameObject.setDepth(1);

depth (z-index) is a sort order, not a position in the display list. All game objects have a default depth of 0. Larger depths sort to the front of smaller depths. The sorting happens right before the scene renders. Game objects keep their depth after sorting.

You can use fractional or negative values for depth. You don't need to use gigantic values.

Game objects in layers can also be depth sorted this way, but game objects in containers can't.

Containers themselves can be depth sorted just like anything else, so it only really becomes an issue when a Container is used to house multiple different entities, i.e. one Container holding say all enemies for a level and suddenly you want one to pop above the rest. In this case, it needs removing from its Container, adding to another one (higher up the render list) until the effect is over, then putting back again.

Effects

Pre FX are for texture based game objects only (Sprite, TileSprite, Video, etc.), but Post FX applies to anything, including Cameras. The difference is that Pre FX can alter the actual draw of the game object, sort of like having a custom pipeline but far less stress.

If you apply Post FX to a Container, then it will render all children to that fx, then run the shader on it and put the results in to the game – so if you had a 'glow' shader it would glow everything as one single object. Pre FX on the other hand would glow each object in turn. So more expensive, but likely more the required result depending on the game.

Scroll factor

Scroll factor controls how much game objects move (relative to the game canvas) when the camera is scrolled.

// No scrolling
gameObject.setScrollFactor(0, 0);

The default values are (1, 1). Scroll factor (0, 0) can be used for fixed backgrounds or UI elements. Factors between 0 and 1 can be used for parallax scrolling effects.

Don't use scroll factors other than (1, 1) for physics game objects, as it doesn't make sense.

State

You can store simple values (number or string) in the state field.

// State
mummy.state = 'sleeping';
bat.state = 'movingLeft';

// Coins
treasure.state = 10;
treasure.state -= 1;

// Health
snake.state = 3;

Data

Use the data store for a more structured approach, or lots of data:

mummy.setData('health', 3);
mummy.getData('health'); // → 3

The data store emits events (from the game object itself) when values are added or changed:

mummy.on('changedata-health', (gameObject, dataKey, dataValue) => {/*…*/});

You can store other objects like game objects, timer events, or tweens. The data store is cleared when the parent game object is destroyed, so there's no problem with cleanup.

const cat = this.add.sprite(/*…*/);

mummy.setData('familiar', cat);
// …
mummy.destroy();

Name

You can use the name field to identify your game objects, either for game logic or debugging. Phaser doesn't use this field.

this.add.sprite(0, 0, 'bat').setName('bat1');

mummy.setName('Reginald');

Type

E.g., "TileSprite". Phaser uses this so you should leave it alone.

Events

Game objects emit events directly.

Each game object emits ADDED_TO_SCENE, REMOVED_FROM_SCENE, and DESTROY. ADDED_TO_SCENE and REMOVED_FROM_SCENE are fired when adding to or removing from any display list, e.g., containers. Only DESTROY happens only once.

Animatable game objects (e.g. Sprite) also emit animation events.

Interactive game objects also emit interaction events.

Video game objects also emit playback events.

Game objects with a data store also emit data events.

Creation patterns

The make methods are flexible and work well with structured data, like JSON.

Groups provide create() and createMultiple() methods.

There are simple ways to organize game object creation without extending classes:

function create() {
  const mummy = createMummy.call(this, 0, 0);
}

function createMummy(x, y) {
  return this.add.mummy(x, y, 'mummy');
}

If you don't like call():

function create() {
  const mummy = createMummy(this, 0, 0);
}

function createMummy(scene, x, y) {
  return scene.add.mummy(x, y, 'mummy');
}

You can add creator and factory methods without extending classes:

function create() {
  const mummy = this.add.mummy(0, 0);
}

Phaser.GameObjects.GameObjectFactory.register('mummy', function (x, y) {
  return this.sprite(x, y, 'mummy');
}

Extending game objects

Most beginners shouldn't extend game object classes yet.

Choose a class to extend and call super() with all required arguments. Don't extend Phaser.GameObjects.GameObject by itself.

class MummySprite extends Phaser.GameObjects.Sprite {
  constructor(scene, x, y, texture = 'mummy', frame = 0) {
    super(scene, x, y, texture, frame);
    // this.scene, this.x, this.y, this.texture, this.frame are set.
  }
}

Phaser game object classes don't add themselves to the scene, so super() will not do that.

Add the game object separately, in the scene:

this.add.existing(new MummySprite(this, 0, 0));

or add it from the class:

this.scene.add.existing(this);

If you override the class's preUpdate() method, call the superclass method as well. Otherwise the game object may freeze.

class MummySprite extends Phaser.GameObjects.Sprite {
  // …
  preUpdate (time, delta) {
    super.preUpdate(time, delta);
    // …
  }
}

If you override the class's destroy() method, call the superclass method also. Or consider using the DESTROY event instead.

A game object's update() method isn't called automatically. You can

  • call the update() method yourself; or
  • add the game object to a group with runChildUpdate = true; or
  • bind to the scene's UPDATE event:
class MummySprite extends Phaser.GameObjects.Sprite {
  constructor () {
    // …
    const { events } = this.scene;

    events.on('update', this.update, this);

    this.once('destroy', function () {
      events.off('update', this.update, this);
    }, this);
  }

  update (time, delta) {
    // …
  }
}

Classes containing game objects

Some authors prefer to make pure entity classes that hold a game object instead of extending a game object class.

class Mummy {
  constructor (scene, x, y) {
    this.sprite = scene.add.sprite(x, y, 'mummy');
    
    // You may need to reach the parent object from the sprite, e.g., from within a callback.
    this.sprite.setData('parent', this);

    this.sprite.once('destroy', this.onSpriteDestroyed, this);
  }
  
  onSpriteDestroyed (sprite) {
    this.sprite = null;
  }
}

Problems

I added a game object but I can't see it

The game developer's lament.

  • it's outside the camera viewport
  • it's behind another game object
  • it has the "default" (blank) texture
  • it's not on the display list
  • it has visible === false
  • it has alpha === 0
  • it has scale 0
  • it's masked
  • it was already destroyed