How to make sprite move in a sinusoidal in cocos2d?
Asked Answered
B

3

5

I have a sprite (paper plane, for example). I'd like make it move like in the picture below. I can use lots of MoveTo and RotateBy actions to define the path by points, but it seems a bad idea to me. How can it be implemented ?

enter image description here

Bothersome answered 6/4, 2014 at 11:8 Comment(1)
possible duplicate of Cocos2d - move a sprite from point A to point B in a sine wave motionChary
S
6

I thought it might be good to post an answer that showed the basics of how the update would work if you had explicit control over the sprite.

I was not sure if you were using Cocos2d or Cocos2d-X, but the technique applies in either case. The code is in C++ using Cocos2d-x.

The idea is that, based on time, you (manually) update the position of the sprite. The position of the sprite at any time is determined by the number of seconds since the animation begun. The line nominally follows a straight path from (x0,y0) to (x1,y0). You can then project the line onto a line drawn at any angle using some trigonometry. This gives the ability to have a sinusoidal path along any direction.

Here is the basic code (the main work is done in UpdateAnimation()):

// This assumes the frame rate is relatively constant
// at 60 fps.
const double SECONDS_PER_TICK = 1.0/60;
const double DURATION = 8.0;     // Seconds for total animation.
const double X_START = 100;      // Pixels
const double Y_START = 200;      // Pixels
const double X_STOP = 800;      // Pixels
const double X_SPEED = (X_STOP-X_START)/DURATION;
const double Y_PERIOD = 4.0;     // Seconds for y cycle.
const double Y_HEIGHT = 100;
const double LAUNCH_ANGLE = M_PI/4; // Angle for line.
const CCPoint ANCHOR(X_START,Y_START);

CCPoint RotatePointAboutAnchor(const CCPoint& pt,double theta,const CCPoint& anchor)
{
   double xPrime = cos(theta) * (pt.x-anchor.x) - sin(theta) * (pt.y-anchor.y) + anchor.x;
   double yPrime = sin(theta) * (pt.x-anchor.x) + cos(theta) * (pt.y-anchor.y) + anchor.y;

   return CCPoint(xPrime,yPrime);
}


void HelloWorld::InitAnimation()
{
   _ticks = 0;
   _ticksTotal = DURATION/SECONDS_PER_TICK;
}

void HelloWorld::UpdateAnimation()
{
   if(_ticks <= _ticksTotal)
   {
      double seconds = _ticks*SECONDS_PER_TICK;
      double xPos = X_START + seconds*X_SPEED;
      double yPos = Y_START + Y_HEIGHT*sin(seconds*2*M_PI/Y_PERIOD);
      CCPoint pos = RotatePointAboutAnchor(CCPoint(xPos,yPos), LAUNCH_ANGLE, ANCHOR);
      // Set the position of the sprite
      _sprite->setPosition(pos);

      CCLOG("Tick: %d, Seconds: %5.2f, Position: (%f,%f)",_ticks,seconds,pos.x,pos.y);
      if(_ticks%10 == 0)
      {  // Add a trail
         CCSprite* marker = CCSprite::create("Icon-72.png");
         marker->setScale(0.1);
         marker->setPosition(_sprite->getPosition());
         marker->setZOrder(50);
         addChild(marker);
      }
      // Increment the ticks count for next time.
      _ticks++;
   }
}

void HelloWorld::draw()
{
   CCLayer::draw();
   CCPoint start;
   CCPoint stop;

   start = RotatePointAboutAnchor(CCPoint(X_START,Y_START), LAUNCH_ANGLE, ANCHOR);
   stop = RotatePointAboutAnchor(CCPoint(X_STOP,Y_START), LAUNCH_ANGLE, ANCHOR);
   ccDrawLine(start,stop);

   start = RotatePointAboutAnchor(CCPoint(X_START,Y_START+Y_HEIGHT), LAUNCH_ANGLE, ANCHOR);
   stop = RotatePointAboutAnchor(CCPoint(X_STOP,Y_START+Y_HEIGHT), LAUNCH_ANGLE, ANCHOR);
   ccDrawLine(start,stop);

   start = RotatePointAboutAnchor(CCPoint(X_START,Y_START-Y_HEIGHT), LAUNCH_ANGLE, ANCHOR);
   stop = RotatePointAboutAnchor(CCPoint(X_STOP,Y_START-Y_HEIGHT), LAUNCH_ANGLE, ANCHOR);
   ccDrawLine(start,stop);
}

void HelloWorld::onEnterTransitionDidFinish()
{
   InitAnimation();
   scheduleUpdate();
}

void HelloWorld::onExitTransitionDidStart()
{
   unscheduleUpdate();
}

void HelloWorld::update(float dt)
{
   UpdateAnimation();
}

I drew some markers to show the path and also drew the lines "around" the path that should be followed. Here is what it looks like:

enter image description here

You can change the LAUNCH_ANGLE as you like to make it move along different angles.

