Patterns
Here are a few patterns and guidelines that you can use in your Excalibur projects.
Project Structure
We have found this structure to work pretty well for games, see an example game
game/images/image1.pngimage2.pngsounds/sound1.mp3sound2.mp3src/main.tslevel1.tslevel2.tsconfig.tsresources.tspreferences.ts
game/images/image1.pngimage2.pngsounds/sound1.mp3sound2.mp3src/main.tslevel1.tslevel2.tsconfig.tsresources.tspreferences.ts
Entry Point
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)
Resource Loading
Keep all of our assets/resources in one file resources.ts
and load them all at once in the main.ts
ts
// Because snippets uses a bundler we load the image with an import// Optionally do this// 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')constResources = {ImagePlayer : newex .ImageSource ('./player.png'),//... more resources} asconst ; // <-- Important for strong typingconstloader = newex .Loader ();for (letres ofObject .values (Resources )) {loader .addResource (res );}classPlayer extendsex .Actor {publiconInitialize (engine :ex .Engine ) {// set as the "default" drawingthis.graphics .use (Resources .ImagePlayer .toSprite ());}}
ts
// Because snippets uses a bundler we load the image with an import// Optionally do this// 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')constResources = {ImagePlayer : newex .ImageSource ('./player.png'),//... more resources} asconst ; // <-- Important for strong typingconstloader = newex .Loader ();for (letres ofObject .values (Resources )) {loader .addResource (res );}classPlayer extendsex .Actor {publiconInitialize (engine :ex .Engine ) {// set as the "default" drawingthis.graphics .use (Resources .ImagePlayer .toSprite ());}}
Configuration
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
Prefer onInitialize
to the constructor
Where 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()
.
Use ex.Scene
as the Composition Root
Generally it is useful to extend ex.Scene (like in level.ts) and use onInitialize()
to assemble all the different bits of your game.
typescript
class MyLevel extends ex.Scene {onInitialize() {const myActor1 = new ex.Actor({...});this.add(myActor1);const map = new ex.TileMap({...});this.add(map);}}
typescript
class MyLevel extends ex.Scene {onInitialize() {const myActor1 = new ex.Actor({...});this.add(myActor1);const map = new ex.TileMap({...});this.add(map);}}