Skip to content
Jesse Himmelstein edited this page May 22, 2023 · 1 revision

Welome to the Booyah wiki. Our goal with the wiki is to document how to make games with Booyah.

Booyah is a JavaScript game engine, optimized for mobile.

Getting Started

Start by following the instructions in the Booyah README to initialize your projet directory and install dependencies.

You will also need a local web server, since many browser features are disabled when loading files from the local file system. We recommend nginx, which is stable and fast, but many possibilities exist for each platform.

Development cycle

Booyah games are made so that they can run directly from the browser (assuming a modern browser that supports ES modules). Therefore the general cycle is write code, reload browser, test game.

For testing on mobile, the easiest is to point your mobile browser to your local web server. Tools like ngrok and serveo can help with this by exposing your local web server over a temporary internet gateway.

For production, we use the gulp tool to make the code accessible to less modern browsers. The build step removes the modules using rollup, and then the dist step minimizes the code.

Folder structure

The folder structure for a Booyah game is the following:

  • index.html - The HTML file containing the game. Point your browser here to start your game. It has some template information (such as build time) that will be transformed by the gulp task "build". It has some social media information and analytics in it already, commented out by default.
  • game.css - A single CSS file for formatting the HTML file. In particular, it handles resizing the game window correctly.
  • package.json - Used to install dependencies with NPM, and also to get information on the name of the game for the deployment task.
  • booyah/ - The Booyah engine is contained as a subdirectory rather than in node_modules/, to help in developing it quickly. When we initialize a new game, we bring in the example files stored in booyah/project_files/ as a starting point.
  • src/ All the JS source files. By default, game.js is imported and run.
  • images/ - All images files, as well as any spritesheet definitions.
  • audio/fx/ - Sound effect files, in MP3 format.
  • audio/music/ - Music files, in MP3 format. Music and sound fx are treated slightly differently in Booyah.
  • fonts/ - Font assets in WOFF and WOFF2 formats. Fonts are loaded with CSS.
  • text/ - Text assets, in JSON format.
  • video/ - Video assets, in MP4 format.
  • build/ - A version of the game without modules, built with the Rollup tool through gulp.
  • dist/ - A minimized version of the game, created with gulp.

Please keep filenames in lowercase. Otherwise it can be hard to change the case down the line, since both MacOS and Windows don't handle case-sensitivity by default.

Coding Standards

To save time dealing with coding standards, we use Prettier. It integrates nicely into Visual Studio Code, so that Prettier will reformat the code upon save.

In the same vein, we use ESLint to catch certain coding errors.

Currently there is no automated testing, which is not something that we're proud of.

In addition to what Prettier enforces, there are a few standards that we enforce in Booyah:

  • Indents: 2-character indents, using spaces.
  • Case: Camel-case variables and class names. Class names start with capital letters
  • Blank lines between functions and methods. Prettier will remove multiple blank lines.
  • Private methods should start with an underscore. The same can be done with private variables.
  • Documentation: Document classes and methods using multiline comments like /** ... */. For the time being we have not chosen a standard document format, but it would be good to have generated documentation like what PIXI uses.

Libraries

We try to use the latest standard of ECMAScript to our full advantage, relying on Babel and core.js to make these features work for older browsers.

We also rely heavily on the following libraries:

Entities

Entities form the heart of the Booyah engine. The name "entity" can mean a lot of different things- for example Entity-Component Systems such as used by the popular game engine Unity are groups of components. But the meaning for Booyah is different.

In Booyah, entities control the input, output, logic, and timing within the game. They are the building blocks of the game code. They are structured in hierarchies/trees, so that some top-level entities will control lower-level ones. In that way, they are composable- more complex entities are build out of less-complex ones.

