Cocos2dx – Platformer Physics Tutorial

I tried implementing the platforming physics logic in cocos2d-x and it came out pretty well as per my expectations. This tutorial is about how I made it. It might not be the best solution out there, but it is something that works. Before diving in, here is the demo video showcasing the final output:

Downloads

1. Mid Project (with just the platforms render)
2. Full Project (all functionalities)

1. Create a Cocos2d-x Project

I am not going to discuss about how to setup a cocos2d-x project for iOS here. I will directly dwelve into the platforming mechanism here. For info on how to setup cocos2d-x project, refer the official wiki.

All our code will go into `HelloWorld` class that comes with the template.

2. Define the Platforms

Add the following struct in HelloWorld.h to model a Line

typedef struct {
    CCPoint p1, p2;
} Line;

Declare a pointer to Line struct in HelloWorld, which will be an array of all platforms and randomly add a few lines in it:

// in HelloWorld.h
Line *lines;

// in init function of HelloWorld.cpp
lines = (Line *)malloc(sizeof(Line) * COUNT);

lines[0].p1 = ccp(100, 100);
lines[0].p2 = ccp(400, 100);

lines[1].p1 = ccp(500, 600);
lines[1].p2 = ccp(700, 600);

lines[2].p1 = ccp(400, 400);
lines[2].p2 = ccp(600, 500);

lines[3].p1 = ccp(500, 200);
lines[3].p2 = ccp(1000, 200);

The platforms are defined well with their points. Now is the time to render them on screen. Add the `draw` function to the `HelloWorld` scene.

// in header file
virtual void draw();

// in cpp file
void HelloWorld::draw() {
    glLineWidth(4.0);
    for (int i = 0; i < COUNT; i++) {
        ccDrawLine( lines[i].p1, lines[i].p2 );
    }
    CCLayer::draw();
}

The above code sets the line width to 4 pixels, loops through our platform array and draws the lines. Our screen should look like this:

3. Character Sprite and Controls

The platforms are setup right. Now lets add the hero sprite which will run through the platforms and two button controls to make the character move. To add the hero and controls, do this:

// in header file
CCSprite* pSprite;
void menuCloseCallback(CCObject* pSender);

// in init function of HelloWorld.cpp
CCMenuItemImage *pCloseItem = CCMenuItemImage::create("CloseNormal.png",
                                                      "CloseSelected.png",
                                                      this,
                                                      menu_selector(HelloWorld::menuCloseCallback) );
pCloseItem->setPosition( ccp(CCDirector::sharedDirector()->getWinSize().width - 60, 60) );
pCloseItem->setTag(1);
pCloseItem->setScale(2.5f);

CCMenuItemImage *pCloseItem1 = CCMenuItemImage::create("CloseNormal.png",
                                                       "CloseSelected.png",
                                                       this,
                                                       menu_selector(HelloWorld::menuCloseCallback) );
pCloseItem1->setPosition( ccp(60, 60) );
pCloseItem1->setTag(2);
pCloseItem1->setScale(2.5f);

// controls for left and right movement
CCMenu* pMenu = CCMenu::create(pCloseItem, pCloseItem1, NULL);
pMenu->setPosition( CCPointZero );
this->addChild(pMenu, 1);   

// hero sprite
pSprite = CCSprite::create("CloseNormal.png");
pSprite->setPosition( ccp(600, 700) );
pSprite->setTag(-1);
this->addChild(pSprite, 0);

By now our screen should look like this:

4. Basic Movement

The stage is set with all the objects put in place. Now is the time to start moving the objects. Add two variables `speedX` and `speedY` that controls the movement of our hero and schedule the update function of CCLayer to get into the game loop.

// in header file
virtual void update(float dt);
float speedX, speedY;

// in init function of HelloWorld.cpp
speedX = 0;
speedY = 0;
this->scheduleUpdate();

// in HelloWorld.cpp
// moves the hero around the screen as per the speedX and speedY values
void HelloWorld::update(float dt) {
    CCPoint p = pSprite->getPosition();    
    pSprite->setPosition( ccp( p.x + speedX * dt, p.y + speedY * dt ) );
}

