Bullfrog Game Design: Dynamic Bugs

Developing software is an interesting process. There are times where I work very hard with little to show for it for several days. Then there are those days where I can knock out a ton of tasks on my “To Do” list with little effort. Usually, one state directly follows the other.

I recently spent several days working on the mechanism that Bullfrog 2 will use to load level and bug definitions. There was much reworking and testing involved. Now that the work is complete, designing new levels and bugs should take little time and effort.

Hardcoded Bugs

In the original Bullfrog, the player was tasked with munching bugs as fast as possible. Bugs were randomly spawned on the screen based on a simple algorithm. This algorithm would generate a certain number of bugs depending on what round the player had reached. For example the gnat was generated using the following calculation.

int numberOfGnats = roundIndex * GNAT_SPAWN_RATE;
listing 1

Each bug type had it’s own spawn formula and custom class. Each bug class would then be allocated and tracked in a NSMutableArray during the round.

NSPoint origin = GetRandomGamePoint(gameRect);
unsigned int direction = GetRandomAngle();
switch([bugDef bugType])
   case GnatType:
      bug = [[Gnat alloc] initAtPosition:origin
//   ...
listing 2

This approach worked fairly well for a simple game using random spawn points on a simple game board. There were no level design elements to worry about. A bug was either alive and on the screen, or it had been eaten.

Dynamic Bugs

For Bullfrog 2, we really wanted to move away from randomly generated rounds and bugs to designed levels. We plan to have multiple levels with unique artwork and challenges. But how do we define bugs and have them spawn based on a definition file?

We could read in the definition and run through a big switch statement with a case block for each bug type similar to the code above (listing 2) used in the first Bullfrog. But, this gets nasty pretty quick.

We’re also adding new game elements to Bullfrog 2. Players will have obstacles such as rocks and water to deal with while hunting down bugs. These elements also need to be defined and tracked inside the game.

With a bit of imagination, rocks and bugs are basically the same thing except for a few different attributes. Bugs can move, rocks can’t. A mosquito can damage the player, but a rock can block the player’s movement.

This lead us to a base class we’re calling Actor. Each actor can be completely defined with a set of attributes which denote size, animation, and other things.

We still have the problem of needing unique AI (artificial intelligence) for the different bugs. Using just the Actor class won’t provide us with customized code to define personality attributes such as chase and escape behaviors.

So we defined a set of Actor subclasses that provide the custom behaviors of the various bugs and other objects populating the Bullfrog world. But, with each new game object, our switch case grows in complexity.

Objective-C to the Rescue

This is where our decision to use Objective-C pays off. By using the dynamic nature of the Objective-C we can allocate and initialize our actors at runtime without using a huge switch statement.

Given the following Actor definitions (listing 3) we can dynamically generate the level’s actors with a simple loop (listing 4).


listing 3
- (void)loadActorsDefinition:(NSArray *)actorsDefinition 
                   intoLayer:(NSMutableArray *)layer
   id actor;
   NSDictionary *actorDef;
   for(actorDef in actorsDefinition) {
      NSString *actorClass = [actorDef valueForKey:@"class"];
      actor = [[NSClassFromString(actorClass) alloc] init];
      if (![actor isKindOfClass:[OLActor class]]) continue;
      [actor loadDefinition:actorDef];
      [layer addObject:actor];
listing 4

The key piece here is the function: NSClassFromString(NSString *str). This Foundation function returns a Class object that can then be used just like your hardcoded class definition for allocating a new instance.

In the above function (listing 4) we get the “class” attribute from the XML and feed that into the NSClassFromString function to get our Class. Then pass in the XML for this particular Actor into the instance method -loadDefinition: on the Actor class. Some more Objective-C runtime magic reads in the defined attributes and stores them in the Actor instance properties using key-value coding (listing 5).

NSString *key;
for (key in [definition allKeys]) {
   [self setValue:[definition valueForKey:key] forKey:key];
listing 5

Now all actors for each defined level are easily loaded and we don’t have to constantly rewrite or modify our code each time we add a new type of bug or game object.

The difference in complexity and number of lines of code in Bullfrog 2 is greatly reduced because of the magic that Objective-C provides. Dynamic class loading is very powerful and provides us with great flexibility moving forward.

One thought on “Bullfrog Game Design: Dynamic Bugs

Comments are closed.