由于项目需要实现一个圆周运动,而Cocos里边没有提供,所以就自己实现一个吧。在实现之前需要先明白原理,圆周运动作为持续性动作需要继承自ActionInterval,动作的本质就是不断的改变节点的属性,而圆周运动需要改变的是节点的位置属性,改变属性的代码需要写到update函数中,也就是在每帧不断的改变节点的坐标,这样看起来就是做圆周运动了。而update函数的调用是由动作管理器统一调用的,不需要我们手动的调用,继承ActionInterval还有一些需要覆写的方法,比如动作的反动作,动作的克隆。那么如何改变节点的位置从而让节点做圆周运动呢,我们需要知道做圆周运动的半径,原点,某一时刻的弧度数,然后使用公式x=r*sinα,y=r*cosα。怪不得人家说学编程要学好数学呢,现在知道了吧。我将代码的主要含义都加了注释说明,所以把最后的代码贴上来吧!

#ifndef __Test2__CircleBy__
#define __Test2__CircleBy__

#include "cocos2d.h"

USING_NS_CC;

//圆周运动
class CircleBy : public ActionInterval
{
public:
    //tm代表圆周运动的时间,circleCenter代表圆心,半径是根据精灵当前的位置和圆心的距离计算出来的,不需要作为参数传递进去,第三个参数代表角度数,来确定当前节点在圆周运动中的位置
    static CircleBy * create(float tm,Point circleCenter,float randiansValue);
    //初始化参数
    bool init(float tm,Point circleCenter,float randiansValue);
    //从父类继承需要覆写的方法
    virtual void update(float);
    //确定执行动作的节点以后调用的方法
    virtual void startWithTarget(Node *target);
    //动作翻转
    virtual CircleBy * reverse() const;
    //克隆一个相同的动作
    virtual CircleBy * clone() const;
    //设置旋转多少度,默认做的圆周运动是一个整圆
    void setRadians(float radians);
private:
    //圆心
    Point _circleCenter;
    //半径
    float _radius;
    //每帧需要转过的弧度数
    float _radians;
    //初始的角度数
    float _radiansValue;
    //旋转角度,默认是360,也就是做一个完整的圆周运动
    float _maxRadians;
    //刷新次数
    int _times;
};

#endif /* defined(__Test2__CircleBy__) */
#include "CircleBy.h"

CircleBy * CircleBy::create(float tm,Point circleCenter,float randiansValue)
{
    CircleBy * _circle = new CircleBy();
    _circle->init(tm,circleCenter,randiansValue);
    _circle->autorelease();

    return _circle;
}

bool CircleBy::init(float tm,Point circleCenter,float randiansValue)
{
    //动作执行时间
    if(initWithDuration(tm))
    {
        //初始化圆心和半径
        this->_circleCenter = circleCenter;
        _radius = sqrt(pow(_circleCenter.x,2)+pow(_circleCenter.y,2));
        //每帧需要转过的弧度
        this->_radians = (Director::getInstance()->getAnimationInterval())*2*M_PI/tm;
        //如果第三个参数小于零,代表做的是逆时针的圆周运动
        if(randiansValue<0)
            _radians = -_radians;
        //刷新次数
        this->_times = 1;
        //初始弧度数
        this->_radiansValue = randiansValue/360*2*M_PI;
        //旋转的角度,默认为360
        this->_maxRadians = 2*M_PI;

        return true;
    }

    return false;
}

void CircleBy::startWithTarget(Node * target)
{
    ActionInterval::startWithTarget(target);
    //根据执行动作精灵的坐标重新初始化圆心,repeat的时候会多次调用该函数,所以这里判断是否是第一次
    if(_times == 1)
        _circleCenter = _circleCenter+target->getPosition();
}

void CircleBy::setRadians(float radians)
{
    auto val = radians/360*2*M_PI;
    this->_radians = _radians/_radians*(Director::getInstance()->getAnimationInterval())*val/_duration;
    //如果第三个参数小于零,代表做的是逆时针的圆周运动
    if(_radiansValue<0)
        _radians = -_radians;
}

//动作管理器调用update函数,每帧刷新坐标
void CircleBy::update(float)
{
    auto radians = _radiansValue;
    //如果radians小于零,需要做逆时针的圆周运动
    if(radians<0)
        radians = -radians;
    radians = _radians*_times+radians;

    auto x = _radius*sin(radians);
    auto y = _radius*cos(radians);

    _target->setPosition(Point(x+_circleCenter.x,y+_circleCenter.y));

    _times++;

    /*以下的代码将做圆周运动的轨迹绘制了出来,必要的时候可以删除掉*/

    //创建一个drawNode用来绘图
    auto draw = DrawNode::create();
    //将DrawNode添加到父节点中,这样绘制的位置就和执行动作的sprite在同一个节点坐标系中
    _target->getParent()->addChild(draw);
    //画点
    draw->drawDot(_target->getPosition(),1,Color4F(1,1,1,1));
}

//反动作,逆时针方向转动
CircleBy* CircleBy::reverse() const
{
    return CircleBy::create(_duration,_circleCenter,-_radiansValue*360/(2*M_PI));
}

//克隆一个相同的动作,init传入的时候转化为角度
CircleBy* CircleBy::clone() const
{
	auto a = new CircleBy();
	a->init(_duration,_circleCenter,_radiansValue*360/(2*M_PI));
	a->autorelease();
	return a;
}

