Skip to content

Commit

Permalink
feat: Implement ECS Foundations (#1316)
Browse files Browse the repository at this point in the history
This is the ECS foundational work for Excalibur

Related #1361

## Changes:

- New `Entity`, `Component`, `System`, `Query` and management types with tests
  • Loading branch information
eonarheim authored Sep 5, 2020
1 parent 69f8f48 commit 855ce51
Show file tree
Hide file tree
Showing 27 changed files with 1,675 additions and 117 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,17 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Breaking Changes

- [#1361] Makes use of proxies, Excalibur longer supports IE11 :boom: ([#1361]https://github.com/excaliburjs/Excalibur/issues/1361)

### Added

- Adds new ECS Foundations API, which allows excalibur core behavior to be manipulated with ECS style code ([#1361]https://github.com/excaliburjs/Excalibur/issues/1361)
- Adds new `ex.Entity` & `ex.EntityManager` which represent anything that can do something in a Scene and are containers for Components
- Adds new `ex.Component` type which allows encapsulation of state on entities
- Adds new `ex.Query` & `ex.QueryManager` which allows queries over entities that match a component list
- Adds new `ex.System` type which operates on matching Entities to do some behavior in Excalibur.
- Adds new `ex.Observable` a small observable implementation for observing Entity component changes over time

### Changed

### Deprecated
Expand Down
37 changes: 13 additions & 24 deletions src/engine/Actor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Class } from './Class';
import { Texture } from './Resources/Texture';
import {
InitializeEvent,
Expand Down Expand Up @@ -47,6 +46,8 @@ import { obsolete } from './Util/Decorators';
import { Collider } from './Collision/Collider';
import { Shape } from './Collision/Shape';

import { Entity } from './EntityComponentSystem/Entity';

export function isActor(x: any): x is Actor {
return x instanceof Actor;
}
Expand Down Expand Up @@ -79,7 +80,7 @@ export interface ActorDefaults {
* @hidden
*/

export class ActorImpl extends Class implements Actionable, Eventable, PointerEvents, CanInitialize, CanUpdate, CanDraw, CanBeKilled {
export class ActorImpl extends Entity implements Actionable, Eventable, PointerEvents, CanInitialize, CanUpdate, CanDraw, CanBeKilled {
// #region Properties

/**
Expand Down Expand Up @@ -352,7 +353,6 @@ export class ActorImpl extends Class implements Actionable, Eventable, PointerEv
*/
public children: Actor[] = [];

private _isInitialized: boolean = false;
public frames: { [key: string]: Drawable } = {};

/**
Expand Down Expand Up @@ -535,13 +535,6 @@ export class ActorImpl extends Class implements Actionable, Eventable, PointerEv
// Override me
}

/**
* Gets whether the actor is Initialized
*/
public get isInitialized(): boolean {
return this._isInitialized;
}

/**
* Initializes this actor and all it's child actors, meant to be called by the Scene before first update not by users of Excalibur.
*
Expand All @@ -550,11 +543,7 @@ export class ActorImpl extends Class implements Actionable, Eventable, PointerEv
* @internal
*/
public _initialize(engine: Engine) {
if (!this.isInitialized) {
this.onInitialize(engine);
super.emit('initialize', new InitializeEvent(engine, this));
this._isInitialized = true;
}
super._initialize(engine);
for (const child of this.children) {
child._initialize(engine);
}
Expand Down Expand Up @@ -654,9 +643,9 @@ export class ActorImpl extends Class implements Actionable, Eventable, PointerEv
public on(eventName: Events.kill, handler: (event: KillEvent) => void): void;
public on(eventName: Events.prekill, handler: (event: PreKillEvent) => void): void;
public on(eventName: Events.postkill, handler: (event: PostKillEvent) => void): void;
public on(eventName: Events.initialize, handler: (event: InitializeEvent) => void): void;
public on(eventName: Events.preupdate, handler: (event: PreUpdateEvent) => void): void;
public on(eventName: Events.postupdate, handler: (event: PostUpdateEvent) => void): void;
public on(eventName: Events.initialize, handler: (event: InitializeEvent<Actor>) => void): void;
public on(eventName: Events.preupdate, handler: (event: PreUpdateEvent<Actor>) => void): void;
public on(eventName: Events.postupdate, handler: (event: PostUpdateEvent<Actor>) => void): void;
public on(eventName: Events.predraw, handler: (event: PreDrawEvent) => void): void;
public on(eventName: Events.postdraw, handler: (event: PostDrawEvent) => void): void;
public on(eventName: Events.predebugdraw, handler: (event: PreDebugDrawEvent) => void): void;
Expand Down Expand Up @@ -721,9 +710,9 @@ export class ActorImpl extends Class implements Actionable, Eventable, PointerEv
public once(eventName: Events.kill, handler: (event: KillEvent) => void): void;
public once(eventName: Events.postkill, handler: (event: PostKillEvent) => void): void;
public once(eventName: Events.prekill, handler: (event: PreKillEvent) => void): void;
public once(eventName: Events.initialize, handler: (event: InitializeEvent) => void): void;
public once(eventName: Events.preupdate, handler: (event: PreUpdateEvent) => void): void;
public once(eventName: Events.postupdate, handler: (event: PostUpdateEvent) => void): void;
public once(eventName: Events.initialize, handler: (event: InitializeEvent<Actor>) => void): void;
public once(eventName: Events.preupdate, handler: (event: PreUpdateEvent<Actor>) => void): void;
public once(eventName: Events.postupdate, handler: (event: PostUpdateEvent<Actor>) => void): void;
public once(eventName: Events.predraw, handler: (event: PreDrawEvent) => void): void;
public once(eventName: Events.postdraw, handler: (event: PostDrawEvent) => void): void;
public once(eventName: Events.predebugdraw, handler: (event: PreDebugDrawEvent) => void): void;
Expand Down Expand Up @@ -799,9 +788,9 @@ export class ActorImpl extends Class implements Actionable, Eventable, PointerEv
public off(eventName: Events.pointerdragmove, handler?: (event: PointerDragEvent) => void): void;
public off(eventName: Events.prekill, handler?: (event: PreKillEvent) => void): void;
public off(eventName: Events.postkill, handler?: (event: PostKillEvent) => void): void;
public off(eventName: Events.initialize, handler?: (event: Events.InitializeEvent) => void): void;
public off(eventName: Events.postupdate, handler?: (event: Events.PostUpdateEvent) => void): void;
public off(eventName: Events.preupdate, handler?: (event: Events.PreUpdateEvent) => void): void;
public off(eventName: Events.initialize, handler?: (event: Events.InitializeEvent<Actor>) => void): void;
public off(eventName: Events.postupdate, handler?: (event: Events.PostUpdateEvent<Actor>) => void): void;
public off(eventName: Events.preupdate, handler?: (event: Events.PreUpdateEvent<Actor>) => void): void;
public off(eventName: Events.postdraw, handler?: (event: Events.PostDrawEvent) => void): void;
public off(eventName: Events.predraw, handler?: (event: Events.PreDrawEvent) => void): void;
public off(eventName: Events.enterviewport, handler?: (event: EnterViewPortEvent) => void): void;
Expand Down
18 changes: 9 additions & 9 deletions src/engine/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,13 +375,13 @@ export class Engine extends Class implements CanInitialize, CanUpdate, CanDraw {

private _isInitialized: boolean = false;

public on(eventName: Events.initialize, handler: (event: Events.InitializeEvent) => void): void;
public on(eventName: Events.initialize, handler: (event: Events.InitializeEvent<Engine>) => void): void;
public on(eventName: Events.visible, handler: (event: VisibleEvent) => void): void;
public on(eventName: Events.hidden, handler: (event: HiddenEvent) => void): void;
public on(eventName: Events.start, handler: (event: GameStartEvent) => void): void;
public on(eventName: Events.stop, handler: (event: GameStopEvent) => void): void;
public on(eventName: Events.preupdate, handler: (event: PreUpdateEvent) => void): void;
public on(eventName: Events.postupdate, handler: (event: PostUpdateEvent) => void): void;
public on(eventName: Events.preupdate, handler: (event: PreUpdateEvent<Engine>) => void): void;
public on(eventName: Events.postupdate, handler: (event: PostUpdateEvent<Engine>) => void): void;
public on(eventName: Events.preframe, handler: (event: PreFrameEvent) => void): void;
public on(eventName: Events.postframe, handler: (event: PostFrameEvent) => void): void;
public on(eventName: Events.predraw, handler: (event: PreDrawEvent) => void): void;
Expand All @@ -391,13 +391,13 @@ export class Engine extends Class implements CanInitialize, CanUpdate, CanDraw {
super.on(eventName, handler);
}

public once(eventName: Events.initialize, handler: (event: Events.InitializeEvent) => void): void;
public once(eventName: Events.initialize, handler: (event: Events.InitializeEvent<Engine>) => void): void;
public once(eventName: Events.visible, handler: (event: VisibleEvent) => void): void;
public once(eventName: Events.hidden, handler: (event: HiddenEvent) => void): void;
public once(eventName: Events.start, handler: (event: GameStartEvent) => void): void;
public once(eventName: Events.stop, handler: (event: GameStopEvent) => void): void;
public once(eventName: Events.preupdate, handler: (event: PreUpdateEvent) => void): void;
public once(eventName: Events.postupdate, handler: (event: PostUpdateEvent) => void): void;
public once(eventName: Events.preupdate, handler: (event: PreUpdateEvent<Engine>) => void): void;
public once(eventName: Events.postupdate, handler: (event: PostUpdateEvent<Engine>) => void): void;
public once(eventName: Events.preframe, handler: (event: PreFrameEvent) => void): void;
public once(eventName: Events.postframe, handler: (event: PostFrameEvent) => void): void;
public once(eventName: Events.predraw, handler: (event: PreDrawEvent) => void): void;
Expand All @@ -407,13 +407,13 @@ export class Engine extends Class implements CanInitialize, CanUpdate, CanDraw {
super.once(eventName, handler);
}

public off(eventName: Events.initialize, handler?: (event: Events.InitializeEvent) => void): void;
public off(eventName: Events.initialize, handler?: (event: Events.InitializeEvent<Engine>) => void): void;
public off(eventName: Events.visible, handler?: (event: VisibleEvent) => void): void;
public off(eventName: Events.hidden, handler?: (event: HiddenEvent) => void): void;
public off(eventName: Events.start, handler?: (event: GameStartEvent) => void): void;
public off(eventName: Events.stop, handler?: (event: GameStopEvent) => void): void;
public off(eventName: Events.preupdate, handler?: (event: PreUpdateEvent) => void): void;
public off(eventName: Events.postupdate, handler?: (event: PostUpdateEvent) => void): void;
public off(eventName: Events.preupdate, handler?: (event: PreUpdateEvent<Engine>) => void): void;
public off(eventName: Events.postupdate, handler?: (event: PostUpdateEvent<Engine>) => void): void;
public off(eventName: Events.preframe, handler?: (event: PreFrameEvent) => void): void;
public off(eventName: Events.postframe, handler?: (event: PostFrameEvent) => void): void;
public off(eventName: Events.predraw, handler?: (event: PreDrawEvent) => void): void;
Expand Down
70 changes: 70 additions & 0 deletions src/engine/EntityComponentSystem/Component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Entity } from './Entity';

export interface ComponentCtor {
new (): Component;
}

function hasClone(x: any): x is { clone(): any } {
return !!x?.clone;
}

/**
* Components are containers for state in Excalibur, the are meant to convey capabilities that an Entity posesses
*
* Implementations of Component must have a zero-arg constructor
*/
export abstract class Component<TypeName extends string = string> {
/**
* Optionally list any component types this component depends on
* If the owner entity does not have these components, new components will be added to the entity
*
* Only components with zero-arg constructors are supported as automatic component dependencies
*/
readonly dependencies?: ComponentCtor[];

/**
* Type of this component, must be a unique type among component types in you game.
* See [[BuiltinComponentTypes]] for a list of built in excalibur types
*/
abstract readonly type: TypeName;

/**
* Current owning [[Entity]], if any, of this component. Null if not added to any [[Entity]]
*/
owner?: Entity;
clone(): this {
const newComponent = new (this.constructor as any)();
for (const prop in this) {
if (this.hasOwnProperty(prop)) {
const val = this[prop];
if (hasClone(val) && prop !== 'owner' && prop !== 'clone') {
newComponent[prop] = val.clone();
} else {
newComponent[prop] = val;
}
}
}
return newComponent;
}

/**
* Optional callback called when a component is added to an entity
*/
onAdd?: (owner: Entity) => void;

/**
* Opitonal callback called when acomponent is added to an entity
*/
onRemove?: (previousOwner: Entity) => void;
}

/**
* Tag components are a way of tagging a component with label and a simple value
*/
export class TagComponent<TypeName extends string, MaybeValueType extends string | symbol | number | boolean = never> extends Component<
TypeName
> {
constructor(public readonly type: TypeName, public readonly value?: MaybeValueType) {
super();
}
}
Loading

0 comments on commit 855ce51

Please sign in to comment.