The RPG Game
I love RPG games (Role Playing Games), and being a programmer, I have always wanted to write my own. However, the problem with serious game development is that it takes more than just programming to produce a good game. I worked for Sony Playstation for a while, but I worked on multimedia-related projects and not on games. So, I benched my aspirations for a spectacular 100 man-years, 10 bazillion dollars RPG. I did a couple of small shoot-em-up and board games and focused on writing looooong articles in various developer journals.
I picked a really stripped down RPG game as the vehicle to showcase the plugin framework. It's not going to amount to much. It is more of a game demo because the main program controls the hero and not the user. The concpeptual foundations are sound though, and it can definitely be extended. Now that I have reduced your expections to zero, we can move on.
Concept
The concept of the game is very basic. There is a heroic hero, who is as much brave as he is fearless. This hero has been teleported by a mysterious force to a battle arena over-populated with various monsters. The hero must fight and defeat all the monsters to win.
The hero and all the monsters function as actors. Actors are entities thast have some attributes such as location in the battlefield, health, and speed. When the health of an actor gets down to 0 (or below) it dies.
The game takes palce on a 2-D grid (the battlefield). It is a turn-based game. In each turn the actors get to play. When an actor plays it can move or attack (if it's next to another monster). Each actor has a list of friends and foes. This enables the concepts of parties, clans, and tribes. In this game the hero has no friends and all the monsters are his foes.
Designing the Interfaces
The interfaces should support the conceptual framework of course. Actors are represented by the ActorInfo struct that contains all their stats. Actors should implement the IActor interface that allows the BattleManager to get their initial stats and to instruct them to play. The ITurn interface is what an actor gets when it's his turn to play. The ITurn interface lets the actor get its own information (if it doesn't store it) to move around and to attack. The idea is that the BattleManager is in charge of the data and the actors receive their information and operate in a managed environment. When an actor moves, the BattleManager should enforce moving based on its movement points, make sure it doesn't go out of bounds, etc. The BattleManager can also ignore illegal operations (according to its policies) by actors like attacking multiple times or attacking friends. That's all there is to it. The actors relate to each other through opaque ids. These ids are refreshed every turn because actors might die and new ones may appear. Since, it's just a sample game I didn't actually implement too much policy enforcement. In online games (especially MMORPG) where the user interacts with the server using a clinet over a network protocol, it is very important to validate any action of the client to prevent cheating, fraud and griefing. Some of these games have virtual and/or real economies and people try all the time. These can easilly ruin the user experience for all the legit users.
Implementing the Object Model
The object model implementation is straightforward once you get past the the dual C/C++ thing. The actual implementation resides in the C++ methods. The ActorInfo is just a struct with data. The ActorInfoIterator is just a container of ActorInfo objects. Let's examine the Turn object. It is a somewhat important object because it is a turn-based game. A fresh Turn object is created for each actor when it is the actor's turn to play. The Turn object is passed to the IActor::play() method of each actor. A Turn object has its actor information (in case the actor doesn't store it) and it has two lists of foes and friends. It provides three accessor methods getSelfInfo(), getFriends(), and getFoes() and two action methods: attack() and move().
Example 7 contains the code for the accessor methods that simply returns the corresponding data members and the move() method that updates the location of the current actor.
ActorInfo * Turn::getSelfInfo() { return self; } IActorInfoIterator * Turn::getFriends() { return &friends; } IActorInfoIterator * Turn::getFoes() { return &foes; } void Turn::move(apr_uint32_t x, apr_uint32_t y) { self->location_x += x; self->location_y += y;
I don't validate anything. The actor may move way outside of the arena or move more than its movement points permit. That wouldn't fly in a real game.
Example 8 contains the attack() code along with its helper function doSingleFightSequence().
static void doSingleFightSequence(ActorInfo & attacker, ActorInfo & defender) { // Check if attacker hits or misses bool hit = (::rand() % attacker.attack - ::rand() % defender.defense) > 0; if (!hit) // miss { std::cout << attacker.name <<" misses " << defender.name <<std::endl; return; } // Deal damage apr_uint32_t damage = 1 + ::rand() % attacker.damage; defender.health -= std::min(defender.health, damage); std::cout << attacker.name << "(" <<attacker.health << ") hits " << defender.name <<"(" <<defender.health <<"), damage: " << damage << std::endl; } void Turn::attack(apr_uint32_t id) { ActorInfo * foe = NULL; foes.reset(); while ((foe = foes.next())) if (foe->id == id) break; // Attack only foes if (!foe) return; std::cout << self->name << "(" << self->health << ") attacks " << foe->name << "(" << foe->health << ")" << std::endl; while (true) { // first attacker attacks doSingleFightSequence(*self, *foe); if (foe->health == 0) { std::cout << self->name << " defeated " << foe->name << std::endl; return; } // then foe retaliates doSingleFightSequence(*foe, *self); if (self-&tl;health == 0) { std::cout << self->name << " was defeated by " << foe->name <<std::endl; return; } } }
The attack logic is simple. When an actor attacks another actor (identified by id), the attacked actor is located in the foes list. If it's not a foe the attack ends. The actor (via the doSingleFightSequence() function) hits the foe and the amount of inflicted damage is reduced from the foe's health. If the foe is still alive, it retaliates and hits the attacker and so on and so forth until one fighter dies.
That's all for today. In the next (and last) article in the series I'll cover the BattleManager and the game's main loop. I'll explore in-depth writing plugins for the RPG game and walk you through the directory structure of various libraries and projects that the plugin framework and the sample game are comprised of. Finally, I'll compare the plugin framework I describe here to NuPIC's plugin framework. NuPIC stands for Numenta's Platform for Intelligent Computing. I developed most of the concepts and ideas I present here while creating NuPIC's plugin infrastructure.