import TWEEN from '../../libs/tween.js';
import * as core from '../core';
/**
* 动作类
*
* `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;
}
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.onStart(this, object);
})
.onUpdate(function () {
self.onUpdate(this, object);
})
.onComplete(function () {
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
}
/**
* 设置动画缓冲
*
* 内置的缓冲效果如下表:
*
* | Linear.None | |
* |:--:|:--:|:--:
* | ||
* | Quadratic.In | Quadratic.Out| Quadratic.InOut | Cubic.In | Cubic.Out| Cubic.InOut
* ||||||
* | Quartic.In | Quartic.Out| Quartic.InOut | Quintic.In | Quintic.Out| Quintic.InOut
* ||||||
* | Sinusoidal.In | Sinusoidal.Out| Sinusoidal.InOut | Exponential.In | Exponential.Out| Exponential.InOut
* ||||||
* | Circular.In | Circular.Out| Circular.InOut | Elastic.In | Elastic.Out| Elastic.InOut
* ||||||
* | Back.In | Back.Out| Back.InOut | Bounce.In | Bounce.Out| Bounce.InOut
* ||||||
*
* @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
* |:--:|:--:|:--:
* |||
* | start===end | start===end | start===end
* |||
*
* @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} obj - 克隆后的 action
*/
Action.clone = function (action) {
if (action === null || !core.isObject(action)) {
return action;
}
const type = action._type;
let to = core.isUndefined(action._to) ? action.to : action._to;
if (core.isObject(to)) {
to = Object.assign(Object.create(Object.prototype), to);
}
let clone;
switch (type) {
case 'Blink':
case 'TintBy':
case 'TintTo':
clone = Tiny[type](action._arg[0], action._arg[1]);
break;
case 'JumpTo':
clone = Tiny[type](action.duration, to, action._arg[0], action._arg[1]);
break;
default:
clone = Tiny[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 数组或参数序列
*/
core.Container.prototype.runAction = function (actions) {
const actionArray = core.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.CONST.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 数组或参数序列
*/
core.Container.prototype.runSequenceAction = function (actions) {
const self = this;
const actionArray = core.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();
};