转 cocos2d-x 贝塞尔曲线之游戏应用
一.贝赛尔曲线简介
贝塞尔曲线是应用于二维图形应用程序的数学曲线。曲线的定义有四个点:起始点、终止点(也称锚点)以及两个相互分离的中间点。滑动两个中间点,贝塞尔曲线的形状会发生变化
p0起点,p3是终点,p1,p2是控制点
http://en.wikipedia.org/wiki/B%C3%A9zier_curve
二.游戏应用
我们可能需要在游戏中模拟导弹或箭的移动轨迹,用才cocos2d-x下的bezier可以轻松的模拟出来
cocos2d-x下为我们提供了两个action CCBezierBy和CCBezierTo,使用也很简单,只需要填充结构体:
tr0.endPosition=ccp(280, 240);
tr0.controlPoint_1=ccp(40, 400);
tr0.controlPoint_2=ccp(280, 80);
CCActionInterval* bezierForward = CCBezierBy::create(1.f, tr0);
我们只需要提供两个控制点和一个终点位置就可以了,这里要注意的是
CCBezier这个action是以当前位置为起始点的,两个控制点和终点都是相对于起始点的偏移值
如:tr0.endPosition = ccp(280,240); 是相对于起始点的偏移
三. 游戏实例
我们来模拟一个射箭游戏,屏幕中心点是一个人,点击屏幕任何位置,会朝这个点击位置射一支箭,箭到达指定点后抖动一会(播放一个抖动动画),然后消失
p0是箭的发射位置,p3是箭的目标点,p1,p2是控制飞行轨迹的控制点,p1p0p3组成了箭的角度,假设我们有十六个方向的箭,即360.f/16= 22.5度一个方向
第一步:存贮箭的飞行动画和抖动动画到CCAnimationCatch中,后面播放动画时通过名字来获得动画
for (int i = 0; i < mDirections; ++i)
{
// 箭飞行动画
CCArray* spriteFramesArray = CCArray::create(flyFrames);
// 从图片中每一帧的位置来生成CCSpriteFrame
for ( int j = 0; j < flyFrames; ++j )
{
CCSpriteFrame* frame = CCSpriteFrame::create(pTexture,
CCRectMake(j * frame_width, i * frame_height, frame_width, frame_height));
spriteFramesArray->addObject(frame);
}
//以键值形式存贮动画到CCAnimationCatch中
float frameTime = 1.f / (mSpeed * flyFrames);
CCAnimation* animation = CCAnimation::create(spriteFramesArray, frameTime);
String name = getArrowAnimateName(i);
CCAnimationCache::sharedAnimationCache()->addAnimation(animation, name.c_str());
spriteFramesArray->removeAllObjects();
spriteFramesArray->release();
}
// 同理存贮箭的抖动动画
.
第二步:获得射箭目标点,用贝塞尔曲线模拟箭的飞行轨迹
首先,要求出箭的射击方向,从CCAnimationCatche中取出对应的飞行动画,我们有16个方向的动画,下面是写的一个简单的求射箭角度和方向的函数:
#define IS_EQUAL(val1, val2) (fabs((val1) - (val2)) <= EPSION)
const int mDirections = 16;
/** 更新方向,传入起始点和终止点,利用actan来获得射箭的弧度,然后转换为角度
*/
int ArrowDirection::updateDirection(const cocos2d::CCPoint& ptRole, const cocos2d::CCPoint& ptTarget)
{
CCPoint sub = ccpSub(ptTarget, ptRole);
if (IS_EQUAL(sub.x, 0.f) && IS_EQUAL(sub.y, 0.f))
return -1;
if (IS_EQUAL(sub.y, 0.f) && sub.x > 0)
{
mDegree = 90.f;
}
else if (IS_EQUAL(sub.y, 0.f) && sub.x < 0)
{
mDegree = 180.f;
}
else
{
// 弧度转角度
float radians = atanf(sub.x/sub.y);
mDegree = CC_RADIANS_TO_DEGREES(radians);
if (sub.x >= 0 && sub.y >= 0 ) // 第一象限
{
}
else if (sub.x >= 0 && sub.y <= 0) // 第二象限
{
mDegree += 180.f;
}
else if (sub.x <= 0 && sub.y <= 0) // 第三象限
{
mDegree += 180.f;
}
else // 第四象限
{
mDegree += 360.f;
}
}
if (mDegree < 0.f)
mDegree = 0.f;
if (mDegree > 360.f)
mDegree = 0.f;
float single = (float)360 / 16;
for (int i = 0; i < mDirections; ++i)
{
if (mDegree >= i * single && mDegree <= (i+1) * single)
return mDirections;
}
return mDirections - 1;
}
有了角度和动画就好办了,我们已经知道了目标点,哈哈,可以让箭一边播放飞行动画一边沿着贝塞尔曲线移动就OK了
String name = getArrowAnimateName(dir);
cocos2d::CCAnimation* animation = CCAnimationCache::sharedAnimationCache()->animationByName(name.c_str());
CCAnimate* animate = CCAnimate::actionWithAnimation(animation);
mSprite->runAction(animate);
// 填充bezier
ccBezierConfig cfg;
cfg.controlPoint_1 = ccp(0, control_height);
cfg.controlPoint_2 = ccp(ptRelativeTarget.x, ptRelativeTarget.y + control_height);
cfg.endPosition = ptRelativeTarget;
// 沿着贝塞尔曲线移动
CCActionInterval* bezierForward = CCBezierBy::create(2.f, cfg);
CCActionInterval* seq = (CCActionInterval*)CCSequence::create(bezierForward,
CCCallFuncND::create(this, callfuncND_selector(ArrowDirection::arrowFlyOverCallBack), this),
NULL);
mSprite->runAction(seq);
我们为action序列添加了回调函数ArrowDirection::arrowFlyOverCallBack 箭飞行完毕后进入下一阶段
第三步:箭到达目标点,播放抖动动画
在上一阶段的回调函数中先停止所有动画
然后播放抖动动画,抖动动画再加一个回调函数
sprite->runAction(CCRepeatForever::create(animate));
CCActionInterval* delay = CCDelayTime::create(pArrowDir->getArrowShakeTime());
CCActionInterval* seq = (CCActionInterval*)CCSequence::create(delay,
CCCallFuncND::create(pArrowDir, callfuncND_selector(ArrowDirection::arrowDisappearedCallBack), pArrowDir),
NULL);
sprite->runAction(seq);
第四步:播放完毕,清除箭
在上一阶段回调中删除自己
-------------------------------------------------------------------------------
调试帮助:
1.光看是不够的,要看箭的飞行轨迹,还是要画出来,在CCNode的派生类中重载draw()函数,在里面画贝塞尔曲线
{
if (mDrawBezier)
{
CCPoint control1 = ccpAdd(mBezierStartPoint, mBezierConfig.controlPoint_1);
CCPoint control2 = ccpAdd(mBezierStartPoint, mBezierConfig.controlPoint_2);
CCPoint end = ccpAdd(mBezierStartPoint, mBezierConfig.endPosition);
// 画控制点
ccDrawLine(mBezierStartPoint, control1);
ccDrawLine(control2, end);
// 画贝塞尔曲线
ccDrawCubicBezier(mBezierStartPoint, mBezierConfig.controlPoint_1, mBezierConfig.controlPoint_2, mBezierConfig.endPosition, 100);
}
}
更多画法参考cocos2d-x粒子DrawPrimitivesTest
原文地址:http://www.cppblog.com/wc250en007/archive/2012/08/24/188048.html
相关推荐
进入场景而且过渡动画结束时候触发。提示 GameScene场景中的继承于节点,这些生命周期事件根本上是从Node继承而来。事实上所有Node对象都有这些事件,具体实现代码与GameScene场景类似。