文章目录
前言
action是我们在cocos当中用到的相当多的一个功能,常见的调用方式通过node的runAction去执行一系列对应的动作,本篇文章介绍一下和action的设计方式和实现原理
相关流程图
基类action
-
要点
- action继承自Ref和clonable,基本类型可重写clone复制
- action是所有动作的基类, 所有可被执行的动作,都是继承自action
- action重点接口step:每一帧都会被调用,参数是时间跨度
- action重点接口update:每次step都会被调用,但是参数不是时间是完成度(0-1),0表示还没开始,1表示动作已经完成了
- 每帧刷新方式是actionManager在director类初始化的时候,执行的一个定时器,更新ActionManager的update方法
-
总结
- 所有的action都是通过在step和update的刷新更新状态,例如scale,在step里计算当前消耗时间和总时间的比例,然后告诉update执行到哪了,update将target按进度变化
- 所有的action都是通过在step和update的刷新更新状态,例如scale,在step里计算当前消耗时间和总时间的比例,然后告诉update执行到哪了,update将target按进度变化
// 每帧被调用,dt是每帧调用的时间 不要轻易重写
virtual void step(float dt);
// 每帧被调用,time是0-1的一个数,表示完成度,0是没开始,1是完成了
virtual void update(float time);
action子类之Speed
Speed是直接继承自Action的一个拓展动作类,功能是用来调整节点的执行action的速度,普通action、sequence或者repeat都可以用,个人觉得有点鸡肋,不如直接调整action的执行时间更方便
在action中我们介绍了,step是每帧都会被调用的,参数就是时间维度,Speed的实现原理如下所示,在init的时候传了一个参数speed,在重写的step方法内,并不是直接传入了时间dt,而是传入了dt*speed,时间变长了,执行进度自然变快了
bool Speed::initWithAction(ActionInterval *action, float speed)
{
CCASSERT(action != nullptr, "action must not be NULL");
if (action == nullptr)
{
log("Speed::initWithAction error: action is nullptr!");
return false;
}
action->retain();
_innerAction = action;
_speed = speed;
return true;
}
void Speed::step(float dt)
{
_innerAction->step(dt * _speed);
}
# ScaleTo快了四倍
local action1 = cc.ScaleTo:create(10,5)
local action2 = cc.Speed:create(action1,4)
sp:runAction(action2)
action子类之Follow
Follow是直接继承自Action的一个拓展动作类,功能是实现一个节点对领一个节点的跟随运动,可以限制跟随的偏移和跟随的范围,一般用在镜头跟随之类的,博主基本没用过
和Speed基本类似啊,在init的时候传入了跟随的目标,跟随的偏移和范围,然后再step里面去计算应该到得位置
bool Follow::initWithTargetAndOffset(Node *followedNode, float xOffset,float yOffset,const Rect& rect)
{
.......
}
void Follow::step(float /*dt*/)
{
if(_boundarySet)
{
// whole map fits inside a single screen, no need to modify the position - unless map boundaries are increased
if(_boundaryFullyCovered)
{
return;
}
Vec2 tempPos = _halfScreenSize - _followedNode->getPosition();
_target->setPosition(clampf(tempPos.x, _leftBoundary, _rightBoundary),
clampf(tempPos.y, _bottomBoundary, _topBoundary));
}
else
{
_target->setPosition(_halfScreenSize - _followedNode->getPosition());
}
}
local action1 = cc.Follow:create(sp1,cc.rect(0,0,0,0))
sp2:runAction(action1)
sp1:runAction(cc.MoveTo:create(2,cc.p(360,300)))
action子类之FinitetimeAction
有限次动作执行类,就是按时间顺序执行一系列动作,执行完后动作结束,下分了两个子类ActionInstant和ActionInterval,我们常用action都是继承自这俩
这个中间类比较简单,多了一个和执行时间相关的变量
/** Get duration in seconds of the action.
*
* @return The duration in seconds of the action.
*/
float getDuration() const { return _duration; }
/** Set duration in seconds of the action.
*
* @param duration In seconds of the action.
*/
void setDuration(float duration) { _duration = duration; }
//
// Overrides
//
virtual FiniteTimeAction* reverse() const override
{
CC_ASSERT(0);
return nullptr;
}
virtual FiniteTimeAction* clone() const override
{
CC_ASSERT(0);
return
}
瞬时动作基类ActionInstant
瞬时动作基类ActionInstant,顾名思义,继承自该类实现的动作类型都是在一帧内就执行完成,例如:show、hide、callback等
step调用后传给update直接是1,进度直接搞满
void ActionInstant::step(float /*dt*/)
{
float updateDt = 1;
#if CC_ENABLE_SCRIPT_BINDING
if (_scriptType == kScriptTypeJavascript)
{
if (ScriptEngineManager::sendActionEventToJS(this, kActionUpdate, (void *)&updateDt))
return;
}
#endif
update(updateDt);
}
void ActionInstant::update(float /*time*/)
{
_done = true;
}
Show* Show::create()
{
Show* ret = new (std::nothrow) Show();
if (ret)
{
ret->autorelease();
}
return ret;
}
void Show::update(float time)
{
ActionInstant::update(time);
_target->setVisible(true);
}
ActionInstant* Show::reverse() const
{
return Hide::create();
}
Show* Show::clone() const
{
// no copy constructor
return Show::create();
}
延时动作基类ActionInterval
延时动作基类所有继承自该类写的action子类,都是需要一定的时间去完成,例如:scale、move、fade等
初始化的时候存了两个变量_duration记录总时间,_elapsed记录执行时间,比值计算当前完成进度,传给update去刷新进度
bool ActionInterval::initWithDuration(float d)
{
_duration = d;
_elapsed = 0;
_firstTick = true;
_done = false;
return true;
}
void ActionInterval::step(float dt)
{
if (_firstTick)
{
_firstTick = false;
_elapsed = 0;
}
else
{
_elapsed += dt;
}
float updateDt = MAX (0,
MIN(1, _elapsed / _duration)
);
if (sendUpdateEventToScript(updateDt, this)) return;
this->update(updateDt);
_done = _elapsed >= _duration;
}
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;
}
bool MoveBy::initWithDuration(float duration, const Vec2& deltaPosition)
{
return MoveBy::initWithDuration(duration, Vec3(deltaPosition.x, deltaPosition.y, 0));
}
bool MoveBy::initWithDuration(float duration, const Vec3& deltaPosition)
{
bool ret = false;
if (ActionInterval::initWithDuration(duration))
{
_positionDelta = deltaPosition;
_is3D = true;
ret = true;
}
return ret;
}
MoveBy* MoveBy::clone() const
{
// no copy constructor
return MoveBy::create(_duration, _positionDelta);
}
void MoveBy::startWithTarget(Node *target)
{
ActionInterval::startWithTarget(target);
_previousPosition = _startPosition = target->getPosition3D();
}
MoveBy* MoveBy::reverse() const
{
return MoveBy::create(_duration, -_positionDelta);
}
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
}
}
action管理类ActionManager
ActionManager管理了所有节点执行action相关的内容
执行的action和target都会被存入到一个结构体内,结构体内还存了一些状态,暂停啥的, 每run一个action,增加到当前链表内
typedef struct _hashElement
{
struct _ccArray *actions;
Node *target;
int actionIndex;
Action *currentAction;
bool currentActionSalvaged;
bool paused;
UT_hash_handle hh;
} tHashElement;
在ActionManager的update内会每帧都在遍历链表内所有结构体能够执行的action,然后执行他们的step方法,结束了就执行对应的结束方法,暂停和恢复都是对结构体内状态的调整
// 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;
}
推送
Github:https://github.com/KingSun5
结语
希望看到最后的同学有所收获,若是觉得博主的文章写的不错,不妨关注一下博主,点赞一下博文,另博主能力有限,若文中有出现什么错误的地方,欢迎各位评论指摘。
QQ交流群:806091680(Chinar)
该群为优快云博主Chinar所创,推荐一下!我也在群里!
本文属于原创文章,转载请著名作者出处并置顶!!!!