How to Make a Platformer Game with Cocos2D-X

Artificial Intelligence

Creating artificial intelligence (AI) for your game can be one of the most rewarding parts of development. It is the icing on the cake, the cherry on top of the pie. Seeing your creations come to life, and being able to guide them is pure satisfaction.
In the screenshot above, we see the hero Billy running away from an evil wizard. The wizards can fire large, red, damaging, magic orbs which fly slowly towards their destination. The wizards themselves wink in and out of existence, warping from one place in the level to the next, making them enigmatic and difficult to defeat. Another type of AI is the bat. The bat hangs upside down from ceilings, waiting for the hero to approach. When close enough, the bat wakes up and swoops down toward the hero.
In this chapter, we'll take a look at how to develop simple AI like these, so you can bring your own game to life. ### Moods To begin coding AI, think of the different possible moods your enemy will experience. This approach helps to compartmentalize the task at hand. Essentially it's the good ol' divide and conquer strategy. For example, the bat has three different moods: 1. Sleep - Wait for the player to get close 2. Seek - Awake and get ready to swoop 3. Attack - Swoop down towards the player enum { kMoodSleep, kMoodSeek, kMoodAttack }; We'll create a basic AI superclass and then subclass out the individual enemies. Each mood will have its own self-contained function which runs the AI while in that particular mood. ### Timers Each mood has a timer. When a mood function is first called, the timer is set the amount of time the AI will need in that mood. When the mood timer counts down to zero, the AI calls the `chooseMood` function, which decides which mood to set next. Now that we have an outline on how to proceed, let's begin coding. class AI : public Character { private: typedef Character super; typedef AI self; protected: /// The current mood of this AI int mood; /// A timer used in mood functions float timer; /// When the player is close enough, this object will wake up float wakeDistanceSQ; /// Return the amount of time this AI will sleep for virtual float sleepTime(); /// Set the AI's mood virtual void setMood(int newMood); /// Automatically choose a new mood virtual void chooseMood(); /// Returns true if the AI can wake up virtual bool canWakeup(); public: AI(); virtual ~AI(); /// Allow the AI to think & move virtual void update(float delta); /// Run the sleep mood virtual void moodSleep(float delta); /// Run the seek mood (find the player) virtual void moodSeek(float delta); /// Run the attack mood virtual void moodAttack(float delta); }; const float kTimerUp = -9999.0f; const float kTimerUp = -9999.0f; AI::AI() { mood = kMoodSleep; timer = kTimerUp; wakeDistanceSQ = 400.0f * 300.0f; } AI::~AI() { } float AI::sleepTime() { // return the amount of time this AI will sleep for; // this function is in place so different types of sub classes // can sleep for different amounts of time return 0.5f; } void AI::setMood(int newMood) { // set new Mood mood = newMood; // reset the timer so the mood initializes itself timer = kTimerUp; } void AI::chooseMood() { // this is the default chooseMood method // override it to implement different enemy personalities // choose between seeking and sleeping this->setMood(this->canWakeup() ? kMoodSeek : kMoodSleep); } bool AI::canWakeup() { // can wake if player is alive and is close enough Player& player = this->getPlayer(); // inherited from LevelObject // note that player distance is saved for later use float playerDistanceSQ = this->distanceSQ(player.getPosition()); return (player.isAlive() && playerDistanceSQ < wakeDistanceSQ); } void AI::update(float delta) { super::update(delta); if (this->isAlive()) { // count down the timer if (timer != kTimerUp) timer -= delta; // perform current mood switch (mood) { case kMoodSleep: this->moodSleep(delta); break; case kMoodSeek: this->moodSeek(delta); break; case kMoodAttack: this->moodAttack(delta); break; } } } void AI::moodSleep(float delta) { // starting sleep mood... if (timer == kTimerUp) { timer = this->sleepTime() + (this->sleepTime() * CCRANDOM_0_1()); this->sprite->stopAllActions(); this->animate("sleep", kActionSleep); } // wake up? else if (timer <= 0.0f) this->chooseMood(); } void AI::moodSeek(float delta) { // starting seek mood... if (timer == kTimerUp) timer = 1.0f; // timer up else if (timer <= 0.0f) this->chooseMood(); } void AI::moodAttack(float delta) { // starting attack mood if (timer == kTimerUp) timer = 1.0f; // timer up else if (timer <= 0.0f) this->chooseMood(); } The first bit to grasp in the above code is the `update` function. If the AI is alive, it counts down the timer and calls the current mood function. Within each mood function, it sets the initial timer if necessary. If the timer has expired, it calls the `chooseMood` function which sleeps if the player is too far away, or wakes up if not. ### The Bat Enemy AI Now let's take a look at the specific AI for the bat. Simply subclass `AI` and override the mood functions to make the bat unique. In the sleep mood, the bat simply hangs on the ceiling. Apply a linear impulse which is greater than gravity to keep the bat hanging: void Bat::moodSleep(float delta) { super::moodSleep(delta); // counteract gravity body->ApplyLinearImpulse( b2Vec2(0.0f, 100.0f * delta), body->GetWorldCenter(), true ); } In the seek mood, the bat simply opens its eyes in warning, prepared to swoop down. A timer of one second is used, an animation is played and the same linear impulse is applied to keep the bat on the ceiling: void Bat::moodSeek(float delta) { if (timer == kTimerUp) { // open up the Bat's eyes and get ready to swoop timer = 1.0f; this->sprite->stopAllActions(); this->animate("rest", kActionRest); } else if (timer <= 0.0f) this->setMood(kMoodAttack); // counteract gravity body->ApplyLinearImpulse( b2Vec2(0.0f, 100.0f * delta), body->GetWorldCenter(), true ); } Now for the attack mood. The bat animates flapping wings, plays a sound, then decides whether to swoop left or right depending on the player position. For one second, the bat swoops down and for another, the bat swoops back up. Both of these motions are controlled by linear impulses which are either slightly stronger or weaker than gravity. This turns the swoop into a nice curve: void Bat::moodAttack(float delta) { if (timer == kTimerUp) { timer = 2.0f; // animate this->sprite->stopAllActions(); this->animate("fly", kActionAttack); this->profile->playSound("fly"); // determine swoop direction auto& player = this->getPlayer(); if (player.getPositionX() > this->getPositionX()) { swoopX = 3.0f; this->sprite->setFlipX(false); } else { swoopX = -3.0f; this->sprite->setFlipX(true); } } // mood finished else if (timer <= 0.0f) { this->setMood(kMoodSleep); } // swooping up else if (timer <= 1.0f) { body->ApplyLinearImpulse( b2Vec2((swoopX / 2.0f) * delta, 55.0f * delta), body->GetWorldCenter(), true ); // keep swooping until we hit the ceiling if (!hasContactAbove) timer = 1.0f; } // swooping down else if(timer <= 2.0f) { body->ApplyLinearImpulse( b2Vec2(swoopX * delta, 40.0f * delta), body->GetWorldCenter(), true ); if (hasContactBelow) timer = 1.0f; } } That's it for the bat AI. ### Conclusion The moods for the wizard are quite similar: 1. Sleep - Wait for the player to get close enough 2. Seek - Find a random position near the player and warp there 3. Attack - Launch a magic orb at the player, then go back to sleep Imagine the AI -- whether it be an enemy or non-playing-character (NPC) -- that you'd like to create for your game. What moods will it have? The easiest way to start is with simple sleeping and seeking. Add the attack mood when you've got that mastered. Good luck and leave a comment when you've brought your first AI to life! [1]: /book/cocos2d-x/diy-game-marketing/

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

Next Chapter >