cocos2dx对动作类进行了精细的划分,最常用的是根据时间属性划分的瞬时动作类ActionInstant和延时动作类ActionInterval及其子类,继承关系如下:
本文对常用动作做了测试,代码和运行效果如下:
bool HelloWorld::init()
{
if ( !Layer::init() )
{
return false;
}
_rootNode = CSLoader::createNode("MainScene.csb");
addChild(_rootNode);
_spritePerson = (Sprite*)_rootNode->getChildByName("Sprite_Person");
Button* btnRunAction = (Button*)_rootNode->getChildByName("BtnAction");
btnRunAction->addClickEventListener(CC_CALLBACK_1(HelloWorld::btnRunCB,this));
Button* btnReset = (Button*)_rootNode->getChildByName("BtnReset");
btnReset->addClickEventListener(CC_CALLBACK_1(HelloWorld::btnResetCB, this));
_label = (Text*)_rootNode->getChildByName("Text1");
return true;
}
void HelloWorld::btnRunCB(Ref* sender)
{
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_MoveTo"))->getSelectedState() == true)
//MoveTo:平移到绝对坐标,第一个参数动作时间,第二个参数终点位置绝对坐标
_spritePerson->runAction(MoveTo::create(1.5, Vec2(600, 200)));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_MoveBy"))->getSelectedState() == true)
//MoveBy:平移相对坐标,,第一个参数动作时间,第二个参数终点位置相对起点位置坐标
_spritePerson->runAction(MoveBy::create(1.5, Vec2(400, 0)));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_FlipX"))->getSelectedState() == true)
//FlipX:X轴翻转,和setFlippedX(true)作用相同
_spritePerson->runAction(FlipX::create(true));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_FlipY"))->getSelectedState() == true)
//FlipY:Y轴翻转,和setFlippedY(true)作用相同
_spritePerson->runAction(FlipY::create(true));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_Hide"))->getSelectedState() == true)
//Hide:隐藏,和setVisible(false)作用相同
_spritePerson->runAction(Hide::create());
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_Show"))->getSelectedState() == true)
//Show:显示,和setVisible(true)作用相同
_spritePerson->runAction(Show::create());
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_Place"))->getSelectedState() == true)
//Place:放置到某位置,和setPosition(x,y)作用相同
_spritePerson->runAction(Place::create(Vec2(200,600)));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_ToggleVisibility"))->getSelectedState() == true)
//ToggleVisibility:更改可见性,和setVisible(true)/setVisible(false)作用相同
_spritePerson->runAction(ToggleVisibility::create());
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_ScaleTo"))->getSelectedState() == true)
//ScaleTo:缩放大小至绝对倍数,第一个参数为动作时间,第二个参数为缩放到的绝对倍数,
_spritePerson->runAction(ScaleTo::create(1.0,2.0));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_ScaleBy"))->getSelectedState() == true)
//ScaleBy:缩放大小至相对倍数,第一个参数为动作时间,第二个参数为缩放到的相对倍数,
_spritePerson->runAction(ScaleBy::create(1.0, 1.2));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_SkewTo"))->getSelectedState() == true)
//SkewTo:倾斜至绝对角度,第一个参数为动作时间,第二个参数为x轴倾斜角度,第三个参数为y轴倾斜角度
_spritePerson->runAction(SkewTo::create(1.0, 45, 0));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_SkewBy"))->getSelectedState() == true)
//SkewBy:倾斜至相对角度,第一个参数为动作时间,第二个参数为x轴倾斜角度,第三个参数为y轴倾斜角度
_spritePerson->runAction(SkewBy::create(1.0, 15, 0));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_RotateTo"))->getSelectedState() == true)
//RotateTo:旋转至绝对角度,第一个参数为动作时间,以第二个参数为x轴旋转角度,第三个参数为y轴旋转角度
_spritePerson->runAction(RotateTo::create(1.0, 90, 90));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_RotateBy"))->getSelectedState() == true)
//RotateBy:旋转至相对角度,第一个参数为动作时间,以第二个参数为x轴旋转角度,第三个参数为y轴旋转角度
_spritePerson->runAction(RotateBy::create(1.0, 15, 15));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_JumpTo"))->getSelectedState() == true)
//JumpTo:跳跃至绝对终点,第一个参数为动作时间,以第二个参数为终点绝对坐标,第三个参数为跳跃高度,第四个参数为跳跃次数
_spritePerson->runAction(JumpTo::create(1.0, Vec2(600,200), 300,4));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_JumpBy"))->getSelectedState() == true)
//JumpBy:跳跃至绝对终点,第一个参数为动作时间,以第二个参数为终点相对坐标,第三个参数为跳跃高度,第四个参数为跳跃次数
_spritePerson->runAction(JumpBy::create(1.0, Vec2(100, 0), 200, 1));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_BezierTo"))->getSelectedState() == true)
{
//BezierTo:绝对贝塞尔曲线(轨迹S型曲线),第一个参数为动作时间,以第二个参数为类型ccBezierConfig结构体,包括波峰顶点,波谷顶点,最终顶点
ccBezierConfig bezierConfig;
bezierConfig.controlPoint_1 = Vec2(800, 300);
bezierConfig.controlPoint_2 = Vec2(180, 400);
bezierConfig.endPosition = Vec2(890, 490);
_spritePerson->runAction(BezierTo::create(1.5, bezierConfig));
}
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_BezierBy"))->getSelectedState() == true)
{
//BezierBy:相对贝塞尔曲线(轨迹S型曲线),第一个参数为动作时间,以第二个参数为类型ccBezierConfig结构体,包括波峰顶点,波谷顶点,最终顶点
ccBezierConfig bezierConfig;
bezierConfig.controlPoint_1 = Vec2(280, 480);
bezierConfig.controlPoint_2 = Vec2(340, 40);
bezierConfig.endPosition = Vec2(400, 200);
_spritePerson->runAction(BezierBy::create(1.5, bezierConfig));//
}
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_FadeIn"))->getSelectedState() == true)
//FadeIn:逐渐显示(透明度逐渐升高),参数为动作时间
_spritePerson->runAction(FadeIn::create(1.5));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_FadeOut"))->getSelectedState() == true)
//FadeOut:逐渐消失(透明度逐渐降低),参数为动作时间
_spritePerson->runAction(FadeOut::create(1.5));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_Blink"))->getSelectedState() == true)
//Blink:闪烁若干次,第一个参数为动作时间,第二个参数为闪烁次数
_spritePerson->runAction(Blink::create(2.0,3));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_TintTo"))->getSelectedState() == true)
//TintTo:变化至绝对颜色,第一个参数为动作时间,第2,3,4个参数为R,G,B颜色分量
_spritePerson->runAction(TintTo::create(1,255,0,0));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_TintBy"))->getSelectedState() == true)
//TintBy:变化至相对颜色,第一个参数为动作时间,第2,3,4个参数为R,G,B颜色分量
_spritePerson->runAction(TintBy::create(1, 255, 255, 255));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_Repeat"))->getSelectedState() == true)
//Repeat:重复动作若干次,第一个参数为重复的动作,第二个参数为重复的次数
_spritePerson->runAction(Repeat::create(RotateBy::create(0.5,90),3));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_RepeatForever"))->getSelectedState() == true)
//RepeatForever:无限重复动作,参数为重复的动作
_spritePerson->runAction(RepeatForever::create(RotateBy::create(0.5, 90)));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_Sequence"))->getSelectedState() == true)
{
//Sequence:顺序执行多个动作,注意最后的参数必须为NULL,参数依次为第一个,第二个,第N个动作,NULL
JumpBy* a1 = JumpBy::create(1.0, Vec2(100, 0), 200, 1);
ScaleBy* a2 = ScaleBy::create(1.0, 2.0);
_spritePerson->runAction(Sequence::create(a1, a2, NULL));
}
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_Spawn"))->getSelectedState() == true)
{
//Spawn:同时执行多个动作,注意最后的参数必须为NULL,参数依次为第一个,第二个,第N个动作,NULL
JumpBy* a1 = JumpBy::create(1.0, Vec2(100, 0), 200, 1);
ScaleBy* a2 = ScaleBy::create(1.0, 2.0);
_spritePerson->runAction(Spawn::create(a1, a2, NULL));
}
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_OrbitCamera"))->getSelectedState() == true)
{
//OrbitCamera:动作执行对象绕Z轴旋转动作,第一个参数为动作时间,后面参数为和球坐标相关的一些半径,夹角,具体可参考头文件中的注释说明
JumpBy* a1 = JumpBy::create(1.0, Vec2(100, 0), 200, 1);
ScaleBy* a2 = ScaleBy::create(1.0, 2.0);
_spritePerson->runAction(OrbitCamera::create(3, 100, 200, 0, 180, 0, 0));
}
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_CardinalSplineTo"))->getSelectedState() == true)
{
//CardinalSplineTo:绝对样条曲线,第一个参数为动作时间,第二个参数为经过点组成的数组,第三个参数为拉力系数
PointArray* arr = PointArray::create(10);
arr->addControlPoint(Point(0, 0));
arr->addControlPoint(Point(100, 400));
arr->addControlPoint(Point(300, 50));
arr->addControlPoint(Point(500, 600));
_spritePerson->runAction(CardinalSplineTo::create(2, arr, 1.0));
}
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_CardinalSplineBy"))->getSelectedState() == true)
{
//CardinalSplineTo:相对样条曲线,第一个参数为动作时间,第二个参数为经过点组成的数组,第三个参数为拉力系数
PointArray* arr = PointArray::create(10);
arr->addControlPoint(Point(0, 0));
arr->addControlPoint(Point(100, 400));
arr->addControlPoint(Point(300, 50));
arr->addControlPoint(Point(500, 600));
_spritePerson->runAction(CardinalSplineBy::create(2, arr, 0));//
}
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_EaseExponentialIn"))->getSelectedState() == true)
//Ease开头的动作由很多,作用都是改变参数动作的速度,由in结尾的加速度由低变高,out结尾的加速度由高变低,inOut结尾的加速度由低变高再变低,具体可参考头文件中的注释说明
//EaseExponentialIn:速度缓冲动作,加速度由低变高,增幅剧烈,参数为动作对象
_spritePerson->runAction(EaseExponentialIn::create(JumpBy::create(1.0, Vec2(100, 0), 300, 1)));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_EaseExponentialOut"))->getSelectedState() == true)
//EaseExponentialOut:速度缓冲动作,加速度由高变低,增幅剧烈,参数为动作对象
_spritePerson->runAction(EaseExponentialOut::create(JumpBy::create(1.0, Vec2(100, 0), 300, 1)));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_EaseExponentialInOut"))->getSelectedState() == true)
//EaseExponentialInOut:速度缓冲动作,加速度由低变高再变低,增幅剧烈,参数为动作对象
_spritePerson->runAction(EaseExponentialInOut::create(JumpBy::create(1.0, Vec2(100, 0), 300, 1)));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_EaseIn"))->getSelectedState() == true)
//EaseIn:速度缓冲动作,加速度由低变高,增幅平滑,参数为动作对象
_spritePerson->runAction(EaseIn::create(JumpBy::create(1.0, Vec2(100, 0), 300, 1), 2.0));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_EaseOut"))->getSelectedState() == true)
//EaseOut:速度缓冲动作,加速度由高变低,增幅平滑,参数为动作对象
_spritePerson->runAction(EaseOut::create(JumpBy::create(1.0, Vec2(100, 0), 300, 1), 2.0));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_EaseInOut"))->getSelectedState() == true)
//EaseInOut:速度缓冲动作,加速度由低变高再变低,增幅平滑,参数为动作对象
_spritePerson->runAction(EaseInOut::create(JumpBy::create(1.0, Vec2(100, 0), 300, 1), 2.0));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_Follow"))->getSelectedState() == true)
//Follow:摄像机跟随动作,参数为跟随目标
this->runAction(Follow::create(_spritePerson));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_Speed"))->getSelectedState() == true)
//Speed:改变参数动作的速率,第一个参数为动作对象,第二个参数为速率系数,大于1加速,小于1减速
_spritePerson->runAction(Speed::create(MoveBy::create(1.5, Vec2(400, 0)),2.0));
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_DelayTime"))->getSelectedState() == true)
{
//DelayTime:延时动作,实质为一个空动作,经常用于动作序列中,参数为延迟时间
DelayTime *d1 = DelayTime::create(1.0);
JumpBy* a1 = JumpBy::create(1.0, Vec2(100, 0), 200, 1);
ScaleBy* a2 = ScaleBy::create(1.0, 2.0);
_spritePerson->runAction(Sequence::create(a1, d1, a2, NULL));
}
if (((CheckBox*)Helper::seekWidgetByName((Widget*)_rootNode, "CB_CallFunc"))->getSelectedState() == true)
{
//CallFunc:回调动作,一般用于动作序列中,在前面动作结束后做些特定的事比如播放声音,显示提示等,根据参数不同有子类CallFuncN,__CCCallFuncND,__CCCallFuncO,
CallFunc* callFunc = CallFunc::create(CC_CALLBACK_0(HelloWorld::callBack0, this));
JumpBy* j2 = JumpBy::create(1.0, Vec2(100, 0), 300, 1);
_spritePerson->runAction(Sequence::create(j2, callFunc, NULL));
}
}
void HelloWorld::callBack0()
{
_label->setString("jump finished");
}
void HelloWorld::btnResetCB(Ref* sender)
{
_label->setString("jump unfinished");
_spritePerson->setColor(Color3B(255, 255, 255));
_spritePerson->setOpacity(255);
_spritePerson->setRotation(0);
_spritePerson->setSkewX(0);
_spritePerson->setSkewY(0);
_spritePerson->setScale(1.0);
_spritePerson->setVisible(true);
_spritePerson->setFlippedX(false);
_spritePerson->setFlippedY(false);
_spritePerson->stopAllActions();
_spritePerson->setPosition(200, 200);
}
接下来我们通过跟踪源码,理解下为什么调用runAction()后,会产生动画效果
Action * Node::runAction(Action* action)
{
CCASSERT(action != nullptr, "Argument must be non-nil");
_actionManager->addAction(action, this, !_running);
return action;
}
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!");
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);
}
bool Director::init(void)
{
//...
_scheduler = new (std::nothrow) Scheduler();
// action manager
_actionManager = new (std::nothrow) ActionManager();
_scheduler->scheduleUpdate(_actionManager, Scheduler::PRIORITY_SYSTEM, false);//执行完这句后每一帧都会调用ActionManager的update()
//...
return true;
}
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 = (Action*)_currentTarget->actions->arr[_currentTarget->actionIndex];
if (_currentTarget->currentAction == nullptr)
{
continue;
}
_currentTarget->currentActionSalvaged = false;
//关键点,执行当前动作的step()
_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);
}
}
// issue #635
_currentTarget = nullptr;
}
//接下来以MoveBy为例,分析step()执行内容,发现MoveBy中没有step(),则step()继承自基类ActionInterval
void ActionInterval::step(float dt)
{
if (_firstTick)
{
_firstTick = false;
_elapsed = 0;
}
else
{
_elapsed += dt;
}
float updateDt = MAX(0, // needed for rewind. elapsed could be negative
MIN(1, _elapsed /
MAX(_duration, FLT_EPSILON) // division by 0
)
);
if (sendUpdateEventToScript(updateDt, this)) return;
this->update(updateDt);//此处调用子类的update()
}
//接下来在MoveBy的update()中,通过每一帧调用setPosition3D(),不断改变自己的坐标属性,实现平移效果.
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的实现和MoveBy类似,主要区别在于update()中改变的属性不同