cocos2dx学习笔记之常用动作类Action详解

本文深入解析了 Cocos2d-x 游戏引擎中的动作系统,介绍了常用的瞬时动作类和延时动作类,并通过代码示例展示了如何使用这些动作来实现角色的移动、旋转、缩放等效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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()中改变的属性不同


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ellis1970

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值