Skip to content

Commit

Permalink
[#1146] Update actions to work relative to position
Browse files Browse the repository at this point in the history
  • Loading branch information
eonarheim committed Jun 2, 2019
1 parent faf704b commit 2c5e6b3
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 84 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -23,6 +23,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Changed

- Changed `moveBy`, `rotateBy`, and `scaleBy` to operate relative to the current actor position at a speed, instead of moving to an absolute by a certain time.
- Changed event handlers in excalibur to expect non-null event objects, before `hander: (event?: GameEvent) => void` implied that event could be null. This change addresses ([#1147](https://github.com/excaliburjs/Excalibur/issues/1147)) making strict null/function checks compatible with new typescript.
- Changed collision system to remove actor coupling, in addition `ex.Collider` is a new type that encapsulates all collision behavior. Use `ex.Actor.body.collider` to interact with collisions in Excalibur ([#1119](https://github.com/excaliburjs/Excalibur/issues/1119))

Expand Down
77 changes: 39 additions & 38 deletions src/engine/Actions/Action.ts
Expand Up @@ -144,35 +144,34 @@ export class MoveBy implements Action {
public y: number;
private _distance: number;
private _speed: number;
private _time: number;

private _start: Vector;
private _offset: Vector;
private _end: Vector;
private _dir: Vector;
private _started = false;
private _stopped = false;
constructor(actor: Actor, destx: number, desty: number, time: number) {

constructor(actor: Actor, offsetX: number, offsetY: number, speed: number) {
this._actor = actor;
this._end = new Vector(destx, desty);
if (time <= 0) {
Logger.getInstance().error('Attempted to moveBy time less than or equal to zero : ' + time);
throw new Error('Cannot move in time <= 0');
this._speed = speed;
this._offset = new Vector(offsetX, offsetY);
if (speed <= 0) {
Logger.getInstance().error('Attempted to moveBy with speed less than or equal to zero : ' + speed);
throw new Error('Speed must be greater than 0 pixels per second');
}
this._time = time;
}

public update(_delta: number) {
if (!this._started) {
this._started = true;
this._start = new Vector(this._actor.pos.x, this._actor.pos.y);
this._distance = this._start.distance(this._end);
this._end = this._start.add(this._offset);
this._distance = this._offset.magnitude();
this._dir = this._end.sub(this._start).normalize();
this._speed = this._distance / (this._time / 1000);
}

const m = this._dir.scale(this._speed);
this._actor.vel.x = m.x;
this._actor.vel.y = m.y;
this._actor.vel = this._dir.scale(this._speed);

if (this.isComplete(this._actor)) {
this._actor.pos.x = this._end.x;
Expand All @@ -183,7 +182,7 @@ export class MoveBy implements Action {
}

public isComplete(actor: Actor): boolean {
return this._stopped || new Vector(actor.pos.x, actor.pos.y).distance(this._start) >= this._distance;
return this._stopped || actor.pos.distance(this._start) >= this._distance;
}

public stop(): void {
Expand Down Expand Up @@ -449,7 +448,8 @@ export class RotateBy implements Action {
private _start: number;
private _end: number;
private _speed: number;
private _time: number;
private _offset: number;

private _rotationType: RotationType;
private _direction: number;
private _distance: number;
Expand All @@ -458,17 +458,19 @@ export class RotateBy implements Action {
private _shortestPathIsPositive: boolean;
private _started = false;
private _stopped = false;
constructor(actor: Actor, angleRadians: number, time: number, rotationType?: RotationType) {
constructor(actor: Actor, angleRadiansOffset: number, speed: number, rotationType?: RotationType) {
this._actor = actor;
this._end = angleRadians;
this._time = time;
this._speed = speed;
this._offset = angleRadiansOffset;
this._rotationType = rotationType || RotationType.ShortestPath;
}

public update(_delta: number): void {
if (!this._started) {
this._started = true;
this._start = this._actor.rotation;
this._end = this._start + this._offset;

const distance1 = Math.abs(this._end - this._start);
const distance2 = Util.TwoPI - distance1;
if (distance1 > distance2) {
Expand Down Expand Up @@ -515,7 +517,6 @@ export class RotateBy implements Action {
}
break;
}
this._speed = Math.abs((this._distance / this._time) * 1000);
}

this._actor.rx = this._direction * this._speed;
Expand Down Expand Up @@ -617,40 +618,39 @@ export class ScaleBy implements Action {
private _actor: Actor;
public x: number;
public y: number;
private _startX: number;
private _startY: number;
private _endX: number;
private _endY: number;
private _startScale: Vector;
private _endScale: Vector;
private _offset: Vector;
private _distanceX: number;
private _distanceY: number;
private _directionX: number;
private _directionY: number;
private _started = false;
private _stopped = false;
private _speedX: number;
private _speedY: number;
constructor(actor: Actor, scaleX: number, scaleY: number, time: number) {
constructor(actor: Actor, scaleOffsetX: number, scaleOffsetY: number, speed: number) {
this._actor = actor;
this._endX = scaleX;
this._endY = scaleY;
this._speedX = ((this._endX - this._actor.scale.x) / time) * 1000;
this._speedY = ((this._endY - this._actor.scale.y) / time) * 1000;
this._offset = new Vector(scaleOffsetX, scaleOffsetY);
this._speedX = this._speedY = speed;
}

public update(_delta: number): void {
if (!this._started) {
this._started = true;
this._startX = this._actor.scale.x;
this._startY = this._actor.scale.y;
this._distanceX = Math.abs(this._endX - this._startX);
this._distanceY = Math.abs(this._endY - this._startY);
this._startScale = this._actor.scale.clone();
this._endScale = this._startScale.add(this._offset);
this._distanceX = Math.abs(this._endScale.x - this._startScale.x);
this._distanceY = Math.abs(this._endScale.y - this._startScale.y);
this._directionX = this._endScale.x < this._startScale.x ? -1 : 1;
this._directionY = this._endScale.y < this._startScale.y ? -1 : 1;
}
const directionX = this._endX < this._startX ? -1 : 1;
const directionY = this._endY < this._startY ? -1 : 1;
this._actor.sx = this._speedX * directionX;
this._actor.sy = this._speedY * directionY;

this._actor.sx = this._speedX * this._directionX;
this._actor.sy = this._speedY * this._directionY;

if (this.isComplete()) {
this._actor.scale.x = this._endX;
this._actor.scale.y = this._endY;
this._actor.scale = this._endScale;
this._actor.sx = 0;
this._actor.sy = 0;
}
Expand All @@ -659,7 +659,8 @@ export class ScaleBy implements Action {
public isComplete(): boolean {
return (
this._stopped ||
(Math.abs(this._actor.scale.x - this._startX) >= this._distanceX && Math.abs(this._actor.scale.y - this._startY) >= this._distanceY)
(Math.abs(this._actor.scale.x - this._startScale.x) >= this._distanceX &&
Math.abs(this._actor.scale.y - this._startScale.y) >= this._distanceY)
);
}

Expand Down
43 changes: 21 additions & 22 deletions src/engine/Actions/ActionContext.ts
Expand Up @@ -90,17 +90,16 @@ export class ActionContext {
}

/**
* This method will move an actor to the specified x and y position by a
* certain time (in milliseconds). This method is part of the actor
* 'Action' fluent API allowing action chaining.
* @param x The x location to move the actor to
* @param y The y location to move the actor to
* @param time The time it should take the actor to move to the new location in milliseconds
* This method will move an actor by the specified x offset and y offset from its current position, at a certain speed.
* This method is part of the actor 'Action' fluent API allowing action chaining.
* @param xOffset The x offset to apply to this actor
* @param yOffset The y location to move the actor to
* @param speed The speed in pixels per second the actor should move
*/
public moveBy(x: number, y: number, time: number): ActionContext {
public moveBy(xOffset: number, yOffset: number, speed: number): ActionContext {
const len = this._queues.length;
for (let i = 0; i < len; i++) {
this._queues[i].add(new Actions.MoveBy(this._actors[i], x, y, time));
this._queues[i].add(new Actions.MoveBy(this._actors[i], xOffset, yOffset, speed));
}
return this;
}
Expand All @@ -122,17 +121,17 @@ export class ActionContext {
}

/**
* This method will rotate an actor to the specified angle by a certain
* time (in milliseconds) and return back the actor. This method is part
* This method will rotate an actor by the specified angle offset, from it's current rotation given a certain speed
* in radians/sec and return back the actor. This method is part
* of the actor 'Action' fluent API allowing action chaining.
* @param angleRadians The angle to rotate to in radians
* @param time The time it should take the actor to complete the rotation in milliseconds
* @param rotationType The [[RotationType]] to use for this rotation
* @param angleRadiansOffset The angle to rotate to in radians relative to the current rotation
* @param speed The speed in radians/sec the actor should rotate at
* @param rotationType The [[RotationType]] to use for this rotation, default is shortest path
*/
public rotateBy(angleRadians: number, time: number, rotationType?: RotationType): ActionContext {
public rotateBy(angleRadiansOffset: number, speed: number, rotationType?: RotationType): ActionContext {
const len = this._queues.length;
for (let i = 0; i < len; i++) {
this._queues[i].add(new Actions.RotateBy(this._actors[i], angleRadians, time, rotationType));
this._queues[i].add(new Actions.RotateBy(this._actors[i], angleRadiansOffset, speed, rotationType));
}
return this;
}
Expand All @@ -156,17 +155,17 @@ export class ActionContext {
}

/**
* This method will scale an actor to the specified size by a certain time
* (in milliseconds) and return back the actor. This method is part of the
* This method will scale an actor by an amount relative to the current scale at a certin speed in scale units/sec
* and return back the actor. This method is part of the
* actor 'Action' fluent API allowing action chaining.
* @param sizeX The scaling factor to apply on X axis
* @param sizeY The scaling factor to apply on Y axis
* @param time The time it should take to complete the scaling in milliseconds
* @param sizeOffsetX The scaling factor to apply on X axis
* @param sizeOffsetY The scaling factor to apply on Y axis
* @param speed The speed to scale at in scale units/sec
*/
public scaleBy(sizeX: number, sizeY: number, time: number): ActionContext {
public scaleBy(sizeOffsetX: number, sizeOffsetY: number, speed: number): ActionContext {
const len = this._queues.length;
for (let i = 0; i < len; i++) {
this._queues[i].add(new Actions.ScaleBy(this._actors[i], sizeX, sizeY, time));
this._queues[i].add(new Actions.ScaleBy(this._actors[i], sizeOffsetX, sizeOffsetY, speed));
}
return this;
}
Expand Down
35 changes: 12 additions & 23 deletions src/spec/ActionSpec.ts
Expand Up @@ -145,7 +145,7 @@ describe('Action', () => {
expect(actor.pos.x).toBe(0);
expect(actor.pos.y).toBe(0);

actor.actions.moveBy(100, 0, 2000);
actor.actions.moveBy(100, 0, 50);

actor.update(engine, 1000);
expect(actor.pos.x).toBe(50);
Expand All @@ -156,7 +156,7 @@ describe('Action', () => {
expect(actor.pos.x).toBe(0);
expect(actor.pos.y).toBe(0);

actor.actions.moveBy(20, 0, 1000);
actor.actions.moveBy(20, 0, 20);
actor.update(engine, 500);

actor.actions.clearActions();
Expand Down Expand Up @@ -430,22 +430,10 @@ describe('Action', () => {
});

describe('rotateBy', () => {
// it('can be rotated to an angle by a certain time', () => {
// expect(actor.rotation).toBe(0);

// actor.rotateBy(Math.PI/2, 2000);
// actor.update(engine, 1000);

// expect(actor.rotation).toBe(Math.PI/4);
// actor.update(engine, 1000);

// expect(actor.rotation).toBe(Math.PI/2);
//});

it('can be rotated to an angle by a certain time via ShortestPath (default)', () => {
expect(actor.rotation).toBe(0);

actor.actions.rotateBy(Math.PI / 2, 2000);
actor.actions.rotateBy(Math.PI / 2, Math.PI / 4);

actor.update(engine, 1000);
expect(actor.rotation).toBe(Math.PI / 4);
Expand All @@ -460,7 +448,7 @@ describe('Action', () => {
it('can be rotated to an angle by a certain time via LongestPath', () => {
expect(actor.rotation).toBe(0);

actor.actions.rotateBy(Math.PI / 2, 3000, ex.RotationType.LongestPath);
actor.actions.rotateBy(Math.PI / 2, Math.PI / 2, ex.RotationType.LongestPath);

actor.update(engine, 1000);
expect(actor.rotation).toBe((-1 * Math.PI) / 2);
Expand All @@ -476,7 +464,7 @@ describe('Action', () => {
it('can be rotated to an angle by a certain time via Clockwise', () => {
expect(actor.rotation).toBe(0);

actor.actions.rotateBy(Math.PI / 2, 1000, ex.RotationType.Clockwise);
actor.actions.rotateBy(Math.PI / 2, Math.PI / 2, ex.RotationType.Clockwise);

actor.update(engine, 500);
expect(actor.rotation).toBe(Math.PI / 4);
Expand All @@ -492,7 +480,7 @@ describe('Action', () => {
it('can be rotated to an angle by a certain time via CounterClockwise', () => {
expect(actor.rotation).toBe(0);

actor.actions.rotateBy(Math.PI / 2, 3000, ex.RotationType.LongestPath);
actor.actions.rotateBy(Math.PI / 2, Math.PI / 2, ex.RotationType.LongestPath);

actor.update(engine, 1000);
expect(actor.rotation).toBe((-1 * Math.PI) / 2);
Expand All @@ -507,7 +495,7 @@ describe('Action', () => {
it('can be stopped', () => {
expect(actor.rotation).toBe(0);

actor.actions.rotateBy(Math.PI / 2, 2000);
actor.actions.rotateBy(Math.PI / 2, Math.PI / 4);

actor.update(engine, 1000);
actor.actions.clearActions();
Expand Down Expand Up @@ -560,22 +548,23 @@ describe('Action', () => {
expect(actor.scale.x).toBe(1);
expect(actor.scale.y).toBe(1);

actor.actions.scaleBy(4, 5, 1000);
actor.actions.scaleBy(4, 4, 4);

actor.update(engine, 500);
expect(actor.scale.x).toBe(2.5);
expect(actor.scale.x).toBe(3);
expect(actor.scale.y).toBe(3);

actor.update(engine, 500);
expect(actor.scale.x).toBe(4);
actor.update(engine, 1);
expect(actor.scale.x).toBe(5);
expect(actor.scale.y).toBe(5);
});

it('can be stopped', () => {
expect(actor.scale.x).toBe(1);
expect(actor.scale.y).toBe(1);

actor.actions.scaleBy(4, 4, 1000);
actor.actions.scaleBy(4, 4, 3);

actor.update(engine, 500);

Expand Down
5 changes: 4 additions & 1 deletion src/spec/ActorSpec.ts
Expand Up @@ -532,8 +532,9 @@ describe('A game actor', () => {

actor.add(childActor);

actor.actions.moveBy(10, 15, 1000);
actor.actions.moveTo(10, 15, 1000);
actor.update(engine, 1000);
actor.update(engine, 1);

expect(childActor.getWorldPos().x).toBe(60);
expect(childActor.getWorldPos().y).toBe(65);
Expand All @@ -551,6 +552,7 @@ describe('A game actor', () => {

actor.actions.moveBy(10, 15, 1000);
actor.update(engine, 1000);
actor.update(engine, 1);

expect(grandChildActor.getWorldPos().x).toBe(70);
expect(grandChildActor.getWorldPos().y).toBe(75);
Expand All @@ -562,6 +564,7 @@ describe('A game actor', () => {

actor.actions.moveBy(10, 15, 1000);
actor.update(engine, 1000);
actor.update(engine, 1);

expect(actor.getWorldPos().x).toBe(10);
expect(actor.getWorldPos().y).toBe(15);
Expand Down

0 comments on commit 2c5e6b3

Please sign in to comment.