展会信息港展会大全

关于cocos2dx中的action源码分析
来源:互联网   发布日期:2015-09-28 10:51:34   浏览:1692次  

导读: action是cocos2dx扮演中很重要的角色,很多特殊的效果,都是通过他来实现,而且通过他可以方便的产生很多效果, 而不需要太多的相关知识储备、以及实现技巧。借着学习的思路,我们走一下co...

action是cocos2dx扮演中很重要的角色,很多特殊的效果,都是通过他来实现,而且通过他可以方便的产生很多效果,

而不需要太多的相关知识储备、以及实现技巧。借着学习的思路,我们走一下cocos2dx中action的流程分析,大家共勉

吧。

【ActionManager篇】

一般action的入口在:

Action * Node::runAction(Action* action)

{

CCASSERT( action != nullptr, "Argument must be non-nil");

_actionManager->addAction(action, this, !_running);

return action;

}

他是node一个借口,而node是cocos2dx中绝大部分类的根类。所以意味着基本所有的cocos2dx具体类,都可以调用这个接口,

也意味着基本都可以为其添加特殊效果。

里面_actionManager成员,在Node::Node()构造函数里赋值,如下

_actionManager = director->getActionManager();

从director类获取到了,而director类是cocosdx里的最基本的功能类,在其init方法里,

_actionManager = new ActionManager();

// 初始化完,马上就注册了update更新

_scheduler->scheduleUpdate(_actionManager, Scheduler::PRIORITY_SYSTEM, false);

在其初始化后,就注册了update更新事件,所以ActionManager的update方法在每帧都会调用了,达到可以更新action的作用。

接着分析ActionManager::addAction,看action是如何加入ActionManager

void ActionManager::addAction(Action *action, Node *target, bool paused)

{

CCASSERT(action != nullptr, "");

CCASSERT(target != nullptr, "");

tHashElement *element = nullptr;

// we should convert it to Ref*, because we save it as Ref*

Ref *tmp = target;

// 根据目标来查找对应的数据(tHashElement)

HASH_FIND_PTR(_targets, &tmp, element);

// 没有找到

if (! element)

{

// 构造元素tHashElement空间

element = (tHashElement*)calloc(sizeof(*element), 1);

element->paused = paused;

// 这里引用了target,为了防止在更新action,其宿主释放,这里retain一下

target->retain();

element->target = target;

// 添加至_targets,以target的指针为key

HASH_ADD_PTR(_targets, target, element);

}

// 分配element里的成员空间

actionAllocWithHashElement(element);

//

CCASSERT(! ccArrayContainsObject(element->actions, action), "");

// 将action放入element->actions

ccArrayAppendObject(element->actions, action);

// 这里设置一下action的对象

action->startWithTarget(target);

}

里面涉及的方法如下:

// 分配tHashElement成员空间

void ActionManager::actionAllocWithHashElement(tHashElement *element)

{

// 分配action的存储空间,默认是4个

// 4 actions per Node by default

if (element->actions == nullptr)

{

element->actions = ccArrayNew(4);

}else

if (element->actions->num == element->actions->max) // action满了,空间翻倍

{

ccArrayDoubleCapacity(element->actions);

}

}

// 将action放入element

void ccArrayAppendObject(ccArray *arr, Ref* object)

{

CCASSERT(object != nullptr, "Invalid parameter!");

// 这里将action retain了一下

object->retain();

// 放置action序列末尾

arr->arr[arr->num] = object;

arr->num++;

}

// 设置action的对象

void Action::startWithTarget(Node *aTarget)

{

_originalTarget = _target = aTarget;

}

好了,到此,我们的action已经加入ActionManager中去了,现在由cocos2dx框架来驱动action了,前面已经分析了,ActionManager::update

会每帧调用,我们下面分析update

void ActionManager::update(float dt)

{

// 遍历_targets

for (tHashElement *elt = _targets; elt != nullptr; )

{

//

_currentTarget = elt;

_currentTargetSalvaged = false;

// 该对象没有暂停

if (! _currentTarget->paused)

{

// 遍历该对象的所有actions

// 英文提示该actions可能在循环里改变

// The 'actions' MutableArray may change while inside this loop.

for (_currentTarget->actionIndex = 0; _currentTarget->actionIndex < _currentTarget->actions->num;

_currentTarget->actionIndex++)

{

// 当前action

_currentTarget->currentAction = (Action*)_currentTarget->actions->arr[_currentTarget->actionIndex];

if (_currentTarget->currentAction == nullptr)

{

continue;

}

_currentTarget->currentActionSalvaged = false;

// 回调action::step

_currentTarget->currentAction->step(dt);

if (_currentTarget->currentActionSalvaged)

{

// 这里action::release了,记得前面我们分析,在

// void ccArrayAppendObject(ccArray *arr, Ref* object) retain了一下

// The currentAction told the node to remove it. To prevent the action from

// accidentally deallocating itself before finishing its step, we retained

// it. Now that step is done, it's safe to release it.

_currentTarget->currentAction->release();

} else

if (_currentTarget->currentAction->isDone()) // action完成

{

// 回调action::stop

_currentTarget->currentAction->stop();

// 移除该action

Action *action = _currentTarget->currentAction;

// Make currentAction nil to prevent removeAction from salvaging it.

_currentTarget->currentAction = nullptr;

// 移除该action,其会改变_currentTargetSalvaged的标志,

// 也会改变_currentTarget->actions->num

removeAction(action);

}

// 清理一下当前action,

_currentTarget->currentAction = nullptr;

}

}

// elt, at this moment, is still valid

// so it is safe to ask this here (issue #490)

elt = (tHashElement*)(elt->hh.next);

// 如果该actions没有action,并且_currentTargetSalvaged为真

// only delete currentTarget if no actions were scheduled during the cycle (issue #481)

if (_currentTargetSalvaged && _currentTarget->actions->num == 0)

{

deleteHashElement(_currentTarget);

}

}

// issue #635

_currentTarget = nullptr;

}

