一、前言
嗨,大家好,我是思航。 今天我们通过一个简单的例子,来研究一下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);