Source: tiny/actions/Action.js

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();
};
Documentation generated by JSDoc 3.4.3 on Fri Jul 09 2021 19:32:25 GMT+0800 (CST)