Custom cocos2d Action for rotating a sprite around an arbitrary point

Cocos2d is and excellent framework. It has saved me tons of time during my game projects. It offers almost everything I need. However, sometimes there are some features that are not supported by cocos2d.

This is the case of rotating a sprite around an arbitrary point. Rotation in cocos2d is based on the concept of anchor point. This is ok on the 99% of situations, probably. However, during the development of Muster my Monsters I need to perform rotations around arbitrary points. The idea is to have an sprite “orbiting” around another sprite or some defined point in the space.

cocos2d rotation around arbitrary point

Actually, you can achieve it using the concept of anchor points. You could define an anchor point that is out of the content size of the sprite (see the last example on this article). However, anchor points in cocos2d are normalized, so you need to figure out how to map your centre rotation point to normalized coordinates (from 0 to 1). This is far from being intuitive.

So, I decided to type some code to implement this functionality.

Rotating a point around another point

The general formula for rotating a point around another arbitrary point is the following:

p'x = cos(theta) * (px-ox) - sin(theta) * (py-oy) + ox
p'y = sin(theta) * (px-ox) + cos(theta) * (py-oy) + oy

Based on this simple formula we will create the following cocos2d extensions.

An objective-c category to rotate CCNodes around an arbitrary point

It is useful to extend the CCNode functionality to allow for this kind of rotation. But instead of subclassing CCNode I think it is better to create a category for it.

This is the header:

@interface CCNode (RotationAround)

/**  Rotates a CCNode object to a certain angle around
 a certain rotation point by modifying it's rotation
 attribute and position.
 */
-(void) rotateAroundPoint:(CGPoint)rotationPoint angle:(CGFloat)angle;

@end

And here we have the implementation:

#import "CCNode+RotationAround.h"

@implementation CCNode (RotationAround)

//p'x = cos(theta) * (px-ox) - sin(theta) * (py-oy) + ox
//p'y = sin(theta) * (px-ox) + cos(theta) * (py-oy) + oy

-(void) rotateAroundPoint:(CGPoint)rotationPoint angle:(CGFloat)angle {
    CGFloat x = cos(CC_DEGREES_TO_RADIANS(-angle)) * (self.position.x-rotationPoint.x) - sin(CC_DEGREES_TO_RADIANS(-angle)) * (self.position.y-rotationPoint.y) + rotationPoint.x;
    CGFloat y = sin(CC_DEGREES_TO_RADIANS(-angle)) * (self.position.x-rotationPoint.x) + cos(CC_DEGREES_TO_RADIANS(-angle)) * (self.position.y-rotationPoint.y) + rotationPoint.y;

    self.position = ccp(x, y);
    self.rotation = angle;
}

@end

Take into account that the sin and cos functions operate in radians while cocos2d uses degrees. This is why I use the CC_DEGREES_TO_RADIANS() macro.

Very easy and handy ;)

A CCAction to rotate a CCNode around an arbitrary point

But one of the greatest features of cocos2d is the Actions system. So, having and action to animate a sprite around an arbitrary point is very powerful. Here you have the header file:

#import <Foundation/Foundation.h>
#import "cocos2d.h"

/**  Rotates a CCNode object to a certain angle around
 a certain rotation point by modifying it's rotation
 attribute and position.
 The direction will be decided by the shortest angle.
 */
@interface CCRotateAroundTo : CCRotateTo {
    CGPoint rotationPoint_;
	CGPoint startPosition_;
}

/** creates the action */
+(id) actionWithDuration: (ccTime) t angle:(float) a rotationPoint:(CGPoint) rotationPoint;
/** initializes the action */
-(id) initWithDuration: (ccTime) t angle:(float) a rotationPoint:(CGPoint) rotationPoint;

@end

/** Rotates a CCNode object clockwise around a certain
 rotation point a number of degrees by modiying its
 rotation attribute and position.
 */
@interface CCRotateAroundBy : CCRotateBy {
    CGPoint rotationPoint_;
	CGPoint startPosition_;
}

/** creates the action */
+(id) actionWithDuration: (ccTime) t angle:(float) a rotationPoint:(CGPoint) rotationPoint;
/** initializes the action */
-(id) initWithDuration: (ccTime) t angle:(float) a rotationPoint:(CGPoint) rotationPoint;

@end

And the implementation file:

#import "CCRotateAround.h"

//p'x = cos(theta) * (px-ox) - sin(theta) * (py-oy) + ox
//p'y = sin(theta) * (px-ox) + cos(theta) * (py-oy) + oy

@implementation CCRotateAroundTo

+(id) actionWithDuration: (ccTime) t angle:(float) a rotationPoint:(CGPoint) rotationPoint
{
	return [[[self alloc] initWithDuration:t angle:a rotationPoint:rotationPoint] autorelease];
}

-(id) initWithDuration: (ccTime) t angle:(float) a rotationPoint:(CGPoint) rotationPoint
{
	if( (self=[super initWithDuration: t angle: a]) )
    {
        rotationPoint_ =  rotationPoint;
    }

	return self;
}

-(void) startWithTarget:(CCNode *)aTarget
{
	[super startWithTarget:aTarget];
	startPosition_ = [(CCNode*)target_ position];
}

