Phaser Basics
Phaser is a free, open source game development framework. You use TypeScript and other web technologies for the development. The result runs on browsers by default but you can use third party tools to compile the game to run as a native application.
GameObject
is the main low-level building block of a Phaser game. Everything visual or intractable, from images and shapes to particle effects and videos, are game objects.
Group
is a collection of game objects in a scene.
- Nonexclusive membership
- Can disable or enable all children in one-go.
- Can hide or show all children in one-go.
- Can remove all children in one-go.
- Allows reusing disabled game objects to conserve resources with e.g.
get()
.
Container
is a collection of game objects in a scene.
- Exclusive membership
- Children move relative to the container.
- Children are rendered when the container is rendered.
- Containers can be nested.
- Containers can be expensive for performance.
Scene
is the main high-level building block of a Phaser game. Scene is a collection of game objects and surrounding logic. You can have as many running scenes as you want. You usually split your game to e.g. the gameplay scene and the user interface overlay scene.
Each scene has 5 core methods that can define. I'll also mention the main use-case for each method.
constructor
is ran once when the scene is defined, useful for initializing variables.init
: is ran each time the scene starts, allows e.g switching to anotherScene
before any loading happens or receiving startup information from other scenes.preload
: is ran afterinit
is used to load assets like images withthis.load
.create
: is ran afterpreload
and used to create game objects withthis.add
as assets are now available.update
: is ran once on each game loop if scene isactive
.
You can use a Scene
constructor files
property which will be loaded before preload
. These constructor files are good for either simple graphics required for a loading screen or small configuration files required to determine which assets to download in preload
.
public constructor() {
super({key: SceneName.Boot, files: {
{type: 'image', key: 'background', url: 'assets/background.png'}
}});
}
Note that init
, preload
and create
can be called again sometime later. For example, if you do stop()
and sometime later start()
on the scene.
The first listed scene in the game config is started automatically.
new Phaser.Game({
...,
scenes: [Scene1, Scene2, Scene3],
})
You can add scenes with this.scene.add
if it was not included in the game config. But they are not automatically started. You start scenes with start
(that also stops the current scene) or launch (which doesn't stop the current scene.)
You can destroy scenes with this.scene.remove
. Or simply pause them with pause()
(doesn't call update each loop) or make them inactive with stop()
(the next start will rerun the initialization methods).
Scenes share a global Cache
. All scenes use the same, potentially cached, files.
Scenes share a global Registry
which is a DataManager
. This global DataManager
can be used for cross-scene communication.
Each scene has core plugins that can't be removed:
- An Event Emitter
- The 2D Camera Manager
- The Game Object Creator
- The Game Object Factory
- The Scene Plugin
- The Display List
- The Update List
Each scene has some plugins that can optionally be removed in the constructor:
- The 3D Camera Manager
- The Clock
- The Data Manager Plugin
- The Input Plugin
- The Loader Plugin
- The Tween Manager
- The Lights Plugin
Scenes communicate by:
- direct method calls e.g.
this.scene.get('sceneName').doIt()
- events through the default Scene
EventEmitter
atthis.events
- events through a custom
EventEmitter
instance, to separate from system events - values and events through
this.registry
, which is intended for scene comms - any other JavaScript-based paradigm like Redux, just pass comm objects to scenes
The right communication approach depends on your game but... Using this.registry
is a good practice, while custom paradigms are only beneficial for games with complex game state and logic. Main benefit of custom paradigms is more thorough testing (no need to rely on Phaser stuff) and forcing separation of logic and visuals.
# the registry approach:
this.registry.set('score', 0);
this.registry.get('score');
this.registry.on('changedata' (_parent, key, data) => void, this)
# redux-in-a-nutshell:
# * Always separate 1) canonical state 2) view state and 3) edit state
# * Input triggers an action.
# * Action updates game state.
# * The action optionally triggers separate action for UI changes.
const state = getGameState()
const store = createGameStore(state)
const scene = new BootScene('BootScene', {store})