1. 从一个Action开始
1.1 创建
在Scene里写一个Sprite,并添加Action:
Sprite *sp = Sprite::create("m1.png");
MoveBy *mb = MoveBy::create(20, Vec2(300,10));
sp->runAction(mb);
this->addChild(sp);
先通过create方法创建Action,之后Sprite和Action通过runAction方法产生联系。
create方法坐标参数被转为Z为0的三维坐标。
看create方法主要语句:
MoveBy* MoveBy::create(float duration, const Vec2& deltaPosition)
{
return MoveBy::create(duration, Vec3(deltaPosition.x, deltaPosition.y, 0));
}
MoveBy* MoveBy::create(float duration, const Vec3 &deltaPosition)
{
MoveBy *ret = new (std::nothrow) MoveBy();
if (ret && ret->initWithDuration(duration, deltaPosition))
{
ret->autorelease();
return ret;
}
delete ret;
return nullptr;
}
Action交给内存管理池进行内存管理,还要执行initWithDuration方法。
bool MoveBy::initWithDuration(float duration, const Vec3& deltaPosition)
{
bool ret = false;
if (ActionInterval::initWithDuration(duration))
{
_positionDelta = deltaPosition;
_is3D = true;
ret = true;
}
return ret;
}
看initWithDuration方法,调用了父类的ActionInterval的initWithDuration(duration),并设置MoveBy的成员变量:
_positionDelta = deltaPosition;
_is3D = true;
看父类的ActionInterval的initWithDuration(duration)方法,是设置了父类成员变量:
bool ActionInterval::initWithDuration(float d)
{
_duration = d;
_elapsed = 0;
_firstTick = true;
_done = false;
return true;
}
创建好了Action,执行runAction方法,主要是执行下面的语句:
Action* Sprite3D::runAction(Action *action)
{
setForceDepthWrite(true);
return Node::runAction(action);
}
Action * Node::runAction(Action* action)
{
CCASSERT( action != nullptr, "Argument must be non-nil");
_actionManager->addAction(action, this, !_running);
return action;
}
ActionManager的addAction(...)方法;
ActionManager的addAction方法的大致流程:
void ActionManager::addAction(Action *action, Node *target, bool paused)
{
CCASSERT(action != nullptr, "action can't be nullptr!");
CCASSERT(target != nullptr, "target can't be nullptr!");
if(action == nullptr || target == nullptr)
return;
tHashElement *element = nullptr;
// we should convert it to Ref*, because we save it as Ref*
Ref *tmp = target;
HASH_FIND_PTR(_targets, &tmp, element);
if (! element)
{
element = (tHashElement*)calloc(sizeof(*element), 1);
element->paused = paused;
target->retain();
element->target = target;
HASH_ADD_PTR(_targets, target, element);
}
actionAllocWithHashElement(element);
CCASSERT(! ccArrayContainsObject(element->actions, action), "action already be added!");
ccArrayAppendObject(element->actions, action);
action->startWithTarget(target);
}
结束之前执行action的startWithTarget(target)方法,该方法实际执行了其所有继承类的同名方法,对以下成员变量设置:
void MoveBy::startWithTarget(Node *target)
{
ActionInterval::startWithTarget(target);
_previousPosition = _startPosition = target->getPosition3D();
}
void ActionInterval::startWithTarget(Node *target)
{
FiniteTimeAction::startWithTarget(target);
_elapsed = 0.0f;
_firstTick = true;
_done = false;
}
void Action::startWithTarget(Node *aTarget)
{
_originalTarget = _target = aTarget;
}
_originalTarget = _target = aTarget; // Action
_elapsed = 0.0f;
_firstTick = true;
_done = false;
_previousPosition = _startPosition = target->getPosition3D(); // MoveBy
简要地说,是把action添加到node对应的element的actions容器中,并通过ActionManager把action和node匹配,并不是“run”。
1.2 运行
Action的实际运行在ActionManager的update方法中,该方法简要地说是对ActionManager的哈希链表_targets进行遍历,对遍历到的每个element的actions再遍历,对遍历到的action执行step(float dt)。
// main loop
void ActionManager::update(float dt)
{
for (tHashElement *elt = _targets; elt != nullptr; )
{
_currentTarget = elt;
_currentTargetSalvaged = false;
if (! _currentTarget->paused)
{
// The 'actions' MutableArray may change while inside this loop.
for (_currentTarget->actionIndex = 0; _currentTarget->actionIndex < _currentTarget->actions->num;
_currentTarget->actionIndex++)
{
_currentTarget->currentAction = static_cast<Action*>(_currentTarget->actions->arr[_currentTarget->actionIndex]);
if (_currentTarget->currentAction == nullptr)
{
continue;
}
_currentTarget->currentActionSalvaged = false;
_currentTarget->currentAction->step(dt);
if (_currentTarget->currentActionSalvaged)
{
// 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())
{
_currentTarget->currentAction->stop();
Action *action = _currentTarget->currentAction;
// Make currentAction nil to prevent removeAction from salvaging it.
_currentTarget->currentAction = nullptr;
removeAction(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);
// only delete currentTarget if no actions were scheduled during the cycle (issue #481)
if (_currentTargetSalvaged && _currentTarget->actions->num == 0)
{
deleteHashElement(_currentTarget);
}
//if some node reference 'target', it's reference count >= 2 (issues #14050)
else if (_currentTarget->target->getReferenceCount() == 1)
{
deleteHashElement(_currentTarget);
}
}
// issue #635
_currentTarget = nullptr;
}
MoveBy的step(float dt)方法实际是执行父类的同名方法,在每次step方法中又会调用一次MoveBy的update方法,step和update的大致流程:
2. 总结
创建并使用action,用到了action的create方法和node的runAction方法,也使action与node产生了关联。
随着ActionManager在每帧调用回调函数update,每个element的action都会执行一次step方法和update方法,step通过已执行的时间(进度时间与帧间隔dt的和)与我们定义的总时长来计算动作执行进度,update把执行进度转为我们在屏幕上看到的进度。当进度到1,动作执行完了,_done置true。
void ActionInterval::step(float dt)
{
if (_firstTick)
{
_firstTick = false;
_elapsed = 0;
}
else
{
_elapsed += dt;
}
float updateDt = std::max(0.0f, // needed for rewind. elapsed could be negative
std::min(1.0f, _elapsed / _duration)
);
if (sendUpdateEventToScript(updateDt, this)) return;
this->update(updateDt);
_done = _elapsed >= _duration;
}
step里面调用action的update方法
void MoveBy::update(float t)
{
if (_target)
{
#if CC_ENABLE_STACKABLE_ACTIONS
Vec3 currentPos = _target->getPosition3D();
Vec3 diff = currentPos - _previousPosition;
_startPosition = _startPosition + diff;
Vec3 newPos = _startPosition + (_positionDelta * t);
_target->setPosition3D(newPos);
_previousPosition = newPos;
#else
_target->setPosition3D(_startPosition + _positionDelta * t);
#endif // CC_ENABLE_STACKABLE_ACTIONS
}
}
参考:https://www.cnblogs.com/deepcho/p/cocos2dx-action-how-to-run.html