简单来说,update的工作,就是遍历_targets,换句话说,就是遍历所有调用了runAction方法的Node对象,执行其Action的step方法,传入的是每帧

逝去的时间,在处理完后,看这个action是不是完成了,完成了的话,就移除该action,在最后,如果该tHashElement的actions都没有action,就移除

该tHashElement,其中里面涉及的方法分析如下:

void ActionManager::removeAction(Action *action)

{

// explicit null handling

if (action == nullptr)

{

return;

}

// 找到该action对应的tHashElement

tHashElement *element = nullptr;

Ref *target = action->getOriginalTarget();

HASH_FIND_PTR(_targets, &target, element);

if (element)

{

// 获得该action在actions的索引

auto i = ccArrayGetIndexOfObject(element->actions, action);

if (i != CC_INVALID_INDEX)

{

// 移除该索引位的action

removeActionAtIndex(i, element);

}

}

else

{

CCLOG("cocos2d: removeAction: Target not found");

}

}

里面涉及的方法如下:

void ActionManager::removeActionAtIndex(ssize_t index, tHashElement *element)

{

Action *action = (Action*)element->actions->arr[index];

// 如果该action是当前处理的action,将该action retain一下,并设置

// currentActionSalvaged 标志

if (action == element->currentAction && (! element->currentActionSalvaged))

{

element->currentAction->retain();

element->currentActionSalvaged = true;

}

// 这里释放该索引位置的action,并拼合剩下action的位置关系

// 注意最后的参数传了true,说明要清理对象

ccArrayRemoveObjectAtIndex(element->actions, index, true);

// 当前索引并移除,后面索引前移来填充,所以循环索引要回退。

// 仅仅也只有等于的情形吧

// update actionIndex in case we are in tick. looping over the actions

if (element->actionIndex >= index)

{

element->actionIndex--;

}

// 移除该索引后,actions里没有action里

if (element->actions->num == 0)

{

// 当前处理的element,就是移除action所属的tHashElement

// 意味这个tHashElement没有action了,标志其要移除出_targets

if (_currentTarget == element)

{

// 是当前处理的tHashElement,暂缓移除,标志一下

_currentTargetSalvaged = true;

}

else

{

// 如果element不是当前处理的tHashElement,就直接移除

deleteHashElement(element);

}

}

}

再接着:

void ccArrayRemoveObjectAtIndex(ccArray *arr, ssize_t index, bool releaseObj/* = true*/)

{

// 要不要清理该对象

CCASSERT(arr && arr->num > 0 && index>=0 && index < arr->num, "Invalid index. Out of bounds");

if (releaseObj)

{

CC_SAFE_RELEASE(arr->arr[index]);

}

// actions个数减一

arr->num--;

// 该位置移除,后面的填上

ssize_t remaining = arr->num - index;

if(remaining>0)

{

memmove((void *)&arr->arr[index], (void *)&arr->arr[index+1], remaining * sizeof(Ref*));

}

}

至此,ActionManager部分,就是Aciton的框架部分,分析至此,脉络已经出来了,就是Scheduler驱动着这一切,我们只要调用runAction,

将Action交给ActionManager就好了,

【Action篇】

下面分析一下action的体系,action的怎样实现,造就了如此丰富的action效果,

下面是我重新摘除重点的action类定义

class CC_DLL Action : public Ref, public Clonable

{

public:

/** returns a clone of action */

virtual Action* clone() const = 0;

/** returns a new action that performs the exactly the reverse action */

virtual Action* reverse() const = 0;

//! called before the action start. It will also set the target.

virtual void startWithTarget(Node *target);

//called after the action has finished. It will set the 'target' to nil.

virtual void stop();

//! called every frame with it's delta time. DON'T override unless you know what you are doing.

virtual void step(float dt);

For example:

- 0 means that the action just started

- 0.5 means that the action is in the middle

- 1 means that the action is over

*/

virtual void update(float time);

protected:

Node*_originalTarget;

/** The "target".

The target will be set with the 'startWithTarget' method.

When the 'stop' method is called, target will be set to nil.

The target is 'assigned', it is not 'retained'.

*/

Node*_target;

/** The action tag. An identifier of the action */

int_tag;

}

