Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Refactor Actions to ECS System and Component #2061

Merged
merged 12 commits into from
Oct 17, 2021
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ This project adheres to [Semantic Versioning](http://semver.org/).
-
### Changed

-
- Internal Actions implementation converted to ECS system and component, this is a backwards compatible change with v0.25.0
- `ex.ActionsSystem` and `ex.ActionsComponent` now wrap the existing `ex.ActionContext`
- Actions can be shared with all entities now!
### Deprecated

-
- Actions `asPromise()` renamed to `toPromise()`
### Removed

-
Expand Down
2 changes: 1 addition & 1 deletion sandbox/src/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ player.body.canSleep = false;
player.graphics.copyGraphics = false;
follower.actions
.meet(player, 60)
.asPromise()
.toPromise()
.then(() => {
console.log('Player met!!');
});
Expand Down
4 changes: 2 additions & 2 deletions src/engine/Actions/Action.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Actor } from '../Actor';
import { Entity } from '../EntityComponentSystem/Entity';

/**
* Used for implementing actions for the [[ActionContext|Action API]].
*/
export interface Action {
update(delta: number): void;
isComplete(actor: Actor): boolean;
isComplete(entity: Entity): boolean;
reset(): void;
stop(): void;
}
26 changes: 16 additions & 10 deletions src/engine/Actions/Action/Blink.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { Actor } from '../../Actor';
import { GraphicsComponent } from '../../Graphics/GraphicsComponent';
import { Entity } from '../../EntityComponentSystem/Entity';
import { Action } from '../Action';

