转 cocos2d-x 贝塞尔曲线之游戏应用

一.贝赛尔曲线简介
贝塞尔曲线是应用于二维图形应用程序的数学曲线。曲线的定义有四个点:起始点、终止点(也称锚点)以及两个相互分离的中间点。滑动两个中间点,贝塞尔曲线的形状会发生变化 
转 cocos2d-x 贝塞尔曲线之游戏应用
p0起点,p3是终点,p1,p2是控制点
http://en.wikipedia.org/wiki/B%C3%A9zier_curve 

二.游戏应用

我们可能需要在游戏中模拟导弹或箭的移动轨迹,用才cocos2d-x下的bezier可以轻松的模拟出来
cocos2d-x下为我们提供了两个action CCBezierBy和CCBezierTo,使用也很简单,只需要填充结构体:

        ccBezierConfig tr0;    
        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); 是相对于起始点的偏移

三. 游戏实例
我们来模拟一个射箭游戏,屏幕中心点是一个人,点击屏幕任何位置,会朝这个点击位置射一支箭,箭到达指定点后抖动一会(播放一个抖动动画),然后消失

转 cocos2d-x 贝塞尔曲线之游戏应用
p0是箭的发射位置,p3是箭的目标点,p1,p2是控制飞行轨迹的控制点,p1p0p3组成了箭的角度,假设我们有十六个方向的箭,即360.f/16= 22.5度一个方向

转 cocos2d-x 贝塞尔曲线之游戏应用


第一步:存贮箭的飞行动画和抖动动画到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();
    }

    // 同理存贮箭的抖动动画
    转 cocos2d-x 贝塞尔曲线之游戏应用.




第二步:获得射箭目标点,用贝塞尔曲线模拟箭的飞行轨迹
      首先,要求出箭的射击方向,从CCAnimationCatche中取出对应的飞行动画,我们有16个方向的动画,下面是写的一个简单的求射箭角度和方向的函数:

#define EPSION 0.0001f 
#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->stopAllActions();


然后播放抖动动画,抖动动画再加一个回调函数

CCAnimate* animate = CCAnimate::actionWithAnimation(animation);
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);


第四步:播放完毕,清除箭
在上一阶段回调中删除自己

removeFromParentAndCleanup(true);


-------------------------------------------------------------------------------
调试帮助
1.光看是不够的,要看箭的飞行轨迹,还是要画出来,在CCNode的派生类中重载draw()函数,在里面画贝塞尔曲线

void ArrowDirection::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

相关推荐