Tuesday, April 6, 2010

iPad Impressions and Other Goodies

The weekend is over and I can start thinking more rationally about the iPad. Haha! Right... Anywho, here are some impressions.

It’s a sleek little device - thin, light, and just shiny enough to invite your fingers to rub it all over. However, the more I keep looking at it, the more it looks like an oversized iPhone. I have to laugh each time I see my iPod Touch right next to it. I can just imagine women pulling an iPad out of their purses to answer a telephone call or jocks with iPads strapped to their biceps as they jog on treadmills in the gym.

If you disregard the perf boost needed to deal with the quadruple-sized screen, the iPad doesn't seem to run that much faster than the iPhone. It doesn't run slower either, which is a good thing, but it's not the speedy bullet that Apple initially implied. If you use the same tricks you use on the iPhone, though, it runs just fast enough.

Initially, I was more than a little worried when I developed code against the iPad simulator. Using Cocos2D, whenever I plopped a large texture on the screen, say a background, the frame rate would immediately drop to 20-30 FPS, even with OpenGL blending completely disabled. If you write a game, you need at least one large background texture most of the time, so the potential frame rate limitation posed a serious problem. That said, I was very happy to find out that the actual iPad handles all OpenGL calls much more gracefully. Doodle Blast! ran on full 60 FPS with the screen mostly empty and barely dipped into the 40s with everything in motion. I must say that the game looks quite slick on the big screen, especially when the camera zooms in and out while everything is in motion. Best looking doodles five bucks can buy!
In the end, I don’t think the iPad will reach nearly the popularity levels of the iPhone (watch me be completely wrong!). It’s definitely a fun toy to play with, though.

So, what’s next? Not sure. Right now I’m taking a little bit of time to catch my breath. I can hardly believe that it has been only one month and two weeks since Doodle Blast! first hit the stores – now there is an update, and iPad version, a blog, a web site, two trailers, over 11,000 lines of code and a contest. Talk about busy!

On a different note, I was browsing through the Doodle Blast! code base and I ran into two Cocos2D utility classes that I keep using and re-using all over the place. So, I thought I would share them with you. The way I generally write code under pressure is to write and polish the code paths that I need, leaving all others to be flushed out when they are actually needed. What that means for you is that these snippets are not fully complete, but they work as a great starting point. Feel free to use them however you please. A mention of credit is always nice, but not required.

The first utility is an action, EyeBlink (EyeBlink.h, EyeBlink.m), that does just that – blinks "eyes" on sprites. Basically, it takes 1 or 2 sprite frames as input. You can either specify two frames – one with eyes open and one with eyes closed – or just a single frame of closed eyelids that will be superimposed over an image of your character. The action then switches between these frames (or hides them if only one frame is specified) with some randomness built in to simulate irregular eye blinks. Here is how you’d use it:

Option 1:
CCSpriteFrame* openFrame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"eyesOpen.png"];
CCSpriteFrame* closedFrame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"eyesClosed.png"];
CCSprite* myCharacter = [CCSprite spriteWithSpriteFrame:openFrame];
[myCharacter runAction:[EyeBlink actionWithOpenDuration:0.7 openVar:0.3 closedDuration:0.1 closedVar:0.05 openFrame:openFrame closedFrame:closedFrame repeatForever:true]];
[myLayer addChild:myCharacter];

In this case you create two frames and alternate between them. The duration of the open frame will last 0.7 +/- 0.3 seconds while the duration of the closed frame will last 0.1 +/- 0.05 seconds.

Option 2:
CCSpriteFrame* characterWithEyesOpenFrame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"myCharacter.png"];
CCSpriteFrame* closedEyeLidsFrame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"eyesClosed.png"];
CCSprite* myCharacter = [CCSprite spriteWithSpriteFrame: characterWithEyesOpenFrame];
CCSprite* eyeLids = [CCSprite spriteWithSpriteFrame:closedEyeLidsFrame];
[eyeLids runAction:[EyeBlink actionWithOpenDuration:0.7 openVar:0.3 closedDuration:0.1 closedVar:0.05 openFrame:nil closedFrame: closedEyeLidsFrame repeatForever:true]];
[myCharacter addChild:eyeLids];
[myLayer addChild:myCharacter];