export class Blink implements Action {
private _graphics: GraphicsComponent;
private _timeVisible: number = 0;
private _timeNotVisible: number = 0;
private _elapsedTime: number = 0;
private _totalTime: number = 0;
private _actor: Actor;
private _duration: number;
private _stopped: boolean = false;
private _started: boolean = false;
constructor(actor: Actor, timeVisible: number, timeNotVisible: number, numBlinks: number = 1) {
this._actor = actor;
constructor(entity: Entity, timeVisible: number, timeNotVisible: number, numBlinks: number = 1) {
this._graphics = entity.get(GraphicsComponent);
this._timeVisible = timeVisible;
this._timeNotVisible = timeNotVisible;
this._duration = (timeVisible + timeNotVisible) * numBlinks;
Expand All @@ -21,21 +22,24 @@ export class Blink implements Action {
if (!this._started) {
this._started = true;
}
if (!this._graphics) {
return;
}

this._elapsedTime += delta;
this._totalTime += delta;
if (this._actor.graphics.visible && this._elapsedTime >= this._timeVisible) {
this._actor.graphics.visible = false;
if (this._graphics.visible && this._elapsedTime >= this._timeVisible) {
this._graphics.visible = false;
this._elapsedTime = 0;
}

if (!this._actor.graphics.visible && this._elapsedTime >= this._timeNotVisible) {
this._actor.graphics.visible = true;
if (!this._graphics.visible && this._elapsedTime >= this._timeNotVisible) {
this._graphics.visible = true;
this._elapsedTime = 0;
}

if (this.isComplete()) {
this._actor.graphics.visible = true;
this._graphics.visible = true;
}
}

Expand All @@ -44,7 +48,9 @@ export class Blink implements Action {
}

public stop(): void {
this._actor.graphics.visible = true;
if (this._graphics) {
this._graphics.visible = true;
}
this._stopped = true;
}

Expand Down
10 changes: 1 addition & 9 deletions src/engine/Actions/Action/Delay.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { Actor } from '../../Actor';
import { Action } from '../Action';

export class Delay implements Action {
public x: number;
public y: number;
private _actor: Actor;
private _elapsedTime: number = 0;
private _delay: number;
private _started: boolean = false;
private _stopped = false;
constructor(actor: Actor, delay: number) {
this._actor = actor;
constructor(delay: number) {
this._delay = delay;
}

Expand All @@ -19,9 +14,6 @@ export class Delay implements Action {
this._started = true;
}

this.x = this._actor.pos.x;
this.y = this._actor.pos.y;

this._elapsedTime += delta;
}

Expand Down
16 changes: 7 additions & 9 deletions src/engine/Actions/Action/Die.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import { Actor } from '../../Actor';
import { Entity } from '../../EntityComponentSystem/Entity';
import { Action } from '../Action';
import { ActionsComponent } from '../ActionsComponent';

export class Die implements Action {
public x: number;
public y: number;

private _actor: Actor;
private _entity: Entity;
private _stopped = false;

constructor(actor: Actor) {
this._actor = actor;
constructor(entity: Entity) {
this._entity = entity;
}

public update(_delta: number): void {
this._actor.actions.clearActions();
this._actor.kill();
this._entity.get(ActionsComponent).clearActions();
this._entity.kill();
this._stopped = true;
}

Expand Down
23 changes: 15 additions & 8 deletions src/engine/Actions/Action/EaseTo.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { Entity} from '../../EntityComponentSystem/Entity';
import { TransformComponent } from '../../EntityComponentSystem/Components/TransformComponent';
import { MotionComponent } from '../../EntityComponentSystem/Components/MotionComponent';
import { Actor } from '../../Actor';
import { vec, Vector } from '../../Math/vector';
import { Action } from '../Action';

export class EaseTo implements Action {
private _tx: TransformComponent;
private _motion: MotionComponent;
private _currentLerpTime: number = 0;
private _lerpDuration: number = 1 * 1000; // 1 second
private _lerpStart: Vector = new Vector(0, 0);
Expand All @@ -11,17 +16,19 @@ export class EaseTo implements Action {
private _stopped: boolean = false;
private _distance: number = 0;
constructor(
public actor: Actor,
entity: Entity,
x: number,
y: number,
duration: number,
public easingFcn: (currentTime: number, startValue: number, endValue: number, duration: number) => number
) {
this._tx = entity.get(TransformComponent);
this._motion = entity.get(MotionComponent);
this._lerpDuration = duration;
this._lerpEnd = new Vector(x, y);
}
private _initialize() {
this._lerpStart = new Vector(this.actor.pos.x, this.actor.pos.y);
this._lerpStart = new Vector(this._tx.pos.x, this._tx.pos.y);
this._currentLerpTime = 0;
this._distance = this._lerpStart.distance(this._lerpEnd);
}
Expand All @@ -34,8 +41,8 @@ export class EaseTo implements Action {

// Need to update lerp time first, otherwise the first update will always be zero
this._currentLerpTime += delta;
let newX = this.actor.pos.x;
let newY = this.actor.pos.y;
let newX = this._tx.pos.x;
let newY = this._tx.pos.y;
if (this._currentLerpTime < this._lerpDuration) {
if (this._lerpEnd.x < this._lerpStart.x) {
newX =
Expand All @@ -53,10 +60,10 @@ export class EaseTo implements Action {
newY = this.easingFcn(this._currentLerpTime, this._lerpStart.y, this._lerpEnd.y, this._lerpDuration);
}
// Given the lerp position figure out the velocity in pixels per second
this.actor.vel = vec((newX - this.actor.pos.x) / (delta / 1000), (newY - this.actor.pos.y) / (delta / 1000));
this._motion.vel = vec((newX - this._tx.pos.x) / (delta / 1000), (newY - this._tx.pos.y) / (delta / 1000));
} else {
this.actor.pos = vec(this._lerpEnd.x, this._lerpEnd.y);
this.actor.vel = Vector.Zero;
this._tx.pos = vec(this._lerpEnd.x, this._lerpEnd.y);
this._motion.vel = Vector.Zero;
}
}
public isComplete(actor: Actor): boolean {
Expand All @@ -67,7 +74,7 @@ export class EaseTo implements Action {
this._initialized = false;
}
public stop(): void {
this.actor.vel = vec(0, 0);
this._motion.vel = vec(0, 0);
this._stopped = true;
}
}
25 changes: 15 additions & 10 deletions src/engine/Actions/Action/Fade.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,57 @@
import { Actor } from '../../Actor';
import { Entity } from '../../EntityComponentSystem/Entity';
import { GraphicsComponent } from '../../Graphics/GraphicsComponent';
import { Logger } from '../../Util/Log';
import { Action } from '../Action';

export class Fade implements Action {
private _graphics: GraphicsComponent;
public x: number;
public y: number;

private _actor: Actor;
private _endOpacity: number;
private _speed: number;
private _multiplier: number = 1;
private _started = false;
private _stopped = false;

constructor(actor: Actor, endOpacity: number, speed: number) {
this._actor = actor;
constructor(entity: Entity, endOpacity: number, speed: number) {
this._graphics = entity.get(GraphicsComponent);
this._endOpacity = endOpacity;
this._speed = speed;
}

public update(delta: number): void {
if (!this._graphics) {
return;
}

if (!this._started) {
this._started = true;

// determine direction when we start
if (this._endOpacity < this._actor.graphics.opacity) {
if (this._endOpacity < this._graphics.opacity) {
this._multiplier = -1;
} else {
this._multiplier = 1;
}
}

if (this._speed > 0) {
this._actor.graphics.opacity += (this._multiplier *
(Math.abs(this._actor.graphics.opacity - this._endOpacity) * delta)) / this._speed;
this._graphics.opacity += (this._multiplier *
(Math.abs(this._graphics.opacity - this._endOpacity) * delta)) / this._speed;
}

this._speed -= delta;

if (this.isComplete()) {
this._actor.graphics.opacity = this._endOpacity;
this._graphics.opacity = this._endOpacity;
}

Logger.getInstance().debug('[Action fade] Actor opacity:', this._actor.graphics.opacity);
Logger.getInstance().debug('[Action fade] Actor opacity:', this._graphics.opacity);
}

public isComplete(): boolean {
return this._stopped || Math.abs(this._actor.graphics.opacity - this._endOpacity) < 0.05;
return this._stopped || Math.abs(this._graphics.opacity - this._endOpacity) < 0.05;
}

public stop(): void {
Expand Down
39 changes: 23 additions & 16 deletions src/engine/Actions/Action/Follow.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { Actor } from '../../Actor';
import { MotionComponent } from '../../EntityComponentSystem/Components/MotionComponent';
import { TransformComponent } from '../../EntityComponentSystem/Components/TransformComponent';
import { Entity } from '../../EntityComponentSystem/Entity';
import { vec, Vector } from '../../Math/vector';
import { Action } from '../Action';

export class Follow implements Action {
private _actor: Actor;
private _actorToFollow: Actor;
private _tx: TransformComponent;
private _motion: MotionComponent;
private _followTx: TransformComponent;
private _followMotion: MotionComponent;

public x: number;
public y: number;
private _current: Vector;
Expand All @@ -16,11 +21,13 @@ export class Follow implements Action {
private _started = false;
private _stopped = false;

constructor(actor: Actor, actorToFollow: Actor, followDistance?: number) {
this._actor = actor;
this._actorToFollow = actorToFollow;
this._current = new Vector(this._actor.pos.x, this._actor.pos.y);
this._end = new Vector(actorToFollow.pos.x, actorToFollow.pos.y);
constructor(entity: Entity, entityToFollow: Entity, followDistance?: number) {
this._tx = entity.get(TransformComponent);
this._motion = entity.get(MotionComponent);
this._followTx = entityToFollow.get(TransformComponent);
this._followMotion = entityToFollow.get(MotionComponent);
this._current = new Vector(this._tx.pos.x, this._tx.pos.y);
this._end = new Vector(this._followTx.pos.x, this._followTx.pos.y);
this._maximumDistance = followDistance !== undefined ? followDistance : this._current.distance(this._end);
this._speed = 0;
}
Expand All @@ -32,31 +39,31 @@ export class Follow implements Action {
this._dir = this._end.sub(this._current).normalize();
}

const actorToFollowSpeed = Math.sqrt(Math.pow(this._actorToFollow.vel.x, 2) + Math.pow(this._actorToFollow.vel.y, 2));
const actorToFollowSpeed = Math.sqrt(Math.pow(this._followMotion.vel.x, 2) + Math.pow(this._followMotion.vel.y, 2));
if (actorToFollowSpeed !== 0) {
this._speed = actorToFollowSpeed;
}
this._current = vec(this._actor.pos.x, this._actor.pos.y);
this._current = vec(this._tx.pos.x, this._tx.pos.y);

this._end = vec(this._actorToFollow.pos.x, this._actorToFollow.pos.y);
this._end = vec(this._followTx.pos.x, this._followTx.pos.y);
this._distanceBetween = this._current.distance(this._end);
this._dir = this._end.sub(this._current).normalize();

if (this._distanceBetween >= this._maximumDistance) {
const m = this._dir.scale(this._speed);
this._actor.vel = vec(m.x, m.y);
this._motion.vel = vec(m.x, m.y);
} else {
this._actor.vel = vec(0, 0);
this._motion.vel = vec(0, 0);
}

if (this.isComplete()) {
this._actor.pos = vec(this._end.x, this._end.y);
this._actor.vel = vec(0, 0);
this._tx.pos = vec(this._end.x, this._end.y);
this._motion.vel = vec(0, 0);
}
}

public stop(): void {
this._actor.vel = vec(0, 0);
this._motion.vel = vec(0, 0);
this._stopped = true;
}

Expand Down
Loading