How to Make a Platformer Game with Cocos2D-X

Powerups and Items

In a side-scrolling platformer game, it can be really fun to add items, powerups, trampolines and other objects that interact with the player.
### Trampolines If you've ever played *Sonic the Hedgehog*, you'll know what I mean by trampolines. Trampolines are anything that launches the player in a certain direction. In the case of the exploding barrels, they launch the player directly upwards. To detect whether the player is making contact with an item or other object, there's generally one of two ways: 1. Set a contact listener for your `b2World` object, then sort through the `BeginContact` and `EndContact` messages looking for meaningful collisions. This will work with solid objects that the player cannot pass through. 2. Create a sensor fixture that the player can pass through, but will trigger `BeginContact` and `EndContact` messages. These are good for non-solid objects. ### b2ContactListener Both of the above mentioned items are solid fixtures, so let's explore how to create a Box2d contact listener. First, you'll need a class that inherits from `b2ContactListener` and overrides `BeginContact` and `EndContact`: class Level : public Node, public b2ContactListener { public: // ... void BeginContact(b2Contact* contact); void EndContact(b2Contact* contact); // ... }; In a previous chapter, we discussed creating dynamic bodies like the player. When the body is created, a user data pointer is set to the containing `LevelObject`: void LevelObject::addBodyToWorld(b2World* world) { b2BodyDef bodyDef; bodyDef.type = b2_dynamicBody; bodyDef.userData = this; this->body = world->CreateBody(&bodyDef); } Having that user data pointer makes for convenient access to the `LevelObject` when contact occurs: void Level::BeginContact(b2Contact* contact) { // call the appropriate LevelObject's beginContact function if (contact && contact->IsTouching()) { // turn fixtures into level objects LevelObject* A = static_cast (contact->GetFixtureA()->GetBody()->GetUserData()); LevelObject* B = static_cast (contact->GetFixtureB()->GetBody()->GetUserData()); // turn fixtures into player Player* player = dynamic_cast(A); LevelObject* other = B; if (player == nullptr) { player = dynamic_cast(B); other = A; } // call the begin contact if (A && B) { // always prefer to call the player's version of beginContact if (player && other) player->beginContact(other, contact); else A->beginContact(B, contact); } } } void Level::EndContact(b2Contact* contact) { // nothing to do here for the current implementation } Essentially, all the above code does is convert the `b2World`'s `BeginContact` message into a `LevelObject::beginContact()` that can be overridden. It prefers to call the `Player` object's version, taking the other level object as a parameter. ### Attack! `Player::beginContact` searches through its body's current contacts, looking for the object that triggered the contact. This reveals the point of collision, which can be used to see which object is higher. If the player is higher than the object, the contact is considered to be the player's attack. void Player::beginContact(LevelObject* otherObject,b2Contact* contact) { const float kPixelsPerMeter = 32.0f; // search contact list for this object b2ContactEdge* edge = body->GetContactList(); for ( ; edge; edge = edge->next) { if (otherObject == static_cast(edge->other->GetUserData())) { // are we landing on top of this object? float heightFactor = this->sprite->getContentSize().height / kPixelsPerMeter / 4.0f; b2Vec2 othersPosition = edge->other->GetPosition(); // if so, attack the object if (othersPosition.y < (body->GetPosition().y - heightFactor)) otherObject->attackedBy(this); // else, we are being attacked else this->attackedBy(otherObject); } } } Landing on an enemy does damage. Landing on a barrel launches the player into the air. Here's the `Barrel` class' `attackedBy` function, which optionally can destroy or re-use the barrel: void Barrel::attackedBy(LevelObject* o) { Player* player = dynamic_cast(o); if (!this->open && player) { bool destroyable = false; this->profile->playSound("open"); // bounce the player in the air player->setVelocity(Point(0.0f,0.0f)); player->applyImpulse(Point(0.0f,50.0f)); // flip in the air if (player->isAlive()) player->animateFlip(player->getVelocity().x < 0.0f); // destroy the barrel if (destroyable) { this->open = true; this->setIsSolid(false); this->animate("animation", kActionOpen, false); this->sprite->runAction(Sequence::create( DelayTime::create(1.0f), CallFunc::create(this, callfunc_selector(self::removeFromLevel)), nullptr )); } // re-use the barrel else { Animation* anim = this->profile->animationForKey("animation"); if (anim) { this->sprite->stopAllActions(); this->sprite->runAction(Sequence::create( Animate::create(anim), Animate::create(anim)->reverse(), CallFunc::create(this, callfunc_selector(self::setFirstFrame)), nullptr )); } } } } ### The Stone Okay, so there's really nothing special about the `Stone` object. All it does is create a dynamic, circular fixture with a high restitution and attach a sprite. Box2d does everything from then on, allowing the player to move the stone around. Because the stone is a `LevelObject` it will receive the `attackedBy` call, essentially absorbing the wizard's magical attacks. ### Sensor Fixtures So what if you wanted to create a non-solid item like a coin that can be picked up on contact? If it's not solid then there are no fixtures, so how would a collision occur to trigger the `BeginContact` call? For this situation, a special type of fixture called a `sensor` can be used. You still create a Box2d body. You just attach a sensor fixture which other objects can pass through, triggering a contact: enum { kFilterCategoryLevel = 0x01, kFilterCategorySolidObject = 0x02, kFilterCategoryNonSolidObject = 0x04 }; // create a circular sensor fixture b2FixtureDef fixtureDef; b2CircleShape circleShape; circleShape.m_radius = 16; fixtureDef.shape = &circleShape; fixtureDef.isSensor = true; fixtureDef.filter.categoryBits = kFilterCategoryNonSolidObject; fixtureDef.filter.maskBits = 0xffff; body->CreateFixture(&fixtureDef); ### Conclusion There are many other ways to use sensor fixtures and the `Begin/EndContact` messages. Play around with the code and you'll learn how to create items that interact with the player in your game. [1]: /book/cocos2d-x/artificial-intelligence/

Got questions? Leave a comment below. You can also subscribe to be notified when we release new chapters.

Next Chapter >