Goodluck doesn't have version numbers; each commit is the latest release of the template from which you can generate a new project.
However, we do distinguish between generations of Goodluck. The core API changes between generations and there's a significant chance that systems written for one generation will not compatible with other generations. Adapting a system to a newer generation is usually easy, though.
Game
and World
now inherit from GameImpl
and WorldImpl
respectively, defined in common/
(renamed to lib/
in August 2022). Input handling and the game loop are now handled by the base classes, which allows the child classes to be lean and to contain mostly any extra data needed by the example. Mixin
, Blueprint
, and instantiate
are now generic over the type of Game
passed into them.
Once you bootstrap a new project, feel free to merge the super class with Game
in src/game.ts
to save a few bytes.
-
Blueprints are no longer objects of the
{Translation, Rotation, Scale, Using, Children}
shape. Instead, they are now regular arrays of mixings. The transform component must be added explicitly to the blueprint.// BEFORE instantiate(game, { Translation: [1, 1, 0], Using: [light_directional([1, 1, 1], 0.5)], }); // AFTER instantiate(game, [ transform([1, 1, 0]), // Must be listed explicitly. light_directional([1, 1, 1], 0.5) ]);
-
A new component,
Children
, now tracks the children relationships of the entity. The reverse relationship, i.e. the parent relationship, is still stored by theTransform
andTransform2D
component.// BEFORE instantiate(game, { Translation: [0, 1, 5], Rotation: [0, 1, 0, 0], Children: [ { Rotation: [0, 1, 0, 0], Using: [camera_canvas_perspective(1, 0.1, 1000)], }, ], }); // AFTER instantiate(game, [ transform([0, 1, 5], [0, 1, 0, 0]), children([ transform(undefined, [0, 1, 0, 0]), camera_canvas_perspective(1, 0.1, 1000) ]), ]);
-
The parent relationship is now computed every frame by
sys_transform
andsys_transform2d
, and it's undefined upon the creation on the entity.
World.Mask
was renamed toWorld.Signature
in #39.
- New directory layout. Each example is now an independent Goodluck project, with its own copy of
core.ts
,game.ts
, etc. Some game-agnostic code has been moved to the top-levelcommon
directory (renamed tolib
in August 2022).
-
Entity masks and component data are stored in
World
instances.class World { public Mask: Array<number> = []; public Animate: Array<Animate> = []; public AudioSource: Array<AudioSource> = []; // ... public Render: Array<Render> = []; public Transform: Array<Transform> = []; } class Game { public World = new World(); }
-
The
Get
enum has been removed. -
Component checks are performed with bitwise operations between the
World.Mask
array and theHas
enum.if (game.World.Mask[entity] & Has.Transform) ...
-
Component data is retrieved from
game.World
.let transform = game.World.Transform[entity];
-
Names of all properties are PascalCase for better naming mangling and minification.
-
Component masks are defined with a
const enum
, and are typically sorted alphabetically.export const enum Get { Animate, AudioSource, // ... Render, Transform } export const enum Has { Animate = 1 << Get.Animate, AudioSource = 1 << Get.AudioSource, // ... Render = 1 << Get.Render, Transform = 1 << Get.Transform, }
-
Game
is a regular class. Component data is stored under computed properties based on theGet
enum.class Game { public World: Array<number> = []; public [Get.Animate]: Array<Animate> = []; public [Get.AudioSource]: Array<AudioSource> = []; // ... public [Get.Render]: Array<Render> = []; public [Get.Transform]: Array<Transform> = []; }
-
Entity checks are performed with bitwise operations using the
Has
enum.if (game.World[entity] & Has.Transform) ...
-
Source code is written in TypeScript.
-
Names of all properties are lowercase snake_case.
-
Component masks are defined as constants.
export const TRANSFORM = 1 << 0; export const RENDER = 1 << 1; // ...
-
Game extends Array
. Component data is accessed with indices equal to component masks. The returned element must be asserted as the interface corresponding to requested component.let transform = game[TRANSFORM][entity] as Transform;
-
Entity checks are performed with bitwise operations using the component masks.
if (game.world[entity] & TRANSFORM) ...
- Early experiments with ECS.