Skip to content

Commit

Permalink
v1.1.2 add runOnChildWithName(), add timingMode to chained actions
Browse files Browse the repository at this point in the history
  • Loading branch information
reececomo committed Apr 24, 2024
1 parent 6dc772c commit 83f414a
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 39 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,6 @@ All actions have a `timingMode` which controls the speed curve of its execution.

The default timingMode for all actions is `TimingMode.linear`, which causes an animation to occur evenly over its duration.

> Note: The `timingMode` property of `.group()`, `.sequence()`, `.repeat()`, and `.repeatForever()` has no effect on the underlying action(s).
You can customize the speed curve of actions like so:

```ts
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pixijs-actions",
"version": "1.1.1",
"version": "1.1.2",
"author": "Reece Como <[email protected]>",
"authors": [
"Reece Como <[email protected]>",
Expand Down
125 changes: 92 additions & 33 deletions src/Action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,25 @@ export abstract class Action {
}

//
// ----------------- Transparency Actions: -----------------
// ----------------- Children Actions: -----------------
//

/**
* Creates an action that runs an action on a named child object.
*
* This action has an instantaneous duration, although the action executed on the child may have
* a duration of its own. When the action executes, it looks up an appropriate child node and
* calls its `run(action)` method, passing in the action to execute.
*
* This action is reversible; it tells the child to execute the reverse of the action specified by
* the action parameter.
*/
public static runOnChildWithName(action: Action, name: string): Action {
return new RunOnChildWithNameAction(action, name);
}

//
// ----------------- Custom Actions: -----------------
//

/**
Expand Down Expand Up @@ -711,11 +729,10 @@ class GroupAction extends Action {
progress: number,
progressDelta: number,
ticker: ActionTicker,
timeDelta: number,
): void {
const relativeTimeDelta = timeDelta * this.speed;

const relativeTimeDelta = progressDelta * ticker.scaledDuration;
let allDone = true;

for (const childTicker of ticker.data.childTickers as ActionTicker[]) {
if (!childTicker.isDone) {
allDone = false;
Expand Down Expand Up @@ -761,15 +778,14 @@ class SequenceAction extends Action {
progress: number,
progressDelta: number,
ticker: ActionTicker,
timeDelta: number,
): void {
let allDone = true;
let remainingTimeDelta = timeDelta * this.speed;
let remainingTimeDelta = progressDelta * ticker.scaledDuration;

for (const childTicker of ticker.data.childTickers as ActionTicker[]) {
if (!childTicker.isDone) {

if (remainingTimeDelta > 0 || childTicker.duration === 0) {
if (remainingTimeDelta > 0 || childTicker.scaledDuration === 0) {
remainingTimeDelta = childTicker.stepActionForward(remainingTimeDelta);
}
else {
Expand All @@ -790,8 +806,9 @@ class SequenceAction extends Action {
}

public reversed(): Action {
const reversedSequence = [...this.actions].reverse().map(action => action.reversed());
return new SequenceAction(reversedSequence);
return new SequenceAction(this.actions.map(action => action.reversed()).reverse())
.setTimingMode(this.timingMode)
.setSpeed(this.speed);
}

protected _setupTicker(target: TargetNode, ticker: ActionTicker): any {
Expand Down Expand Up @@ -832,22 +849,25 @@ class RepeatAction extends Action {

remainingTimeDelta = childTicker.stepActionForward(remainingTimeDelta);

if (remainingTimeDelta > 0) {
if (remainingTimeDelta > 0 || childTicker.scaledDuration === 0) {
if (++ticker.data.n >= this.repeats) {
ticker.isDone = true;
return;
}

childTicker.reset();
childTicker.stepActionForward(remainingTimeDelta);
remainingTimeDelta = childTicker.stepActionForward(remainingTimeDelta);
}
}

protected _setupTicker(target: TargetNode, ticker: ActionTicker): any {
ticker.autoComplete = false;

const childTicker = new ActionTicker(undefined, target, this.action);
childTicker.timingMode = (x) => ticker.timingMode(childTicker.timingMode(x));

return {
childTicker: new ActionTicker(undefined, target, this.action),
childTicker,
n: 0,
};
}
Expand All @@ -873,21 +893,30 @@ class RepeatForeverAction extends Action {
return new RepeatForeverAction(this.action.reversed());
}

public updateAction(target: TargetNode, progress: number, progressDelta: number, ticker: ActionTicker, timeDelta: number): void {
let childTicker: ActionTicker = ticker.data.childTicker;
let remainingTimeDelta = timeDelta * this.speed;
public updateAction(
target: TargetNode,
progress: number,
progressDelta: number,
ticker: ActionTicker,
timeDelta: number
): void {
const childTicker: ActionTicker = ticker.data.childTicker;
let remainingTimeDelta = timeDelta * ticker.speed;

remainingTimeDelta = childTicker.stepActionForward(remainingTimeDelta);

if (remainingTimeDelta > 0) {
childTicker.reset();
childTicker.stepActionForward(remainingTimeDelta);
remainingTimeDelta = childTicker.stepActionForward(remainingTimeDelta);
}
}

protected _setupTicker(target: TargetNode, ticker: ActionTicker): any {
const childTicker = new ActionTicker(undefined, target, this.action);
childTicker.timingMode = (x) => ticker.timingMode(childTicker.timingMode(x));

return {
childTicker: new ActionTicker(undefined, target, this.action)
childTicker
};
}

Expand Down Expand Up @@ -1032,12 +1061,33 @@ class CustomAction extends Action {
}
}

class RunBlockAction extends Action {
protected block: () => any;
class RunOnChildWithNameAction extends Action {
public constructor(
protected action: Action,
protected name: string,
) {
super(0);
}

public constructor(block: () => void) {
public updateAction(target: TargetNode, progress: number, progressDelta: number): void {
if (!('children' in target) || !Array.isArray(target.children)) {
return;
}

const child: TargetNode | undefined = target.children.find(c => c.name === this.name);
child?.run(this.action);
}

public reversed(): Action {
return new RunOnChildWithNameAction(this.action.reversed(), this.name);
}
}

class RunBlockAction extends Action {
public constructor(
protected block: () => void
) {
super(0);
this.block = block;
}

public updateAction(target: TargetNode, progress: number, progressDelta: number): void {
Expand Down Expand Up @@ -1541,31 +1591,37 @@ class ActionTicker {
/**
* Relative speed of the action ticker.
*
* Defaults to the action's speed and is capture at creation time, and updated on
* the setup tick.
* Copies the action's speed when the action is run.
*/
public speed: number;

/**
* Relative speed of the action ticker.
*
* Copies the action's timingMode when the action is run.
*/
public timingMode: TimingModeFn;

/**
* Expected duration of the action ticker.
*
* Defaults to the action's scaled duration and is capture at creation time, and updated on
* the setup tick.
* Copies the action's scaledDuration when the action is run.
*/
public duration: number;
public scaledDuration: number;

public constructor(
public key: string | undefined,
public target: TargetNode,
public action: Action,
) {
this.speed = action.speed;
this.duration = action.scaledDuration;
this.scaledDuration = action.scaledDuration;
this.timingMode = action.timingMode;
}

/** The relative time elapsed between 0 and 1. */
public get timeDistance(): number {
return this.duration === 0 ? 1 : Math.min(1, this.elapsed / this.action.scaledDuration);
return this.scaledDuration === 0 ? 1 : Math.min(1, this.elapsed / this.scaledDuration);
}

/**
Expand All @@ -1574,15 +1630,18 @@ class ActionTicker {
* Can be a value beyond 0 or 1 depending on the timing mode function.
*/
protected get easedTimeDistance(): number {
return this.action.timingMode(this.timeDistance);
return this.timingMode(this.timeDistance);
}

/** @returns Any unused time delta. Negative value means action is still in progress. */
public stepActionForward(timeDelta: number): number {
if (!this.isSetup) {
// cache action attributes
// Copy action attributes:
this.speed = this.action.speed;
this.duration = this.action.duration;
this.scaledDuration = this.action.duration;
this.timingMode = this.action.timingMode;

// Perform first time setup:
this.data = (this.action as any)._setupTicker(this.target, this);
this.isSetup = true;
}
Expand All @@ -1604,7 +1663,7 @@ class ActionTicker {

const scaledTimeDelta = timeDelta * this.speed /* target speed is applied at the root */;

if (this.duration === 0) {
if (this.scaledDuration === 0) {
// Instantaneous action.
action.updateAction(this.target, 1.0, 1.0, this, scaledTimeDelta);
this.isDone = true;
Expand Down Expand Up @@ -1632,7 +1691,7 @@ class ActionTicker {
// Remove completed action.
ActionTicker.removeAction(this);

return this.elapsed > this.duration ? this.elapsed - this.duration : 0;
return this.elapsed > this.scaledDuration ? this.elapsed - this.scaledDuration : 0;
}

return -1; // relinquish no time
Expand Down
32 changes: 31 additions & 1 deletion src/test/Action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,10 @@ describe('Action Chaining', () => {
simulateTime(1.0);
expect(node.position.x).toBeCloseTo(30);

// Sanity check: We're still going.
simulateTime(1.0);
expect(node.position.x).toBeCloseTo(30);

// Sanity check: We've stopped.
expect(node.hasActions()).toBe(false);
});
});
Expand Down Expand Up @@ -299,6 +302,33 @@ describe('Action', () => {
expect(node.scale.y).toBeCloseTo(1.5);
});
});

describe('runOnChildWithName()', () => {
it('passes the action to the named child', () => {
const childNode = new Container();
childNode.name = "myChildNode";

const parentNode = new Container();
parentNode.addChild(childNode);

parentNode.run(
Action.runOnChildWithName(
Action.rotateBy(Math.PI, 1.0),
"myChildNode",
)
);

simulateTime(0.5);

expect(parentNode.hasActions()).toBe(false);
expect(childNode.hasActions()).toBe(true);

simulateTime(1.0);

expect(childNode.rotation).toBeCloseTo(Math.PI);
expect(childNode.hasActions()).toBe(false);
});
});
});

describe('Action and nodes', () => {
Expand Down

0 comments on commit 83f414a

Please sign in to comment.