// makes the hero to move to left / right based on the touched control
void HelloWorld::menuCloseCallback(CCObject* pSender) {
    if (((CCNode *)pSender)->getTag() == 1) {
        speedX += 100;
    } else {
        speedX -= 100;
    }
}

Now touching the left control button, increases the movement of the hero in left direction and the same with the right control button. As of now, Movement happens only in x-axis and there is no gravity (falling down), friction and collision. The project at this point can be downloaded here for reference.

5. Physics Magic

First replace the `update` function with following code.

void HelloWorld::update(float dt) {
    CCPoint p = pSprite->getPosition();

    speedX *= 0.95; // friction reduces the speed over time

    if (pSprite->getTag() == -1) { // free falling
        CCLog("searching for platform... hero is falling");
        // iterate through all platforms
        for (int i = 0; i < COUNT; i++) {
            // if hero position lies between the x bounds of any platform
            if ( lines[i].p1.x < p.x && lines[i].p2.x > p.x ) {
                // also it is closer than 100 pixels to the platform
                if ( p.y - lines[i].p1.y < 100 && p.y - lines[i].p1.y > 0) {
                    // assign the platform to the hero
                    pSprite->setTag(i);
                    break;
                }
            }
        }
        // gravity pull decreases the y-speed
        speedY -= 10.0;

        // not allowing hero to go down indefinitely, by making him swap to the top
        if ( p.y < 0 ) {
             p.y = 768;
        }
    } else { // resting in or closer to some platform
        int tag = pSprite->getTag();
        CCLog("in platform %d", tag);

        // make sure if hero is still in the x bounds of the platform or moved away
        if ( lines[tag].p1.x < p.x && lines[tag].p2.x > p.x ) {
            float x2, y2;
            x2 = p.x;
            // platform may be a sloppy one, so interpolate the y value based on end points of platform and current hero position 
            y2 = (x2 - lines[tag].p1.x) * (lines[tag].p2.y - lines[tag].p1.y) / (lines[tag].p2.x - lines[tag].p1.x) + lines[tag].p1.y;
            if (p.y - y2 > 0) { // if hero is above platform, make him fall down
                speedY -= 10.0;
            } else {            // nullify the y speed and rest on platform
                speedY = 0;
                p.y = y2;
            }
        } else { // switch to free fall mode, if hero is moved away
            pSprite->setTag(-1);
        }
    }

    pSprite->setPosition( ccp( p.x + speedX * dt, p.y + speedY * dt ) );
}

I believe the above code is self-explanatory with all the comments put in. So I will just add few notes that deserve some explanation here.

Tag

`tag` property of Hero is used to refer to the platform he is in. If he is free falling (i.e., not attached to any platform) then `tag` will be -1.

Friction

 speedX *= 0.95;

Above code decrements the x speed of the hero by 5% on each update, thus simulating the friction effect.

Interpolation

If all the lines are perfectly horizontal, then y values of the end points or any other point in that line would be equal. If the lines are sloppy, things would be different and we need to calculate the y-value of any arbitrary point in the given line for the given x. If its confusing, see the picture below and read on.


For any platform, we have the end points A (x1, y1) and B (x2, y2) and we have the position of the hero represented here as H(hx, hy). But to check if the hero is above or below the line, we need the point that lies in the line which is P(x, y). Of course, x would be same as hx, but how to find y? Not to worry, we can interpolate the y value with the following formula:

y = y1 + (hx - x1) * (y2 - y1) / (x2 - x1)

The above formula is translated to CPP in our code with our variables as follows:

y2 = (x2 - lines[tag].p1.x) * (lines[tag].p2.y - lines[tag].p1.y) / (lines[tag].p2.x - lines[tag].p1.x) + lines[tag].p1.y;

Thats the end of the tutorial and here is the full project. This project can be easily extended to 2d side scrolling platformer games. I will try to write a post on that in future. There might be several places where a beginner can easily get lost. Post the queries in comments and I will try to resolve.