测试代码如下,很多图案都是由圆周运动组成的,大家可以自由发挥想象。

//创建一个精灵,执行圆周运动
    auto sprite = Sprite::create("car.png");
    sprite->setPosition(Point(size.width*0.26,size.height*0.53));
    sprite->setRotation(90);
    this->addChild(sprite);

    /*测试圆周运动是否正确*/

    //设置圆心为相对于当前精灵(100,0)的位置处,初始化传入270度,让精灵位于9点钟方向开始做顺时针运动
    auto circle = CircleBy::create(2,Point(100,0),270);
    //设置精灵旋转的角度是90度
    circle->setRadians(90);

    //设置动作为前一个动作的反动作
    auto circle2 = circle->reverse();
    //设置旋转的角度为180度
    circle2->setRadians(180);

    //克隆第二个动作
    auto circle3 = circle2->clone();
    auto action = Sequence::create(circle,circle2,circle3,NULL);
    sprite->runAction(action);

    /*以下的代码为了测试,必要的时候删除*/

    auto draw = DrawNode::create();
    //将DrawNode添加到父节点中,这样绘制的位置就和执行动作的sprite在同一个节点坐标系中
    addChild(draw);
    //画精灵的起始点坐标
    draw->drawDot(sprite->getPosition(),10,Color4F(0.5,0.5,1,1));
    //画圆心
    draw->drawDot(Point(100,0)+sprite->getPosition(),10,Color4F(1,0.5,1,1));

自定义圆周运动

不断的做repeat动作,测试结果如下,代码自己写。

自定义圆周运动

--------------------------------------------更新--------------------------------------------

上边的代码在用的时候发现了些bug,所以重新写了一下,这次的接口很简单,只需要传入时间,圆心以及希望做圆周运动的度数,至于半径和起始的弧度数是通过计算得到的。以下的代码注释写的很简单,但是思路和上边是相同的,所以如果是看思路,上边的代码可能更加详细。最后,我实现这个运动肯定是不健壮的,不过是可以满足我使用需求的,所以自己也没有往更好更健壮的方面写,大家如果有更健壮的代码记得给我共享啊。

#ifndef __Test2__CircleBy__
#define __Test2__CircleBy__

#include "cocos2d.h"

USING_NS_CC;

//圆周运动
class CircleBy : public ActionInterval
{
public:
    //时间,圆心,一共需要旋转的角度
    static CircleBy * create(float tm,Point circleCenter,float numDegree);
    bool init(float tm,Point circleCenter,float degree);
    virtual void update(float);
    virtual void startWithTarget(Node *target);
    virtual CircleBy * reverse() const;
    virtual CircleBy * clone() const;
private:
    //圆心
    Point _circleCenter;
    Point _originCenter;
    //半径
    float _radius;
    //一共需要转的弧度
    float _numDegree;
    //每帧需要转过的弧度数
    float _degree;
    //起始弧度数
    float _beginDegree;
    //刷新次数
    int _times;
};

#endif /* defined(__Test2__CircleBy__) */
#include "CircleBy.h"

CircleBy * CircleBy::create(float tm,Point circleCenter,float randiansValue)
{
    CircleBy * _circle = new CircleBy();
    _circle->init(tm,circleCenter,randiansValue);
    _circle->autorelease();

    return _circle;
}

bool CircleBy::init(float tm,Point circleCenter,float numDegree)
{
    //动作执行时间
    if(initWithDuration(tm))
    {
        //圆心
        _originCenter = circleCenter;
        //半径
        _radius = sqrt(pow(_originCenter.x,2)+pow(_originCenter.y,2));
        //总共需要转过的弧度数
        _numDegree = -numDegree/360*2*M_PI;
        //每帧需要转过的弧度
        _degree = (Director::getInstance()->getAnimationInterval())*_numDegree/tm;
        //刷新次数
        _times = 1;
        //计算起始弧度数
        _beginDegree = M_PI+atan2f(_originCenter.y,_originCenter.x);

        return true;
    }

    return false;
}

void CircleBy::startWithTarget(Node * target)
{
    ActionInterval::startWithTarget(target);

    if(_times == 1 && (int)_numDegree%360 == 0)
        _circleCenter = _originCenter+target->getPosition();
    else
        _circleCenter = _originCenter+target->getPosition();
    _times = 1;
}

//动作管理器调用update函数,每帧刷新坐标
void CircleBy::update(float)
{
    //弧度数
    auto degree = _degree*_times+_beginDegree;
    auto x = _radius*cos(degree);
    auto y = _radius*sin(degree);

    _target->setPosition(Point(x+_circleCenter.x,y+_circleCenter.y));

    _times++;

    /*以下的代码将做圆周运动的轨迹绘制了出来,必要的时候可以删除掉*/
//    auto draw = DrawNode::create();
//    _target->getParent()->addChild(draw);
//    draw->drawDot(_target->getPosition(),1,Color4F(1,1,1,1));
}

CircleBy* CircleBy::reverse() const
{
    return CircleBy::create(_duration,_originCenter,_numDegree*360/(2*M_PI));
}

CircleBy* CircleBy::clone() const
{
	CircleBy * _circle = new CircleBy();
    _circle->init(_duration,_originCenter,-_numDegree*360/(2*M_PI));
    _circle->autorelease();
    return _circle;
}