Obviously this is not production code, but it does demonstrate the idea that you can follow a sinusoidal path in any direction. You should encapsulate it into something more in line with your application.

The entire code base is available on git hub.

And there are more posts about stuff like this in this blog.

Synectics answered 6/4, 2014 at 13:21 Comment(2)
Do you have an example in Objective C?Bothersome
I don't but it would not be hard to translate the code...coco2d (objective-c) has the same basic functions and operations, they are just use objC instead of C++. All the "C" like stuff should port with out change.Synectics
K
2

There is an action for moving a sprite along a path and orienting to the path also. Not at my computer ATM but will try find it.

Here you go... https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SKAction_Ref/Reference/Reference.html#//apple_ref/doc/uid/TP40013017-CH1-SW3

Actually you could probably link together a repeating sequence moving up and down with a moving forward action and create sinusoidal movement that way.

Kinakinabalu answered 6/4, 2014 at 11:24 Comment(0)
U
0

Thank for your question! Sinusoidal cocos2d action below :)

class NDActionSineMoveBy : public ActionInterval
{
public:
    static NDActionSineMoveBy* create(float duration, float sines, float sineSize, const Vec2& deltaPosition);

    //
    // Overrides
    //
    virtual NDActionSineMoveBy* clone() const override;
    virtual NDActionSineMoveBy* reverse() const  override;
    virtual void startWithTarget(Node *target) override;
    /**
    * @param time in seconds
    */
    virtual void update(float time) override;

CC_CONSTRUCTOR_ACCESS:
    NDActionSineMoveBy() {}
    virtual ~NDActionSineMoveBy() {}

    /** initializes the action */
    bool initWithDuration(float duration, float sines, float sineSize, const Vec2& deltaPosition);

protected:
    Vec2 rotate(const Vec2 & point, float angle, const Vec2 & anchor);

protected:
    float _sines;
    float _sineSize;
    float _baseAngle;
    Vec2  _positionDelta;
    Vec2  _startPosition;
    Vec2  _previousPosition;

    float _currentAngle;
    float _distance;

private:
    CC_DISALLOW_COPY_AND_ASSIGN(NDActionSineMoveBy);
};

NDActionSineMoveBy* NDActionSineMoveBy::create(float duration, float sines, float sineSize, const Vec2& deltaPosition)
{
    NDActionSineMoveBy *ret = new (std::nothrow) NDActionSineMoveBy();

    if (ret && ret->initWithDuration(duration, sines, sineSize, deltaPosition))
    {
        ret->autorelease();
        return ret;
    }

    delete ret;
    return nullptr;
}

bool NDActionSineMoveBy::initWithDuration(float duration, float sines, float sineSize, const Vec2& deltaPosition)
{
    bool ret = false;
    if (ActionInterval::initWithDuration(duration))
    {
        _sines = sines;
        _sineSize = sineSize;
        _positionDelta = deltaPosition;

        _baseAngle = atan2f(_positionDelta.y, _positionDelta.x);
        _currentAngle = _sines * (M_PI * 2);

        ret = true;
    }
    return ret;
}

NDActionSineMoveBy* NDActionSineMoveBy::clone() const
{
    // no copy constructor
    return NDActionSineMoveBy::create(_duration, _sines, _sineSize, _positionDelta);
}

void NDActionSineMoveBy::startWithTarget(Node *target)
{
    ActionInterval::startWithTarget(target);
    _previousPosition = _startPosition = target->getPosition();
    _distance = _positionDelta.length();
}

NDActionSineMoveBy* NDActionSineMoveBy::reverse() const
{
    return NDActionSineMoveBy::create(_duration, _sines, _sineSize, -_positionDelta);
}

void NDActionSineMoveBy::update(float t)
{
    if (_target)
    {
        Vec2 newPos;
        newPos.x = _distance * t;
        newPos.y = sin(_currentAngle * t) * _sineSize;
        newPos = rotate(newPos, _baseAngle, Vec2::ZERO);

#if CC_ENABLE_STACKABLE_ACTIONS
        Vec2 currentPos = _target->getPosition();
        Vec2 diff = currentPos - _previousPosition;
        _startPosition = _startPosition + diff;

        newPos += _startPosition;

        _target->setPosition(newPos);
        _previousPosition = newPos;
#else
        newPos += _startPosition;
        _target->setPosition(newPos);
#endif // CC_ENABLE_STACKABLE_ACTIONS
    }
}

Vec2 NDActionSineMoveBy::rotate(const Vec2& point, float angle, const Vec2& anchor)
{
    Vec2 res;
    res.x = cos(angle) * (point.x - anchor.x) - sin(angle) * (point.y - anchor.y) + anchor.x;
    res.y = sin(angle) * (point.x - anchor.x) + cos(angle) * (point.y - anchor.y) + anchor.y;
    return res;
};

Uropod answered 4/5, 2018 at 16:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.