-(void) update: (ccTime) t
{

    CGFloat x = cos(CC_DEGREES_TO_RADIANS(-diffAngle_*t)) * ((startPosition_.x)-rotationPoint_.x) - sin(CC_DEGREES_TO_RADIANS(-diffAngle_*t)) * ((startPosition_.y)-rotationPoint_.y) + rotationPoint_.x;
    CGFloat y = sin(CC_DEGREES_TO_RADIANS(-diffAngle_*t)) * ((startPosition_.x)-rotationPoint_.x) + cos(CC_DEGREES_TO_RADIANS(-diffAngle_*t)) * ((startPosition_.y)-rotationPoint_.y) + rotationPoint_.y;

    [target_ setPosition:ccp(x, y)];
	[target_ setRotation: (startAngle_ + diffAngle_ * t )];
}

@end

@implementation CCRotateAroundBy

+(id) actionWithDuration: (ccTime) t angle:(float) a rotationPoint:(CGPoint) rotationPoint
{
	return [[[self alloc] initWithDuration:t angle:a rotationPoint:rotationPoint] autorelease];
}

-(id) initWithDuration: (ccTime) t angle:(float) a rotationPoint:(CGPoint) rotationPoint
{
	if( (self=[super initWithDuration: t angle: a]) )
    {
        rotationPoint_ =  rotationPoint;
    }

	return self;
}

-(void) startWithTarget:(CCNode *)aTarget
{
	[super startWithTarget:aTarget];
	startPosition_ = [(CCNode*)target_ position];
}

-(void) update: (ccTime) t
{
    CGFloat x = cos(CC_DEGREES_TO_RADIANS(-angle_*t)) * ((startPosition_.x)-rotationPoint_.x) - sin(CC_DEGREES_TO_RADIANS(-angle_*t)) * ((startPosition_.y)-rotationPoint_.y) + rotationPoint_.x;
    CGFloat y = sin(CC_DEGREES_TO_RADIANS(-angle_*t)) * ((startPosition_.x)-rotationPoint_.x) + cos(CC_DEGREES_TO_RADIANS(-angle_*t)) * ((startPosition_.y)-rotationPoint_.y) + rotationPoint_.y;

    [target_ setPosition:ccp(x, y)];
	[target_ setRotation: (startAngle_ + angle_ * t )];
}

@end

Here we have the typical cocos2d two versions of the CCRotation action: CCRotateAroundTo and CCRotateAroundBy. Here you have a usage example:

CCRotateAroundBy *rotateAround = [CCRotateAroundBy actionWithDuration:1.0 angle:90 rotationPoint:screenCenter];
[sprite runAction:rotateAround];

As usual, very easy to use “:^]

Conclusion

It is indeed a very simple feature to implement but it was not initially included to cocos2d. So, here you have it! Enjoy!

HTH!

9 thoughts on “Custom cocos2d Action for rotating a sprite around an arbitrary point

  1. Pingback: Custom cocos2d Action for rotating a sprite around an arbitrary point [Toni Sala] (iDevBlogADay) | zdima.net

  2. Pingback: List of Open Source Cocos2d Projects, Extensions and Code Snippets | iUridium

  3. This code is intended for rotating Cocos2d Nodes. If you want to rotate a box2d body (or any other thing the world) you should use the general formula:

    p’x = cos(theta) * (px-ox) – sin(theta) * (py-oy) + ox
    p’y = sin(theta) * (px-ox) + cos(theta) * (py-oy) + oy

    Modify the position ‘x’ and ‘y’ of your box2d body according to this formula. This should do the work.

    HTH! “:^]

  4. 1.are you sure that you mentioned in this page what are theta and ox/oy?
    2.Wouldn’t it be easier to use BOX2d Revolute joints?, thank you

    • 1. Sorry, actually I didn’t. ‘theta’ is the angle you want to rotate your body. ‘p’ is the current position of your body and ‘o’ is the point around you want to rotate your body.

      2. It depends on the exact task you want to perform. The solution presented in this article is just to rotate sprites. No physic simulation is involved. If you need forces such as inertia, acceleration, friction, etc, you would better use Revolute joints.

      HTH

  5. Hey Toni. Great piece of code, but I think your missing bit of it

    – startAngle_ and diffAngle_ are never declared or assigned in CCRotateAroundTo
    – startAngle and angle_ are never declared or assigned in CCRotateAroundBy

    As well as declaring them in the .h, changed the initWithDuration in each to the following:

    CCRotateAroundTo

    -(id) initWithDuration: (ccTime) t angle:(float) a rotationPoint:(CGPoint) rotationPoint
    {
    if( (self=[super initWithDuration: t angle: a]) )
    {
    rotationPoint_ = rotationPoint;
    startAngle_ = a;
    diffAngle_ = a;
    }

    return self;
    }

    CCRoateAroundBy

    -(id) initWithDuration: (ccTime) t angle:(float) a rotationPoint:(CGPoint) rotationPoint
    {
    if( (self=[super initWithDuration: t angle: a]) )
    {
    rotationPoint_ = rotationPoint;
    angle_ = a;
    startAngle_ = a;
    }

    return self;
    }

Leave a Reply