Here are a few patterns and guidelines that you can use in your Excalibur projects.
We have found this structure to work pretty well for games, see an example game
game/
images/
image1.png
image2.png
sounds/
sound1.mp3
sound2.mp3
src/
main.ts
level1.ts
level2.ts
config.ts
resources.ts
preferences.ts
Define a main.ts
file to serve as the "main" entrypoint into your game.
Keep the main.ts
entrypoint as small as possible (this is where we create a new ex.Engine()
and configure anything else at the engine level)
Keep all of our assets/resources in one file resources.ts
and load them all at once in the main.ts
// Because snippets uses a bunder we load the image with an import
import playerUrl from './player.png';
// If you aren't using a bundler like parcel or webpack you can do this:
// const imagePlayer = new ex.ImageSource('./player.png')
const Resources = {
ImagePlayer: new ex.ImageSource(playerUrl),
//... more resources
}
const loader = new ex.Loader([...Object.values(Resources)]);
class Player extends ex.Actor {
public onInitialize(engine: ex.Engine) {
// set as the "default" drawing
this.graphics.use(Resources.ImagePlayer.toSprite());
}
}
Keep a config.ts
for tweaking global constant values easily, we've wired up things like dat.gui or tweakpane that make changing config during development easy
onInitialize
to the constructorWhere possible we use onInitialize()
for initialization logic over the constructor, this saves CPU cycles because it's called before the first update an entity is needed and makes it easy to restart a game or reusing something by manually calling onInitialize()
.
ex.Scene
as the Composition RootGenerally it is useful to extend ex.Scene (like in level.ts) and use onInitialize()
to assemble all the different bits of your game.
class MyLevel extends ex.Scene {
onInitialize() {
const myActor1 = new ex.Actor({...});
this.add(myActor1);
const map = new ex.TileMap({...});
this.add(map);
}
}