coco2d-x-html5之动作系统

一、前言

嗨,大家好,我是思航。 今天我们通过一个简单的例子,来研究一下cocos2dx是动作的执行流程,最后再实现自定义一个动作类。

二、代码和效果

1、代码

var pSprite = cc.Sprite.create("src/res/ui/icon.png");
pSprite.setPosition(cc.p(cc.winSize.width/2, cc.winSize.height/2));
cc.director.getRunningScene().addChild(pSprite)
var pMoveByAction = cc.moveBy(1, cc.p(100, 100));
pSprite.runAction(pMoveByAction); 

2、效果

我们播放了一个横纵坐标各移动100像素的动作 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o8ZKxte1-1653293026899)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3c4f0413102d4fd0acc8c7610c0b81c3~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image)]

三、分析

1、执行 CCDirector.js的drawScene方法

为什么每帧会执行drawScene方法?可以参考之前的文章 “coco2d-x-html5之Director解析”

// CCDirector.js
drawScene: function () {
        var renderer = cc.renderer;

        // calculate "global" dt
        // 计算切片时间(每帧之间调用时的时间间隔)
        this.calculateDeltaTime();

        //tick before glClear: issue #533
        if (!this._paused) {
            // 执行调度器的update函数
            this._scheduler.update(this._deltaTime);
            cc.eventManager.dispatchEvent(this._eventAfterUpdate);
        } 

在drawScene函数中,我们看到执行CCScheduler.js的 update函数

2、执行CCScheduler.js的 update函数

// CCScheduler.js
/**
 * 'update' the scheduler. (You should NEVER call this method, unless you know what you are doing.)
 * @param {Number} dt delta time
 */
update:function (dt) {
    this._updateHashLocked = true;
    if(this._timeScale !== 1)
        dt *= this._timeScale;

    var i, list, len, entry;

    // 默认是cc.ActionManager
    for(i=0,list=this._updatesNegList, len = list.length; i<len; i++){
        entry = list[i];
        if(!entry.paused && !entry.markedForDeletion)
            entry.callback(dt);
    }
    ......
 } 

2.1 CCActionManager如入添加到CCScheduler的_updatesNegList

通过上面源码我们看到,在CCScheduler.js的update函数里面,遍历执行_updatesNegList的元素的callback函数。 那就是说我们要看下CCActionManager实例化出来的对象,是怎么添加到了CCActionManager的_updatesNegList里面

// CCDirector.js
init: function () {
      ......
      //scheduler
      // 实例化 Scheduler
      this._scheduler = new cc.Scheduler();
      //action manager
      if (cc.ActionManager) {
      	  // 实例化 ActionManager
          this._actionManager = new cc.ActionManager();
          // 将actionManager注册到scheduler._updatesNegList
          this._scheduler.scheduleUpdate(this._actionManager, cc.Scheduler.PRIORITY_SYSTEM, false);
      } else {
          this._actionManager = null;
      }
      ...... 
} 

接下来看下 CCScheduler.js 的 scheduleUpdate 函数

// CCScheduler.js 
 scheduleUpdate: function(target, priority, paused){
    this._schedulePerFrame(function(dt){
         // 注册后,每帧执行target.update方法
         target.update(dt);
     }, target, priority, paused);
 }, 

scheduleUpdate 调用了 _schedulePerFrame函数,所以我们接着玩下看

 _schedulePerFrame: function(callback, target, priority, paused){
        ......
        // 根据不同的优先级,压入到不同的队列
        if (priority === 0){
            this._appendIn(this._updates0List, callback, target, paused);
        }else if (priority < 0){
            this._priorityIn(this._updatesNegList, callback, target, priority, paused);
        }else{
            // priority > 0
            this._priorityIn(this._updatesPosList, callback, target, priority, paused);
        }
    }, 

上面CCDirector.js的init里面,执行了

this._scheduler.scheduleUpdate(this._actionManager, cc.Scheduler.PRIORITY_SYSTEM, false); 

因为cc.Scheduler.PRIORITY_SYSTEM为负数,即priority为负数,这样ActionManager就注册到_updatesNegList里面的。

到这里我们就明白了是如何执行到 CCActionManager.js 的update函数。

3、执行CCActionManager.js 的update函数

// CCActionManager.js
/**
* @param {Number} dt delta time in seconds
*/
update:function (dt) {
   var locTargets = this._arrayTargets , locCurrTarget;
   // this._arrayTargets 是一个数组,里面元素elt 是个字典
   // elt 格式为 {actions: [动作1,动作2,...], }
   for (var elt = 0; elt < locTargets.length; elt++) {
       this._currentTarget = locTargets[elt];
       locCurrTarget = this._currentTarget;
       // 没有暂停,且actions有值
       if (!locCurrTarget.paused && locCurrTarget.actions) {
           locCurrTarget.lock = true;
           // The 'actions' CCMutableArray may change while inside this loop.
           for (locCurrTarget.actionIndex = 0; locCurrTarget.actionIndex < locCurrTarget.actions.length; locCurrTarget.actionIndex++) {
               locCurrTarget.currentAction = locCurrTarget.actions[locCurrTarget.actionIndex];
               if (!locCurrTarget.currentAction)
                   continue;

               //use for speed
               // 这里就是具体的动作,比如 cc.MoveBy实例化出来的对象
               locCurrTarget.currentAction.step(dt * ( locCurrTarget.currentAction._speedMethod ? locCurrTarget.currentAction._speed : 1 ) );
               
               if (locCurrTarget.currentAction && locCurrTarget.currentAction.isDone()) {
                   locCurrTarget.currentAction.stop();
                   var action = locCurrTarget.currentAction;
                   locCurrTarget.currentAction = null;
                   this.removeAction(action);
               }

               locCurrTarget.currentAction = null;
           }
           locCurrTarget.lock = false;
       }
       // only delete currentTarget i 

通过上面源码,我们发现这边执行了CCAction 的 step函数 那我们要看下一个动作是如何添加到_arrayTargets里面的

3.1 动作如何添加到CCActionManager.js 的_arrayTargets

这边我们换种方式来看代码。我们回到我们最上面的例子代码。

var pSprite = cc.Sprite.create("src/res/ui/icon.png");
pSprite.setPosition(cc.p(cc.winSize.width/2, cc.winSize.height/2));
cc.director.getRunningScene().addChild(pSprite)
var pMoveByAction = cc.moveBy(1, cc.p(100, 100));
pSprite.runAction(pMoveByAction); 

首先看下CCSprite.js的runAction函数,我们发现没有重写该函数,那我们看下父类CCNode.js的runAction函数,

// CCNode.js
runAction: function (action) {
    cc.assert(action, cc._LogInfos.Node_runAction);
	// 交给了ActionManager管理
    this.actionManager.addAction(action, this, !this._running);
    return action;
}, 

通过代码看到,runAction调用了actionManager的addAction方法

// CCActionManager.js
addAction:function (action, target, paused) {
        if(!action)
            throw new Error("cc.ActionManager.addAction(): action must be non-null");
        if(!target)
            throw new Error("cc.ActionManager.addAction(): target must be non-null");

        //check if the action target already exists
        var element = this._hashTargets[target.__instanceId];
        //if doesn't exists, create a hashelement and push in mpTargets
        if (!element) {
            element = this._getElement(target, paused);
            this._hashTargets[target.__instanceId] = element;
            this._arrayTargets.push(element);
        }
        else if (!element.actions) {
            element.actions = [];
        }
		// 添加到actions里面
        element.actions.push(action);
        // 添加了目标对象
        action.startWithTarget(target);
    }, 

4、执行 cc.MoveBy 的 step函数

在这里插入图片描述 我们发现cc.MoveBy没有重写step函数 那我们看下父类cc.ActionInterval 的step函数

step: function (dt) {
     if (this._firstTick) {
         this._firstTick = false;
         this._elapsed = 0;
     } else
     {
         // 消逝的时间
         this._elapsed += dt;
     }

     // this._duration 动作为持续时间

     //this.update((1 > (this._elapsed / this._duration)) ? this._elapsed / this._duration : 1);
     //this.update(Math.max(0, Math.min(1, this._elapsed / Math.max(this._duration, cc.FLT_EPSILON))));
     var t = this._elapsed / (this._duration > 0.0000001192092896 ? this._duration : 0.0000001192092896);
     // t 的值为 0到1之间,当前累计时间/总的动作需要时间
     // t 为当前帧比例,到时这个值去乘以总的需要移动的距离,就可以算出当前的偏移值
     t = (1 > t ? t : 1);
     this.update(t > 0 ? t : 0); 

我们看到在step里面调用了 update函数,那我们看下cc.MoveBy的updata函数

 /**
     * Called once per frame. Time is the number of seconds of a frame interval.
     * @param {Number} dt
     */
    update: function (dt) {
        dt = this._computeEaseTime(dt);
        if (this.target) {
            // this._positionDelta.x : 总偏移量x 
            // dt 为 0 到 1 之间的比例 
            var x = this._positionDelta.x * dt;
            var y = this._positionDelta.y * dt;
            var locStartPosition = this._startPosition;
            // 动作叠加
            if (cc.ENABLE_STACKABLE_ACTIONS) {
                // 动作叠加的代码先注释掉,先看简易的分支
                ......
            } else {
                this.target.setPosition(locStartPosition.x + x, locStartPosition.y + y);
            }
        }
    }, 

可以看出动画本质上是每帧刷新精灵的坐标。

四、自定义动作类

通过上面的分析我们知道了cc.moveBy的动作时候怎么生效的,那么接下来我们自定义一个动作类,cc.rectBy,这个动作是指定时间内,移动指定边长的正方形。

4.1 自定义动作类代码

cc.RectBy = cc.ActionInterval.extend(/** @lends cc.MoveBy# */{
    _positionDelta: null,
    _startPosition: null,
    _previousPosition: null,

    /**
     * Constructor function, override it to extend the construction behavior, remember to call "this._super()" in the extended "ctor" function.
     * @param {Number} duration duration in seconds
     * @param {Number} borderLenght 正方形边长
     * @param {Number} [deltaY]
     */
    ctor: function (duration, borderLenght) {
        cc.ActionInterval.prototype.ctor.call(this);
        cc.ActionInterval.prototype.initWithDuration.call(this, duration)
        this._borderLenght = borderLenght;
        this._startPosition = cc.p(0, 0);
    },

    /**
     * Start the action with target.
     * @param {cc.Node} target
     */
    startWithTarget: function (target) {
        cc.ActionInterval.prototype.startWithTarget.call(this, target);
        var locPosX = target.getPositionX();
        var locPosY = target.getPositionY();
        this._startPosition.x = locPosX;
        this._startPosition.y = locPosY;
    },

    /**
     * Called once per frame. Time is the number of seconds of a frame interval.
     * @param {Number} dt
     */
    update: function (dt) {
        dt = this._computeEaseTime(dt);
        if (this.target) {
            // this._positionDelta.x : 总偏移量x 
            // dt 为 0 到 1 之间的比例 
            var x = 0;
            var y = 0;
            
            if (dt <= 0.25) { // 上升
                y = this._borderLenght * dt * 4;
            } else if (dt <= 0.5) { // 右移
                y = this._borderLenght;
                x = this._borderLenght * (dt - 0.25) * 4;
            } else if (dt <= 0.75) { // 下降
                x = this._borderLenght;
                y = this._borderLenght - (dt - 0.5) * this._borderLenght * 4;
            } else { // 左移
                x =  this._borderLenght - (dt - 0.75) * this._borderLenght * 4;
            }
                    
            var locStartPosition = this._startPosition;
            this.target.setPosition(locStartPosition.x + x, locStartPosition.y + y);
            
        }
    },

});


cc.rectBy = function (duration, borderLenght) {
    return new cc.RectBy(duration, borderLenght);
}; 

4.2 自定义动作类表现

var pSprite = cc.Sprite.create("src/res/ui/icon.png");
pSprite.setPosition(cc.p(cc.winSize.width/2, cc.winSize.height/2));
cc.director.getRunningScene().addChild(pSprite)
var pRectByAction = cc.rectBy(2, 100);
pSprite.runAction(pRectByAction); 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值