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.png
    image2.png
  sounds/
    sound1.mp3
    sound2.mp3
  src/
    main.ts
    level1.ts
    level2.ts
    config.ts
    resources.ts
    preferences.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

// 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());
    }
}

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.

class MyLevel extends ex.Scene {

  onInitialize() {
     const myActor1 = new ex.Actor({...});
     this.add(myActor1);

     const map = new ex.TileMap({...});
     this.add(map);
  }
}