透露了几个信息

1、该类继承自Clonable

class CC_DLL Clonable

{

public:

/** returns a copy of the Ref */

virtual Clonable* clone() const = 0;

/**

* @js NA

* @lua NA

*/

virtual ~Clonable() {};

}

就是定了克隆的接口,由于action的使用很频繁,所有有克隆是一个很重要的特性,

2、有reverse接口,说明reverse也是一个常备的特性,一个动作常常提供反转动作,但是也不是都会实现这个反转。

3、step是框架调用的,自己实现要慎重,后面会分析到,这个接口基本可以不用重载。

4、action的成员变量,就是有一个原始目标,还有一个目前目标,而且注明该目标是简单的赋值,不负责维护引用

这个action是个抽象类,规定了一些接口,用来为ActionManager提供操作接口。

下面看看最常用的有限时间动作,我摘除重要的部分如下:

class CC_DLL FiniteTimeAction : public Action

{

protected:

//! duration in seconds

float _duration;

}

其实就是增加了一个时间段。而且还是一个抽象类,没有什么具体作用,下面看看他的具体应用,区间动作(ActionInterval)

class CC_DLL ActionInterval : public FiniteTimeAction

{

public:

// 实现了完成的条件,就是逝去时间大于动作区间

virtual bool isDone(void) const override;

// 这里需要分析下,他是reverse实现的基础

virtual void step(float dt) override;

protected:

float _elapsed;

bool_firstTick;

};

这里要注意下step的实现

void ActionInterval::step(float dt)

{

// 第一次调用,初始化下

if (_firstTick)

{

_firstTick = false;

// 逝去时间初始化

_elapsed = 0;

}

else

{

// 记录逝去的时间和

_elapsed += dt;

}

// 这个表达式表达就是,update的参数,就是逝去的时间在整个动作

// 时间的比例,而不是时间间隔了。

// _elapsed = 0,就是update(0)

// _elapsed = _duration 就是update(1)

this->update(MAX (0,// needed for rewind. elapsed could be negative

MIN(1, _elapsed /

MAX(_duration, FLT_EPSILON)// division by 0

)

)

);

}

下面分析个实例吧,Repeat的实现

Repeat的辅助数据如下:

protected:

// 需要重复次数

unsigned int _times;

// 已重复次数

unsigned int _total;

// 重复动作时间占比,用于统计_total,

float _nextDt;

// 标记该动作是不是瞬时动作

bool _actionInstant;

/** Inner action */

// 重复的动作

FiniteTimeAction *_innerAction;

具体说明都在注释上

void Repeat::startWithTarget(Node *target)

{

// 初始化已重复次数

_total = 0;

// 本动作在总时间的占比

_nextDt = _innerAction->getDuration()/_duration;

ActionInterval::startWithTarget(target);

// 内部动作也初始化下对象

_innerAction->startWithTarget(target);

}

核心实现update,如下:

void Repeat::update(float dt)

{

// 当前时间比例,已经超过一次动作的时间比例

if (dt >= _nextDt)

{

// 出现这种情况,只有卡的情况吧,

// 时间比例超了动作时间占比,而次数又没有到目标

while (dt > _nextDt && _total < _times)

{

// 目标动作直接更新完成

_innerAction->update(1.0f);

// 已重复次数增加

_total++;

// 动作停止接着又开始,保证目标动作的回调函数都调用到

_innerAction->stop();

_innerAction->startWithTarget(_target);

// 重算下次目标占比

_nextDt = _innerAction->getDuration()/_duration * (_total+1);

}

// 总时间占比完成,但是次数没有达到,一般是临界情况

// fix for issue #1288, incorrect end value of repeat

if(dt >= 1.0f && _total < _times)

{

_total++;

}

// don't set an instant action back or update it, it has no use because it has no duration

if (!_actionInstant)

{

if (_total == _times)

{

_innerAction->update(1);

_innerAction->stop();

}

else // 最后一帧让他执行完

{

// issue #390 prevent jerk, use right update

_innerAction->update(dt - (_nextDt - _innerAction->getDuration()/_duration));

}

}

}

else

{

// 目标动作执行update(单动作的时间占比),

_innerAction->update(fmodf(dt * _times,1.0f));

}

}

以上,就是action的一点点分析,希望对大家有点帮助。

赞助本站

人工智能实验室
AiLab云推荐
展开

热门栏目HotCates

Copyright © 2010-2024 AiLab Team. 人工智能实验室 版权所有    关于我们 | 联系我们 | 广告服务 | 公司动态 | 免责声明 | 隐私条款 | 工作机会 | 展会港