All entities go through the same lifecycle:

  • Construction - An entity is built using the constructor. Any options for how the entity works should be sent here. However the entity doesn't start "doing" anything yet, instead waiting for the next step. At this point, we would call the entity inactive.
  • Setup - setup(config) is called once, with a static configuration. This makes the entity active - it do any graphical setup at this point, as well as subscribing to any events, etc. If the entity is done already, it can set this.requestedTransition at this point.
  • Update - On subsequent frames, update(options) is called. Therefore it can be called zero times, one time, or during the whole game. If the entity is "done", it should set this.requestedTransition.
  • Teardown - teardown(options) is called once. This is the opposite of setup. The entity should remove any graphics, unsubscribe from any events (although those events subscribed to using _on() will automatically be unsubscribed), etc. The entity could be torn down because it asked, by setting this.requestedTransition, or because the parent entity decided to stop it. After teardown, the entity returns to an inactive state - the same as it was after construction - ready to be setup and used again! Or perhaps the entity will not be used again, and garbage collected.

All entities derive from the Entity class in the entity module. The base class does some error checking, such as detecting when setup() was called twice in a row. It stores the config sent to setup(config) as this.config and resets this.requestedTransition. Also, it provides the _on() and _off() methods for subscribing to events in such a way as to automatically unsubscribe on teardown.

For convenience, an entity can define the "underscored" private methods such as _setup() and _update() instead of the public ones. In this case, the developer doesn't have to remember to call the overridden method from the parent class.

What's the difference between update() and _update()?

You're free to override update() if you want. But you need to call the base class as well, using super.update(...). This creates a lot of boilerplate, and is error-prone.

This is where the _update() method comes in. The default implementation of update() will call _update(), so most of the time this is the correct choice.

The exception to this rule is if you don't want to do the default behavior of update(). For example, if you are extending a ParallelEntity and you don't want it to update the child entities for some reason, or want to update them in a different order, you should override update() to handle this logic.

Game loop

Booyah manages the game loop, aiming for 60 frames a second.

On each frame, the game loop calls update() on the root entity, and then renders the PIXI scene.

Root entity

At the base of the entity hierarchy is the rootEntity, created by Booyah during the startup process. During the loading process this is set to the Loading Entity, and then it is set to a ParallelEntity containing the installed entities as well as the game state machine.

Config

An entity's config is what is provided to it during the setup phase. It is a read-only object containing static information about the game, links to assets and important entities. Since it doesn't change for a given entity, it is provided to the entity only once, during setup. The base Entity class stores a reference to the config under this.config.

Perhaps the most commonly used property of config is config.container, a PIXI.Container of the parent entity. Entities will add graphics to this container during setup, and remove them during teardown.

The rootConfig, created by Booyah and provided to root entity, contains the following properties:

  • directives - The configuration given to booyah.go()
  • app - The PIXI.js application object.
  • app.renderer - The PIXI renderer, useful for getting the size of the game through app.renderer.width and app.renderer.height.
  • app.loader - The PIXI loader, contains all the image and video resources except those given to the preloader.
  • preloader - A Resource loader used to bootstrap the rest of the loading process. It contains only the resources necessary to show the loading screen.
  • container - The PIXI.Container in which we should put new graphical objects. At the root, this is the same as the PIXI stage. But other entities will change this reference to point to containers that they will put there. For example, a menu entity would create a container and overload this config option so that a child button entity would place its own graphics within the menu container.

You can alter the config in two ways.

  1. Locally: To add to the config of child entities, you can use the 2nd argument to ParallelEntity.addEntity(entity, config), or simply call setup() of child entities with an altered config.
  2. Globally: add "entity installers" to the entityInstallers directive of booyah.go(). These functions are called before the game engine is started, and allow for adding new entities as children of the rootEntity and put links to themselves in the root config. For example, the music player Jukebox and FxMachine use this system, so that you can call this.config.jukebox.changeMusic().

Since new entities are given access to the same config (copy by reference), it is important to not alter the config directly in an entity, as this could change things for all entities, in unexpected ways.

To see the other properties that are present in the default config, search rootConfig in booyah.js.

Options

