canvas2djs
Version:
HTML5 canvas based game engine
297 lines (253 loc) • 8.24 kB
text/typescript
import { Texture } from '../Texture';
import { Delay } from './Delay';
import { Callback } from './Callback';
import { Animation } from './Animation';
import { Tween, EasingFunc } from '../Tween';
import { IActionListener, ActionListener } from './ActionListener';
import { TransByProps, TransToProps, Transition } from './Transition';
import { addArrayItem, removeArrayItem } from '../Util';
import { BaseAction } from './BaseAction';
export enum ActionType {
TO,
BY,
ANIM,
WAIT,
CALLBACK,
}
export type TransToAction = {
type: ActionType.TO;
options: TransToProps;
duration: number;
};
export type TransByAction = {
type: ActionType.BY;
options: TransByProps;
duration: number;
};
export type AnimationAction = {
type: ActionType.ANIM;
frameList: Array<Texture | string>;
frameRate: number;
repetitions?: number;
}
export type WaitAction = {
type: ActionType.WAIT;
duration: number;
}
export type CallbackAction = {
type: ActionType.CALLBACK;
callback: Function;
}
export type ActionQueue = Array<TransToAction | TransByAction | AnimationAction | WaitAction | CallbackAction>;
export enum ActionRepeatMode {
NONE,
REPEAT,
REVERSE_REPEAT,
}
export class Action {
private static _actionList: Array<Action> = [];
private static _listenerList: Array<IActionListener> = [];
private static _scheduleCostTime: number = 0;
public static get scheduleCostTime() {
return this._scheduleCostTime;
}
/**
* Stop action by target
*/
public static stop(target: any, tag?: string) {
let list = Action._actionList.slice();
for (let i = 0, action: Action; action = list[i]; i++) {
if (action.target === target) {
if (tag == null || action.tag == tag) {
action.stop();
}
}
}
}
/**
* Listen a action list, when all actions are done then publish to listener
*/
public static listen(actions: Array<Action>): IActionListener {
var listener = new ActionListener(actions);
Action._listenerList.push(listener);
return listener;
}
public static removeListener(listener: IActionListener) {
removeArrayItem(this._listenerList, this);
}
public static schedule(deltaTime: number): void {
var startTime = Date.now();
var actionList = Action._actionList.slice();
var listenerList = Action._listenerList.slice() as ActionListener[];
for (let i = 0, action: Action; action = actionList[i]; i++) {
action._step(deltaTime);
if (action._done) {
removeArrayItem(Action._actionList, action);
}
}
for (let i = 0, listener: ActionListener; listener = listenerList[i]; i++) {
listener._step();
}
Action._scheduleCostTime = Date.now() - startTime;
}
protected _queue: Array<BaseAction> = [];
protected _currentIndex: number = 0;
protected _done: boolean = false;
protected _repeatMode: ActionRepeatMode = ActionRepeatMode.NONE;
/**
* Action running state
*/
public isRunning: boolean = false;
public target: any;
public tag: string;
constructor(target: any, tag?: string) {
this.target = target;
this.tag = tag;
}
isDone() {
return this._done;
}
setRepeatMode(mode: ActionRepeatMode) {
this._repeatMode = mode;
return this;
}
set repeatMode(mode: ActionRepeatMode) {
this._repeatMode = mode;
}
get repeatMode() {
return this._repeatMode;
}
queue(actions: ActionQueue) {
for (let i = 0, action: ActionQueue[0]; action = actions[i]; i++) {
switch (action.type) {
case ActionType.ANIM:
this.animate(action.frameList, action.frameRate, action.repetitions);
break;
case ActionType.BY:
this.by(action.options, action.duration);
break;
case ActionType.TO:
this.to(action.options, action.duration);
break;
case ActionType.WAIT:
this.wait(action.duration);
break;
case ActionType.CALLBACK:
this.then(action.callback);
break;
}
}
return this;
}
/**
* Add a callback, it will exec after previous action is done.
*/
then(callback: Function): Action {
this._queue.push(new Callback(callback));
return this;
}
/**
* Add a delay action.
*/
wait(time: number): Action {
this._queue.push(new Delay(time));
return this;
}
/**
* Add a animation action
*/
animate(frameList: Array<Texture | string>, frameRate: number, repetitions?: number): Action {
var anim = new Animation(frameList, frameRate, repetitions);
this._queue.push(anim);
anim.step(anim.interval, this.target);
return this;
}
/**
* TransitionTo action
*/
to(attrs: TransToProps, duration: number): Action {
this._queue.push(new Transition(attrs, duration));
return this;
}
/**
* TransitionBy action
*/
by(attrs: TransByProps, duration: number): Action {
this._queue.push(new Transition(attrs, duration, true));
return this;
}
/**
* Start the action
*/
start(): Action {
if (!this.isRunning) {
addArrayItem(Action._actionList, this);
this.isRunning = true;
}
return this;
}
/**
* Stop the action
*/
stop() {
for (let i = 0, action: BaseAction; action = this._queue[i]; i++) {
action.destroy();
}
this._done = true;
this.isRunning = false;
this._queue.length = 0;
removeArrayItem(Action._actionList, this);
}
clear() {
for (let i = 0, action: BaseAction; action = this._queue[i]; i++) {
action.destroy();
}
this._done = false;
this.isRunning = false;
this._queue.length = 0;
this._currentIndex = 0;
this._repeatMode = ActionRepeatMode.NONE;
removeArrayItem(Action._actionList, this);
}
protected _step(deltaTime: number): void {
if (!this._queue.length || this._currentIndex >= this._queue.length) {
return;
}
var action = this._queue[this._currentIndex];
action.step(deltaTime, this.target);
if (action.done) {
this._currentIndex += 1;
if (this._currentIndex >= this._queue.length) {
this._onAllActionDone();
}
else if (action.immediate) {
this._step(deltaTime);
}
}
}
protected _onAllActionDone() {
switch (this._repeatMode) {
case ActionRepeatMode.REPEAT:
for (let i = 0, action: BaseAction; action = this._queue[i]; i++) {
action.reset();
}
this._currentIndex = 0;
break;
case ActionRepeatMode.REVERSE_REPEAT:
for (let i = 0, action: BaseAction; action = this._queue[i]; i++) {
action.reverse();
}
this._currentIndex = 0;
break;
default:
this._done = true;
this.isRunning = false;
this.target = null;
for (let i = 0, action: BaseAction; action = this._queue[i]; i++) {
action.destroy();
}
this._queue.length = 0;
break;
}
}
}