-
Notifications
You must be signed in to change notification settings - Fork 4
Game Objects
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 |
Particle Emitter Manager | Particle Emitter Manager |
Path Follower | Path Follower |
Point Light | |
Render Texture | |
Rope | Rope |
Shader | |
Shape | |
Sprite | Sprite |
Text | |
Tile Sprite | |
Tilemap Layer | |
Video | Video |
(As of Phaser v3.60.)
- Create and add game objects in the scene's
create()
method, usually - Toggle a game object's
active
andvisible
properties to enable or disable it - Destroy a game object to permanently remove it
- Avoid creating or destroying game objects very frequently
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');
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.
Textured 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.
Textured game objects can't be created without a texture. Instead they will receive the "default" texture (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.loadTexture('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'
x
and y
are the game object's local position (if within a Container) or local and world position (if not within a Container).
originX
and originY
place the game object onto its position: (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. Blitter, Container, and Graphics have a nonconfigurable origin of (0, 0), as they're dimensionless.
For most game objects that have displayWidth
and displayHeight
properties, use those properties to get or set a game object's visual size. displayWidth
is width * scaleX
and displayHeight
is height * scaleY
.
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.
displayOriginX
is width * originX
and displayOriginY
is height * originY
. They are unrelated to displayWidth
and displayHeight
, since they refer to the intrinsic size.
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.
Use flipX
and flipY
instead of negative scale values.
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 … }
You can get or set the edge coordinates as well:
const left = Phaser.Display.Bounds.GetLeft(gameObject);
Phaser.Display.Bounds.SetLeft(gameObject, 0);
The local, scaled, unrotated bounds are given by
const rect = gameObject.getBounds();
// → Rectangle { x, y, width, height, left, top, right, bottom … }
This is the smallest rectangle containing all four corners, including rotation and scaling.
You can also get the corners separately:
const { x, y } = gameObject.getTopLeft();
const { x, y } = gameObject.getBottomLeft();
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 getLocalPoint()
method converts world coordinates into a game object's local coordinates.
These are for game objects in Containers.
The game object's position in world coordinates is
const { tx, ty } = gameObject.getWorldTransformMatrix();
The bounds in world coordinates are
const bounds = gameObject.getBounds(undefined, true);
// → Rectangle { x, y, width, height, left, top, right, bottom, centerX, centerY, … }
The 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);
You can convert most game objects to a Geom point, circle, or rectangle for some calculations, such as intersection checks.
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.
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);
}
});
Game objects with alpha === 0
are invisible.
In WebGL mode the four corners of a texture can have different alpha values.
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.
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.
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.
You can store simple values in the state
field.
// State
mummy.state = 'sleeping';
bat.state = 'movingLeft';
// Coins
treasure.state = 10;
treasure.state -= 1;
// Health
snake.state = 3;
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();
You can use the name
field to identify your game objects, either for game logic or debugging.
this.add.sprite(0, 0, 'bat').setName('bat1');
mummy.setName('Reginald');
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 for 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.
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');
}
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, as it lacks any rendering.
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, and this.frame are now 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:
class MummySprite extends Phaser.GameObjects.Sprite {
// …
preUpdate (time, delta) {
super.preUpdate(time, delta);
// …
}
}
Otherwise your game object may freeze.
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 (systems, time, delta) {
// …
}
}
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