Skip to content

Commit

Permalink
feat: Actions with option bags and duration (#3289)
Browse files Browse the repository at this point in the history
- Added new option bag style input to actions with durations in milliseconds instead of speed
  ```typescript
  player.actions.rotateTo({angleRadians: angle, durationMs: 1000, rotationType});
  player.actions.moveTo({pos: ex.vec(100, 100), durationMs: 1000});
  player.actions.scaleTo({scale: ex.vec(2, 2), durationMs: 1000});
  player.actions.repeatForever(ctx => {
    ctx.curveTo({
      controlPoints: [cp1, cp2, dest],
      durationMs: 5000,
      mode: 'uniform'
    });
    ctx.curveTo({
      controlPoints: [cp2, cp1, start1],
      durationMs: 5000,
      mode: 'uniform'
    });
  });
  ```
  • Loading branch information
eonarheim authored Nov 27, 2024
1 parent 823acd9 commit cc7c805
Show file tree
Hide file tree
Showing 25 changed files with 1,327 additions and 211 deletions.
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Deprecated


- `easeTo(...)` and `easeBy(...)` actions marked deprecated, use `moveTo({easing: ...})` instead
- `Vector.size` is deprecated, use `Vector.magnitude` instead
- `ScreenShader` v_texcoord is deprecated, use v_uv. This is changed to match the materials shader API
- `actor.getGlobalPos()` - use `actor.globalPos` instead
Expand All @@ -76,6 +76,25 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Added

- Added `easing` option to `moveTo(...)`
- Added new option bag style input to actions with durations in milliseconds instead of speed
```typescript
player.actions.rotateTo({angleRadians: angle, durationMs: 1000, rotationType});
player.actions.moveTo({pos: ex.vec(100, 100), durationMs: 1000});
player.actions.scaleTo({scale: ex.vec(2, 2), durationMs: 1000});
player.actions.repeatForever(ctx => {
ctx.curveTo({
controlPoints: [cp1, cp2, dest],
durationMs: 5000,
mode: 'uniform'
});
ctx.curveTo({
controlPoints: [cp2, cp1, start1],
durationMs: 5000,
mode: 'uniform'
});
});
```
- Added `ex.lerpAngle(startAngleRadians: number, endAngleRadians: number, rotationType: RotationType, time: number): number` in order to lerp angles between each other
- Added `pointerenter` and `pointerleave` events to `ex.TileMap` tiles!
- Added `pointerenter` and `pointerleave` events to `ex.IsometricMap` tiles!
Expand Down
221 changes: 178 additions & 43 deletions sandbox/tests/bezier/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@ var game = new ex.Engine({
width: 600,
height: 400,
displayMode: ex.DisplayMode.FillScreen
// physics: {
// spatialPartition: ex.SpatialPartitionStrategy.DynamicTree
// }
});
game.toggleDebug();

var hashGrid = (game.currentScene.world.systemManager.get(ex.PointerSystem) as any)
._graphicsHashGrid as ex.SparseHashGrid<ex.GraphicsComponent>;

var curve = new ex.BezierCurve({
controlPoints: [ex.vec(0, 700), ex.vec(100, -300), ex.vec(150, 800), ex.vec(500, 100)],
quality: 10
Expand All @@ -14,6 +20,7 @@ var reverseCurve = curve.clone();
reverseCurve.controlPoints = [...reverseCurve.controlPoints].reverse() as any;

var actor = new ex.Actor({
name: 'Red Box',
pos: ex.vec(500, 500),
width: 100,
height: 100,
Expand All @@ -22,57 +29,185 @@ var actor = new ex.Actor({
});
game.add(actor);

let selection: ex.Actor | null = null;

const start1 = ex.vec(500, 500);
const dest = ex.vec(500, 100);
const cp1 = ex.vec(100, 300);
const cp2 = ex.vec(150, 800);

const curve2 = new ex.BezierCurve({
controlPoints: [start1, cp1, cp2, dest],
quality: 10
});

var points: ex.Vector[] = [];
const drawCurve = () => {
points.length = 0;
for (let i = 0; i < 100; i++) {
points.push(curve2.getPoint(i / 100));
}
};
drawCurve();

const startActor = new ex.Actor({
name: 'start',
pos: start1,
radius: 20,
color: ex.Color.Black
});
startActor.on('postupdate', () => {
if (selection === startActor) {
start1.x = startActor.pos.x;
start1.y = startActor.pos.y;
}
});
startActor.on('pointerdown', (evt) => {
selection = startActor;
});
game.add(startActor);

const cp1Actor = new ex.Actor({
name: 'cp1',
pos: cp1,
radius: 20,
color: ex.Color.Red
});
cp1Actor.on('postupdate', () => {
if (selection === cp1Actor) {
cp1.x = cp1Actor.pos.x;
cp1.y = cp1Actor.pos.y;
}
});
cp1Actor.on('pointerdown', (evt) => {
selection = cp1Actor;
});
game.add(cp1Actor);

const cp2Actor = new ex.Actor({
name: 'cp2',
pos: cp2,
radius: 20,
color: ex.Color.Red
});
cp2Actor.on('postupdate', () => {
if (selection === cp2Actor) {
cp2.x = cp2Actor.pos.x;
cp2.y = cp2Actor.pos.y;
}
});
cp2Actor.on('pointerdown', (evt) => {
selection = cp2Actor;
});
game.add(cp2Actor);

const destActor = new ex.Actor({
name: 'dest',
pos: dest,
radius: 20,
color: ex.Color.Black
});
destActor.on('postupdate', (evt) => {
if (selection === destActor) {
dest.x = destActor.pos.x;
dest.y = destActor.pos.y;
}
});
destActor.on('pointerdown', () => {
selection = destActor;
});
game.add(destActor);

game.input.pointers.primary.on('up', () => {
selection = null;
drawCurve();
});
game.input.pointers.primary.on('move', (evt) => {
if (selection) {
selection.pos = evt.worldPos;
drawCurve();
}
});

actor.onPostUpdate = () => {
// ex.Debug.drawPoint(start1, {color: ex.Color.Black, size: 10});

ex.Debug.drawLine(start1, cp1, { color: ex.Color.Black });
// ex.Debug.drawPoint(cp1, { color: ex.Color.Red, size: 10 });

ex.Debug.drawLine(dest, cp2, { color: ex.Color.Black });
// ex.Debug.drawPoint(cp2, { color: ex.Color.Red, size: 10 });

// ex.Debug.drawPoint(dest, {color: ex.Color.Black, size: 10});
// ex.Debug.draw(ctx => {
// (game.currentScene.physics.collisionProcessor as ex.SparseHashGridCollisionProcessor).hashGrid.debug(ctx, 1);
// });
};

actor.actions.repeatForever((ctx) => {
ctx.curveTo({
controlPoints: [ex.vec(100, -300), ex.vec(150, 800), ex.vec(500, 100)],
durationMs: 6000
});
ctx.curveBy({
controlPoints: [ex.vec(100, 0), ex.vec(-100, 0), ex.vec(0, 300)],
durationMs: 1000
controlPoints: [cp1, cp2, dest],
durationMs: 5000,
mode: 'uniform'
});
ctx.curveTo({
controlPoints: [ex.vec(150, 800), ex.vec(100, -300), ex.vec(0, 700)],
durationMs: 6000
controlPoints: [cp2, cp1, start1],
durationMs: 5000,
mode: 'uniform'
});
});

var time = 0;
var points: ex.Vector[] = [];
// actor.actions.repeatForever((ctx) => {
// ctx.curveTo({
// controlPoints: [ex.vec(100, -300), ex.vec(150, 800), ex.vec(500, 100)],
// durationMs: 6000
// });
// ctx.curveBy({
// controlPoints: [ex.vec(100, 0), ex.vec(-100, 0), ex.vec(0, 300)],
// durationMs: 1000
// });
// ctx.curveTo({
// controlPoints: [ex.vec(150, 800), ex.vec(100, -300), ex.vec(0, 700)],
// durationMs: 6000
// });
// });

// var time = 0;

game.onPostDraw = (ctx: ex.ExcaliburGraphicsContext, elapsedMs) => {
if (time < 5000) {
var t = ex.clamp(ex.remap(0, 5000, 0, 1, time), 0, 1);

var p = curve.getPoint(t);
ctx.drawCircle(p, 20, ex.Color.Red);
var p2 = curve.getUniformPoint(t);
ctx.drawCircle(p2, 20, ex.Color.Purple);
points.push(p2);

var tangent = curve.getTangent(t);
var normal = curve.getNormal(t);
ex.Debug.drawRay(new ex.Ray(p, tangent), {
distance: 100,
color: ex.Color.Yellow
});
ex.Debug.drawRay(new ex.Ray(p, normal), {
distance: 100,
color: ex.Color.Green
});

var uTangent = curve.getUniformTangent(t);
var uNormal = curve.getUniformNormal(t);
ex.Debug.drawRay(new ex.Ray(p2, uTangent), {
distance: 100,
color: ex.Color.Yellow
});
ex.Debug.drawRay(new ex.Ray(p2, uNormal), {
distance: 100,
color: ex.Color.Green
});

time += elapsedMs;
}
// if (time < 5000) {
// var t = ex.clamp(ex.remap(0, 5000, 0, 1, time), 0, 1);

// var p = curve.getPoint(t);
// ctx.drawCircle(p, 20, ex.Color.Red);
// var p2 = curve.getUniformPoint(t);
// ctx.drawCircle(p2, 20, ex.Color.Purple);
// points.push(p2);

// var tangent = curve.getTangent(t);
// var normal = curve.getNormal(t);
// ex.Debug.drawRay(new ex.Ray(p, tangent), {
// distance: 100,
// color: ex.Color.Yellow
// });
// ex.Debug.drawRay(new ex.Ray(p, normal), {
// distance: 100,
// color: ex.Color.Green
// });

// var uTangent = curve.getUniformTangent(t);
// var uNormal = curve.getUniformNormal(t);
// ex.Debug.drawRay(new ex.Ray(p2, uTangent), {
// distance: 100,
// color: ex.Color.Yellow
// });
// ex.Debug.drawRay(new ex.Ray(p2, uNormal), {
// distance: 100,
// color: ex.Color.Green
// });

// time += elapsedMs;
// }

for (let i = 0; i < points.length - 1; i++) {
ctx.drawLine(points[i], points[i + 1], ex.Color.Purple, 2);
Expand Down
2 changes: 1 addition & 1 deletion sandbox/tests/rotation/rotation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ engine.input.pointers.primary.on('down', (e: ex.PointerEvent) => {
var vector = new ex.Vector(e.worldPos.x - player.pos.x, e.worldPos.y - player.pos.y);
var angle = vector.toAngle();

player.actions.rotateTo(angle, 1, rotationType);
player.actions.rotateTo({ angleRadians: angle, durationMs: 1000, rotationType });
//console.log('rotating from ' + player.rotation + ' to ' + angle);
}
});
Expand Down
4 changes: 2 additions & 2 deletions src/engine/Actions/Action/CurveBy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface CurveByOptions {
/**
* Quality when sampling uniform points on the curve. Samples = 4 * quality;
*
* For bigger 'uniform' curves you may want to increase quality
* For bigger 'uniform' curves you may want to increase quality to make the motion appear smooth
*
* Default 4
*/
Expand Down Expand Up @@ -64,13 +64,13 @@ export class CurveBy implements Action {
this._curve.setControlPoint(3, this._curve.controlPoints[3].add(this._tx.globalPos));
this._started = true;
}
this._currentMs -= elapsedMs;
const t = clamp(remap(0, this._durationMs, 0, 1, this._durationMs - this._currentMs), 0, 1);
if (this._mode === 'dynamic') {
this._tx.pos = this._curve.getPoint(t);
} else {
this._tx.pos = this._curve.getUniformPoint(t);
}
this._currentMs -= elapsedMs;
if (this.isComplete(this._entity)) {
if (this._mode === 'dynamic') {
this._tx.pos = this._curve.getPoint(1);
Expand Down
2 changes: 1 addition & 1 deletion src/engine/Actions/Action/CurveTo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ export class CurveTo implements Action {
this._curve.setControlPoint(0, this._tx.globalPos.clone());
this._started = true;
}
this._currentMs -= elapsedMs;
const t = clamp(remap(0, this._durationMs, 0, 1, this._durationMs - this._currentMs), 0, 1);
if (this._mode === 'dynamic') {
this._tx.pos = this._curve.getPoint(t);
} else {
this._tx.pos = this._curve.getUniformPoint(t);
}
this._currentMs -= elapsedMs;
if (this.isComplete(this._entity)) {
if (this._mode === 'dynamic') {
this._tx.pos = this._curve.getPoint(1);
Expand Down
4 changes: 2 additions & 2 deletions src/engine/Actions/Action/Delay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export class Delay implements Action {
private _delay: number;
private _started: boolean = false;
private _stopped = false;
constructor(delay: number) {
this._delay = delay;
constructor(durationMs: number) {
this._delay = durationMs;
}

public update(elapsedMs: number): void {
Expand Down
3 changes: 3 additions & 0 deletions src/engine/Actions/Action/EaseBy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { Entity } from '../../EntityComponentSystem/Entity';
import { vec, Vector } from '../../Math/vector';
import { Action, nextActionId } from '../Action';

/**
* @deprecated use moveBy({offset: Vector, durationMs: number, easing: EasingFunction})
*/
export class EaseBy implements Action {
id = nextActionId();
private _tx: TransformComponent;
Expand Down
3 changes: 3 additions & 0 deletions src/engine/Actions/Action/EaseTo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { MotionComponent } from '../../EntityComponentSystem/Components/MotionCo
import { vec, Vector } from '../../Math/vector';
import { Action, nextActionId } from '../Action';

/**
* @deprecated use moveTo({pos: Vector, durationMs: number, easing: EasingFunction})
*/
export class EaseTo implements Action {
id = nextActionId();
private _tx: TransformComponent;
Expand Down
4 changes: 2 additions & 2 deletions src/engine/Actions/Action/Fade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ export class Fade implements Action {
private _started = false;
private _stopped = false;

constructor(entity: Entity, endOpacity: number, time: number) {
constructor(entity: Entity, endOpacity: number, durationMs: number) {
this._graphics = entity.get(GraphicsComponent);
this._endOpacity = endOpacity;
this._remainingTime = this._originalTime = time;
this._remainingTime = this._originalTime = durationMs;
}

public update(elapsedMs: number): void {
Expand Down
Loading

0 comments on commit cc7c805

Please sign in to comment.