The same frame duration parameters apply here as well, but instead of switching between two frames, the code shows and hides a single frame.

The second snippet is a Chipmunk / Cocos2D debug node (DebugDrawNode.h, DebugDrawNode.m) that automagically draws debug outlines of all collision shapes associated with a given cpSpace object. It’s a simple subclass of CCNode with a custom draw() method. To use it, instantiate it and add it to your layer at the proper Z-depth thusly:

cpSpace* space = … ;
[myLayer addChild:[DebugDrawNode nodeWithSpace:space] z:SOME_Z_VALUE];

Enjoy!

6 comments:

  1. Hey GC,

    Cool code snippet! But it didn't work work in Chipmunk 6 due to some of the functions being deprecated! So, I've updated the code as follows...

    DebugDraw.m:
    //
    // DebugDrawNode.m
    // BallTest
    //
    // Created by Juraj Hlavac on 4/5/10.
    // Copyright 2010 Apple. All rights reserved.
    //

    #import "DebugDrawNode.h"

    @implementation DebugDrawNode

    static void eachBody(cpBody *body, void *unused) {
    cpBodyEachShape(body, drawEachShape, nil);
    }

    static void drawEachShape(cpBody* body, cpShape* shape, void* data) {
    if (shape) {
    if (shape->klass_private->type == CP_CIRCLE_SHAPE) {
    glColor4f(0.5f, 0.5f, 0.5f, 1.0);
    cpCircleShape* crc = (cpCircleShape*)shape;
    cpVect c = cpvadd(shape->body->p, cpvrotate(crc->c, shape->body->rot));
    ccDrawCircle(c, crc->r, shape->body->a, 10, true);
    }
    else if (shape->klass_private->type == CP_POLY_SHAPE) {
    glColor4f(1.0f, 0.0f, 0.0f, 1.0);
    cpPolyShape* poly = (cpPolyShape*)shape;
    ccDrawPoly(poly->tVerts, poly->numVerts, YES);
    }
    else if (shape->klass_private->type == CP_SEGMENT_SHAPE) {
    glColor4f(1.0f, 0.0f, 0.0f, 1.0);
    cpSegmentShape* segment = (cpSegmentShape*)shape;
    ccDrawLine(segment->ta, segment->tb);
    }
    else {
    glColor4f(0.0f, 0.0f, 1.0f, 1.0);
    cpSegmentShape* seg = (cpSegmentShape*)shape;
    ccDrawLine(seg->ta, seg->tb);
    }
    }
    }

    +(id) nodeWithSpace:(cpSpace*)space {
    return [[[DebugDrawNode alloc] initWithSpace:space] autorelease];
    }

    -(id) initWithSpace:(cpSpace*)space {
    if ((self == [super init])) {
    _space = space; // weak ref
    }
    return self;
    }

    -(void) draw {
    if (visible_ && _space) {
    cpSpaceEachBody(_space, &eachBody, self);
    }
    }

    @end




    DebugDraw.h:
    //
    // DebugDrawNode.h
    // BallTest
    //
    // Created by Juraj Hlavac on 4/5/10.
    // Copyright 2010 Apple. All rights reserved.
    //

    #import "cocos2d.h"
    #import "chipmunk.h"

    @interface DebugDrawNode : CCNode {
    cpSpace* _space; // weak ref
    }
    +(id) nodeWithSpace:(cpSpace*)space;
    -(id) initWithSpace:(cpSpace*)space;
    static void drawEachShape(cpBody* body, cpShape* shape, void* data);
    @end

    ReplyDelete
  2. Oh, cool, thanks for updating it! Yes, the original snippet was compiled against Chipmunk 4.?. I've updated the links with your code.

    ReplyDelete
  3. What changes does it need to draw static shapes?

    ReplyDelete
  4. I found it. I was adding only the shapes to the space and not the bodies. cpSpaceEachBody couldn't find them.

    ReplyDelete
  5. How can I draw constraints and collisions?

    ReplyDelete
  6. DebugDrawNode should take care of drawing constraints for you already. And if you look into cpSpaceDraw(), you will find code that draws contact points (ie. collisions) for you already. I think it's just currently disabled.

    ReplyDelete