In contrast to config, the options are information provided to an entity at each frame, in the update() and teardown() methods. The options contain information specific to the frame itself:

  • playTime - Total amount of time (in ms) the game has been running, but not paused
  • timeSinceStart - Amount of time (in ms) since the entity has started. NOTE: This is currently only used correctly by the StateMachineEntity, and will later be changed to have each entity track the time it has run (see issue #11).
  • timeSinceLastFrame - Time (in ms) since the last frame has executed.
  • timeScale - The fraction of time between frames over the expected time, so a value of 1 when the game is running at 60 FPS, or less if the game is running slowly. Provided by deltaTime in PIXI
  • gameState - "playing" or "paused". Currently, the game state machine will not be executed when the game is paused, but other entities will be, such as the menu and jukebox.

Typically, an entity does not need to manipulate the options for child entities.

Transitions

A transition is a mechanism for child entities to tell parent entities that they are ready to stop. Not all entities need to request transitions, but here are some examples of those that would:

  • An entity playing a video, saying the video is done
  • An entity that manages a level, saying that the level is over.
  • An entity that only wants to execute once, saying that that work is done
  • An entity that just waits, saying that the wait is over

All entities have a property requestedTransition, that is set to null in setup. To request a transition, an entity sets transition to something "truthy" (e.g. true, "next", etc.). An entity can provide additional information in the transition. To do so, it should return an object like { name: string, params: object }, where the name provides the name of the transition and parameters is an optional set of extra information. This information is used in particular by StateMachine to decide what state to move to next.

Signals

Besides the lifecycle we just described, entities can also receive signals from their parent entity. This is done by calling onSignal(name, data). Currently the only signals defined are "play" and "pause", so that an entity can stop playing sound, for example. The idea of signals is to send them to all the child entities, in a top-down manner.

Events

Another way for entities to communicate is through events. Events are sent using the EventEmitter3 library, which is also used by PIXI. As a convenience, the base Entity class inherits from EventEmitter.

To broadcast events from within an entity, use the method emit(event, ...). To listen to events from other event emitters, you can use the _on(emitter, event, cb) method. In addition to listening to the events, it will unsubscribe to the events on teardown (using _off()) and will call the provided function with this set to the entity instance.

In contrast to signals, events are not meant to be broadcast to all entities. Instead, they are a useful way for any entity to keep track of another, irregardless of their place within the entity hierarchy.

Starting Booyah

The rest of the Booyah game engine is basically preloading the needed assets, starting the game, and running the root game entity at the correct framerate.

To start Booyah use the go() function. It accepts a bunch of options, called directives to distinguish them from the options used elsewhere. They are defined and documented (albeit quickly) at the top of booyah.js, at DEFAULT_DIRECTIVES.

All the directives are later available to entities as this.config.directives. In that way a developer can add in any new arbitrary directives they want.

Booyah does the loading in several stages. These are set as gameState:

  • preloading - Loads the game splash screen and the assets needed to display the loading spinner.
  • loadingFixed - Loads all the remaining assets defined in the directives, including audio, video, images, fonts, and text. The loaded assets are put onto the rootConfig. For example, the assets defined in fxAssets are put into the map rootConfig.fxAudio. The LoadingEntity is displayed at this point, with a progress bar.
  • loadingVariable - If the game defined extraLoaders, they are called here. An extra loader must return a promise that is resolved when the loading is complete, or rejected if it encounters an error. Loaders can modify the root config to store the assets.
  • ready - The game is ready to play. At this point, Booyah switches to a looping sequence of entities:
    • ReadyScene - wait for the player to hit play
    • A StateMachine, based on the information provided in "states" and "transitions" of the directives.
    • DoneScene - wait for the player to hit restart

The go() function returns a loading promise, the root entity, and the root config, although for normal use case these can be safely ignored.

Controlling Booyah via the URL

You can do control certain aspects of Booyah in the URL. For example, you can start from a certain scene and mute the music and/or sound fx.

To do so, simply add parameters to the query string in the URL. For example: http://localhost:8080/my-game/?mute=1&subtitles=0

In URL parameters, the strings "false", "0", or "off" are all considered false. Everything else is considered true.

Here are the options:

  • mute - if true, shut off music and sound fx
  • music - have music at start?
  • fx - have fx at start?
  • subtitles - show subtitles at start?
  • scene - name of the scene/state to start in
  • params - params of the scene/state (in JSON)
  • progress - the progress to set in the game state machine (in JSON)