Cocos2d-iPhone Sprite Rotation to an Arbitrary Point

I had some time during the Thanksgiving weekend to work on Dungeonators. I’m hoping to get an upgrade out to the App Store soon. One thing I needed //TODO: is refactor my rather poor implementation of rotating a sprite to face another sprite. My original code worked ok, in a roundabout way, but was ugly and mysterious.

(My iPhone game is full of ugly mysterious code: As I implement my ideas in code I first focus on getting it to work. Then, if I have time I go back and fix it to work cleanly. Generally cleanly means boiling the code down to the fewest number of lines possible, making it as functional as possible, and using functions from the operating system or open source code libraries as much as possible.)

I won’t show you my original sprite rotation implementation. Some things are too gross even for the Internet. Let’s just say it was written as if I was trying to remember high school trigonometry by trial and error.

Instead below is the final implementation, useful to any Cocos2d-iPhone programmer who wants a sprite to rotate to face an opponent at any point on the screen:

- (void) rotateSourceSprite:(CCSprite *)sourceSprite toFaceTargetSprite:(CCSprite *)targetSprite {

    // Calculation
    CGPoint difference = ccpSub(sourceSprite.position, targetSprite.position);
    CGFloat rotationRadians = ccpToAngle(difference);
    CGFloat rotationDegrees = -CC_RADIANS_TO_DEGREES(rotationRadians);
    rotationDegrees += 90.0f;
    CGFloat rotateByDegrees = rotationDegrees - sourceSprite.rotation;

    // animation
    CCRotateBy * turnBy = [CCRotateBy actionWithDuration:0.5f angle:rotateByDegrees];
    CCEaseIn * ease = [CCEaseIn actionWithAction:turnBy rate:4];
    [sourceSprite runAction:ease];
}

And here’s how it works, just incase you’re curious…

  • The method takes a source sprite and as target sprite as parameters. After it runs it will rotate the source to face the target.
  • The calculation part uses Cocos2d helper functions and macros (ccpSub, ccpToAngle, and CC_RADIANS_DEGREES) to figure the maths. In my original implementation I had written my own versions. Using the Cocos2d API makes my code easier to read and perhaps faster.
  • The maths work like this: Create a vector out of the distance between to points. Convert the vector to an angle. Convert the angle from radians to degrees and negate it*. Add 90 degrees to the angle to get the proper orientation. (The +=90.0f value is game specific: My sprites are rotated 90 degrees to start with.) Subtract the penultimate result of the calculation from the current rotation property (angle) of the source sprite to get the final result (the amount to rotate by).
  • The animation, which could easily be factored out into a method of its own, uses Cocos2d’s CCRotateBy and CCEaseIn to turn the source sprite to face the target in half a second. This part didn’t change from the original code. I didn’t factor it out because in my game I always use these two pieces together. I don’t like to do too much work in the abstract because I usually end up making my code too complex.

* The need to negate the value produced by CC_RADIANS_DEGREES is a bit of a mystery to me. If I don’t do it my sprite ends up facing in the opposite direction.


Posted

in

,

by

Comments

6 responses to “Cocos2d-iPhone Sprite Rotation to an Arbitrary Point”

  1. Bill Avatar
    Bill

    > My iPhone game is full of ugly mysterious code

    So why should yours be any different? ^_^

    This blog entry does raise the interesting question of code efficiency. Clearly the code is now easy to read, but how efficient is it? (Not to say that your original was necessarily better.)

    When ever code is reused (be it libraries, classes, or what have you) the programmer is establishing a trust relationship with another person that 1) they did the “right thing” and 2) that there are no unintended consequences stemming from that use.

    By having initially attempted to write the code yourself you now have some idea of the potential pitfalls and trade offs involved with the design of the borrowed routine.

    Maybe this is just me being old school, but my experience tells me that if you don’t have a good understanding of the underlying software structure your results will often be inferior.

  2. pav Avatar

    Old School is Good School! But as Cocos2d-iphone is completely open source I was able to read over implementation of the macros and functions in the API and they are awesome: http://www.cocos2d-iphone.org/api-ref/0.99.3/_c_g_point_extension_8h_source.html

  3. Bill Avatar
    Bill

    Yes, that’s what I love about open source in the modern world; the chance to follow a link and see exactly what we are talking about. For example:

    static inline CGPoint
    ccpAdd(const CGPoint v1, const CGPoint v2)
    {
    return ccp(v1.x + v2.x, v1.y + v2.y);
    }

    Suggests efficient coding, but I would next want to ask how this translates (compiles?)into machine code.

    Anyway the point is to check what the other guy is doing rather then simply assuming goodness. ^_^

  4. Mike H Avatar

    Don’t you mean CCRotateTo instead of CCRotateBy ?

  5. pav Avatar

    Hi Mike H,
    I’m using CCRotateTo because adds the amount of rotation I’ve calculated to the current value of the sprite’s rotation property. This StackOverflow answer describes the difference between the 2: http://stackoverflow.com/questions/5280539/ccrotateto-and-ccrotateby-differnce

  6. Matthew Wridgway Avatar

    Thanks John, you just saved me a whole lot of time with this post. My code was also quite a lot of trial and error, but now I’ve adapted your handy function to my needs and deleted all the ugly stuff.