import TWEEN from '../../libs/tween.js';
import Application from '../core/Application.js';
import Container from '../core/display/Container';
import * as ActionInterval from './ActionInterval';
import { isUndefined, isObject } from '../../utils';
const FPSPool = {
defaultFPS: 0,
actions: [],
};
/**
* 动作类
*
* `Action`是对`Tween.js`做的一层封装,你可以很方便的制作动画;你也可以通过`new Tiny.TWEEN.Tween(..)`来直接使用`Tween.js`原生方法
*
* @example
* var action = new Tiny.Action(600, Tiny.point(100, 120));
* action.yoyo = true;
* //重复4次
* action.repeatTimes = 4;
* //延迟500ms开始
* action.setDelay(500);
* //设置动画缓冲为`Quadratic.InOut`
* action.setEasing(Tiny.TWEEN.Easing.Quadratic.InOut);
* //运动中更改精灵的坐标
* action.onUpdate = function (tween, object) {
* sprite.setPosition(tween.x, tween.y);
* };
* //运动完成后的回调
* action.onComplete = function (tween, object) {
* console.log('complete');
* };
*
* @class
* @memberof Tiny
*/
export default class Action {
/**
*
* @param {number} duration - 动作持续时间(ms)
* @param {object} to - 运动到的状态
*/
constructor(duration, to) {
/**
* action 的唯一 name
*
* @member {string}
* @default ''
* @private
*/
this.name = '';
/**
* 当前 action 对应的 tween 对象
*
* @type {Tiny.TWEEN.Tween}
* @version 1.0.2
*/
this.tween = null;
/**
* 动画持续时间(ms)
* > 注意:无论帧频是多少,都会在这个时间内完成动画
*
* @member {number}
*/
this.duration = duration;
/**
* 动画需要达到的目标形态的属性
*
* @member {object}
*/
this.to = to;
/**
* 延迟时间
*
* @member {number}
* @default 0
* @private
*/
this.delay = 0;
/**
* 悠悠球效果
*
* @member {boolean}
* @default false
* @private
*/
this.yoyo = false;
/**
* 重复次数
*
* @member {number}
* @default 1
*/
this.repeatTimes = 0;
/**
* 重复的延迟时间
*
* @type {number}
* @default 0
*/
this.repeatDelayTime = 0;
/**
* 动画缓冲
*
* @member {Tiny.TWEEN.Easing}
* @default Tiny.TWEEN.Easing.Linear.None
* @private
*/
this.easing = TWEEN.Easing.Linear.None;
/**
* 插值
*
* @member {Tiny.TWEEN.Interpolation}
* @default Tiny.TWEEN.Interpolation.Linear
* @private
*/
this.interpolation = TWEEN.Interpolation.Linear;
this._fps = 0;
}
create() {
const self = this;
return function(object) {
const tween = new TWEEN.Tween(object.getNature())
.to(self.to, self.duration)
.repeat(self.repeatTimes)
.repeatDelay(self.repeatDelayTime)
.delay(self.delay)
.easing(self.easing)
.yoyo(self.yoyo)
.interpolation(self.interpolation)
.onStart(function() {
self._onTweenStart();
self.onStart(this, object);
})
.onUpdate(function() {
self.onUpdate(this, object);
})
.onComplete(function() {
self._onTweenComplete();
self.onComplete(this, object);
})
.onStop(function() {
self.onStop(this, object);
});
tween.name = self.name;
self.tween = tween;
return tween;
};
}
/**
* 设置 action 的 name 值
*
* @example
* var action = new Tiny.Action(200, {x: 100});
* action.setName = 'MOVE_POISITION';
*
* @param {string} name - 要设置的 name 值
*/
setName(name) {
this.name = name;
}
/**
* 动画开始时的回调
*
* @example
* var action = new Tiny.Action(100, {scaleX: 2});
* action.onStart = function(tween, object) {
* console.log('action started');
* };
*
* @param {Tiny.TWEEN.Tween} tween
* @param {object} object
*/
onStart(tween, object) {
this._onStart(tween, object);
}
/**
* 动画更新时的回调
* > 注意:如果使用 Tiny 提供的静态 actions(eg: MoveBy, ScaleTo..) 同时又要使用`onUpdate`,需要调用`_onUpdate`来还原原 action,也可以自己重写 action。
*
* @example
* var moveByAction = Tiny.MoveBy(1000, Tiny.point(100, -200));
* moveByAction.onUpdate = function (tween, object) {
* console.log('update');
* // 还原 MoveBy 行为
* this._onUpdate.call(this, tween, object);
* };
*
* @param {Tiny.TWEEN.Tween} tween
* @param {object} object
*/
onUpdate(tween, object) {
this._onUpdate(tween, object);
}
/**
* 动画完成的回调
*
* @example
* var moveByAction = Tiny.MoveBy(1000, Tiny.point(2));
* moveByAction.onComplete = function (tween, object) {
* console.log('completed!');
* // 还原 MoveBy 行为,一般用在 Repeat/RepeatForever 的情况
* this._onComplete.call(this, tween, object);
* };
* sprite.runAction(Tiny.RepeatForever(moveByAction));
*
* @param {Tiny.TWEEN.Tween} tween
* @param {object} object
*/
onComplete(tween, object) {
this._onComplete(tween, object);
}
/**
* 动画停止的回调
*
* @param {Tiny.TWEEN.Tween} tween
* @param {object} object
*/
onStop(tween, object) {
this._onStop(tween, object);
}
_onStart(tween, object) {
//OVERRIDE
}
_onUpdate(tween, object) {
//OVERRIDE
}
_onComplete(tween, object) {
//OVERRIDE
}
_onStop(tween, object) {
//OVERRIDE
}
_onTweenStart() {
if (!FPSPool.defaultFPS) {
FPSPool.defaultFPS = Application.FPS;
}
if (!this._fps) {
return;
}
FPSPool.actions.push(this);
if (FPSPool.actions.length) {
Application.FPS = Math.max.apply(null, FPSPool.actions.map(action => action._fps || 60));
}
}
_onTweenComplete() {
if (!this._fps || !FPSPool.actions.length) {
return;
}
FPSPool.actions = FPSPool.actions.filter(action => action.isPlaying());
const index = FPSPool.actions.indexOf(this);
if (~index) {
FPSPool.actions.splice(index, 1);
}
if (FPSPool.actions.length) {
Application.FPS = Math.max.apply(null, FPSPool.actions.map(action => action._FPS || 60));
} else {
Application.FPS = FPSPool.defaultFPS;
}
}
/**
* 设置此 Action 执行时的帧率,结束后自动恢复到默认帧率
* 用于动态调帧
*
* @version 1.3.0
* @param {number} fps - 帧率(取值:10/20/30/40/50/60)
* @default 60
*/
setFPS(fps = 60) {
this._fps = fps;
}
/**
* 设置动画缓冲
*
* 内置的缓冲效果如下表:
*
* | Linear.None | |
* |:--:|:--:|:--:
* | ![](https://gw.alipayobjects.com/zos/rmsportal/xHLCeeTUbcKOoiEdSoVb.png)||
* | Quadratic.In | Quadratic.Out| Quadratic.InOut | Cubic.In | Cubic.Out| Cubic.InOut
* |![](https://gw.alipayobjects.com/zos/rmsportal/bmXLkbJLtzyWwJSEkdfm.png)|![](https://gw.alipayobjects.com/zos/rmsportal/RFZGonunOaWrnywVqUBO.png)|![](https://gw.alipayobjects.com/zos/rmsportal/bfLsmoZkAikEzNSXSHWh.png)|![](https://gw.alipayobjects.com/zos/rmsportal/HEXPOCOiojeKUDMKisJp.png)|![](https://gw.alipayobjects.com/zos/rmsportal/vZCUliuGFTDVSCduDqRH.png)|![](https://gw.alipayobjects.com/zos/rmsportal/rtpBvxNkdFWtamVhZdkQ.png)
* | Quartic.In | Quartic.Out| Quartic.InOut | Quintic.In | Quintic.Out| Quintic.InOut
* |![](https://gw.alipayobjects.com/zos/rmsportal/MKxrNVWXZsawHZKSSGtf.png)|![](https://gw.alipayobjects.com/zos/rmsportal/wXcCqgEVNZMVPSNldeWZ.png)|![](https://gw.alipayobjects.com/zos/rmsportal/IgrvhMyPYmGTSdjMncZy.png)|![](https://gw.alipayobjects.com/zos/rmsportal/IUoELDGYFkDKcRGcjyeb.png)|![](https://gw.alipayobjects.com/zos/rmsportal/vqtkEuaUbbfqjaOXEDFn.png)|![](https://gw.alipayobjects.com/zos/rmsportal/bxSjlyiWOVJLYxNGzSib.png)
* | Sinusoidal.In | Sinusoidal.Out| Sinusoidal.InOut | Exponential.In | Exponential.Out| Exponential.InOut
* |![](https://gw.alipayobjects.com/zos/rmsportal/VVaDZIJphFcTbgyjbKGT.png)|![](https://gw.alipayobjects.com/zos/rmsportal/ExVkPzCRhEbpUwmcqgMf.png)|![](https://gw.alipayobjects.com/zos/rmsportal/mSuTICjQAOTdOfSAbwHz.png)|![](https://gw.alipayobjects.com/zos/rmsportal/hpLsUyjdhSwEWuMhiCQD.png)|![](https://gw.alipayobjects.com/zos/rmsportal/DJgKaxKQvnAgnRNamLXJ.png)|![](https://gw.alipayobjects.com/zos/rmsportal/NYeYSVLHysUCRltlCDKE.png)
* | Circular.In | Circular.Out| Circular.InOut | Elastic.In | Elastic.Out| Elastic.InOut
* |![](https://gw.alipayobjects.com/zos/rmsportal/ABckFwQKahquFfuETPVp.png)|![](https://gw.alipayobjects.com/zos/rmsportal/suKheoPdbnvnwdzTEYwQ.png)|![](https://gw.alipayobjects.com/zos/rmsportal/cSBNmWFzHLEarLgooEcV.png)|![](https://gw.alipayobjects.com/zos/rmsportal/WuqHhXjNArNwLOliqiXm.png)|![](https://gw.alipayobjects.com/zos/rmsportal/SOOyWYVdlDPppaFpKWuX.png)|![](https://gw.alipayobjects.com/zos/rmsportal/GwqgiMOkFzZQhtsVmXFw.png)
* | Back.In | Back.Out| Back.InOut | Bounce.In | Bounce.Out| Bounce.InOut
* |![](https://gw.alipayobjects.com/zos/rmsportal/vwGtMvOurAZBieEkwayF.png)|![](https://gw.alipayobjects.com/zos/rmsportal/mqpdGdmwbwuJBBTqvYXW.png)|![](https://gw.alipayobjects.com/zos/rmsportal/eguKvIEfuNqacRvENERe.png)|![](https://gw.alipayobjects.com/zos/rmsportal/SHdZxnDkfTULbpXFZNBI.png)|![](https://gw.alipayobjects.com/zos/rmsportal/cbfHmuklomRKjULuMSCC.png)|![](https://gw.alipayobjects.com/zos/rmsportal/XyuCcxsHgtIFltQamaur.png)
*
* @example
* var action = new Tiny.Action(150, {x: 100});
* action.onUpdate = function (tween, object) {
* console.log(tween.x);
* };
* action.setEasing(Tiny.TWEEN.Easing.Quartic.InOut);
*
* @param {Tiny.TWEEN.Easing} easing - 缓存类型,值见上表
* @default Tiny.TWEEN.Easing.Linear.None
*/
setEasing(easing) {
this.easing = easing;
}
/**
* 设置插值
*
* 内置的插值效果如下表:
*
* | Linear | Bezier | CatmullRom
* |:--:|:--:|:--:
* |![](https://gw.alipayobjects.com/zos/rmsportal/lVaiWObEJiCgHshzFmKl.png)|![](https://gw.alipayobjects.com/zos/rmsportal/YuvtNeRrSuobgYnCcbEW.png)|![](https://gw.alipayobjects.com/zos/rmsportal/VNUxidULbepffAFVKrim.png)
* | start===end | start===end | start===end
* |![](https://gw.alipayobjects.com/zos/rmsportal/kLPjaZFIUTzKrtDuVuXt.png)|![](https://gw.alipayobjects.com/zos/rmsportal/XrVohPavkaahIbOTAcjK.png)|![](https://gw.alipayobjects.com/zos/rmsportal/DHcLSlmlWDlpQVKkYjLF.png)
*
* @example
* var action = new Tiny.Action(150, {x: 100});
* action.onUpdate = function (tween, object) {
* console.log(tween.x);
* };
* action.setInterpolation(Tiny.TWEEN.Interpolation.Bezier);
*
* @param {Tiny.TWEEN.Interpolation} interpolation - 插值类型,值见上表
* @default Tiny.TWEEN.Interpolation.Linear
*/
setInterpolation(interpolation) {
this.interpolation = interpolation;
}
/**
* 设置延迟
*
* @example
* var action = Tiny.MoveTo(500, Tiny.point(200));
* action.setDelay(3000);
*
* @param {number} delay - 延迟时长(ms)
* @default 0
*/
setDelay(delay) {
this.delay = delay;
}
/**
* 设置重复延迟
*
* @example
* var action = Tiny.MoveBy(1000, Tiny.point(100, 200));
* action.setRepeatDelay(3000);
*
* @param {number} delay - 延迟时长(ms)
* @default 0
*/
setRepeatDelay(delay) {
this.repeatDelayTime = delay;
}
/**
* 是否运动中
*
* var action = Tiny.MoveBy(1000, Tiny.point(100, 200));
* if (action.isPlaying()){
* console.log('action is playing');
* }
*
* @version 1.0.2
* @return {boolean}
*/
isPlaying() {
if (this.tween) {
return this.tween.isPlaying();
} else {
return false;
}
}
/**
* 停止动画
*
* @example
* var action = Tiny.MoveTo(1000, Tiny.point(100, 200));
* action.stop();
*
* @version 1.0.2
*/
stop() {
this.tween && this.tween.stop();
}
/**
* 暂停动画
*
* @example
* var sprite = Tiny.Sprite.fromImage('https://gw.alipayobjects.com/zos/rmsportal/feRRHmdAYifYMyChbUbC.png');
* var action = Tiny.MoveTo(1000, Tiny.point(100, 200));
* sprite.runAction(Tiny.RepeatForever(Tiny.Back(action)));
* // 设置精灵可交互
* sprite.setEventEnabled(true);
* // 绑定事件
* sprite.on('pointerdown', function () {
* if (action.isPlaying()) {
* action.pause();
* } else {
* action.resume();
* }
* });
*
* @version 1.0.2
*/
pause() {
this.tween && this.tween.pause();
}
/**
* 继续暂停的动画
*
* @version 1.1.0
*/
resume() {
this.tween && this.tween.resume();
}
}
/**
* 清除某对象上的所有 Action
*
* @example
* var sprite = Tiny.Sprite.fromImage('https://gw.alipayobjects.com/zos/rmsportal/feRRHmdAYifYMyChbUbC.png');
* var action = new Tiny.Action(10000, {x: 300});
* action.onUpdate = function (tween, object) {
* sprite.setPositionX(tween.x);
* };
* sprite.runAction(action);
* // 设置精灵可交互
* sprite.setEventEnabled(true);
* sprite.on('pointerdown', function () {
* // 执行后,精灵停下来了
* Tiny.Action.cleanup(sprite);
* });
*
* @static
* @param {Tiny.DisplayObject} sprite - 要清除动作的精灵对象
*/
Action.cleanup = function(sprite) {
if (sprite && sprite.actions.length !== 0) {
sprite.actions.forEach(function(action) {
TWEEN.remove(action);
});
sprite.actions = [];
}
};
/**
* 克隆某个 Action
* 如果要让某个 Action 被多个精灵使用,请使用 clone 方法以避免因为精灵的初始状态不一导致的动画冲突
*
* @example
* var action = Tiny.MoveBy(1000, Tiny.point(10));
* sprite1.runAction(action);
* sprite2.runAction(Tiny.Action.clone(action));
* sprite3.runAction(Tiny.Repeat(5, Tiny.Action.clone(action)));
*
* @example
* var action1 = Tiny.ScaleTo(500, Tiny.scale(0.5));
* var action2 = var action = Tiny.RotateBy(1000, {rotation: Tiny.deg2radian(-75)});
* sprite1.runAction(action1);
* sprite2.runSequenceAction(Tiny.Action.clone(action1), action2);
*
* @param {Tiny.Action} action - 要克隆的 action
* @version 1.0.2
* @return {Tiny.Action} 克隆后的 action
*/
Action.clone = function(action) {
if (action === null || !isObject(action)) {
return action;
}
const type = action._type;
let to = isUndefined(action._to) ? action.to : action._to;
if (isObject(to)) {
to = Object.assign(Object.create(Object.prototype), to);
}
let clone;
switch (type) {
case 'Blink':
case 'TintBy':
case 'TintTo':
clone = ActionInterval[type](action._arg[0], action._arg[1]);
break;
case 'JumpTo':
clone = ActionInterval[type](action.duration, to, action._arg[0], action._arg[1]);
break;
default:
clone = ActionInterval[type](action.duration, to);
}
clone.setDelay(action.delay);
clone.setEasing(action.easing);
clone.setInterpolation(action.interpolation);
clone.setRepeatDelay(action.repeatDelayTime);
return clone;
};
/**
* 让 action 们动起来吧
*
* > Tips: 多组action同时:`runAction([action1, action2], action3)`
*
* @example
* var action = Tiny.MoveBy(1000, Tiny.point(100, 100));
* container.runAction(Tiny.RepeatForever(action));
* // container 会在舞台的(0, 0)位置和(100, 100)位置来回不停的移动
*
* @memberof Tiny.Container#
* @function runAction
* @param {array<Tiny.Action>|Tiny.Action} actions - action 数组或参数序列
*/
Container.prototype.runAction = function(actions) {
const actionArray = Array.isArray(actions) ? actions : arguments;
for (let i = 0; i < actionArray.length; i++) {
actionArray[i]._caller = 'runAction';
const action = actionArray[i].create()(this).start();
this.actions.push(action);
}
};
/**
* 有顺序的让 action 们动起来吧
*
* > Tips: `runSequenceAction(action1, action2)`
*
* @example
* var action1 = Tiny.MoveTo(1000, Tiny.point(100, 100));
* var action2 = Tiny.ScaleBy(1200, Tiny.scale(0.25, 2));
* var action3 = Tiny.RotateTo(2000, {rotation: Tiny.PI_2});
* container.runSequenceAction(action1, action2, action3);
* //container 先在1000ms内从坐标(0, 0)移动到(100, 100),然后在1200ms内横向缩小0.25倍,纵向拉伸2倍,最后在2000ms内顺时针旋转360度
*
* @memberof Tiny.Container#
* @function runSequenceAction
* @param {array<Tiny.Action>|Tiny.Action} actions - action 数组或参数序列
*/
Container.prototype.runSequenceAction = function(actions) {
const self = this;
const actionArray = Array.isArray(actions) ? actions : arguments;
const tempArray = [];
if ((actionArray.length > 0) && (actionArray[actionArray.length - 1] == null)) {
throw new Error('parameters should not be ending with null');
}
for (let i = 0; i < actionArray.length; i++) {
tempArray.push(actionArray[i].create()(self));
}
for (let i = tempArray.length - 1; i > 0; i--) {
tempArray[i - 1].chain(tempArray[i]);
}
tempArray[0].start();
};