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
                           withDirection:direction];
      break;					
//   ...
}
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).


   
      class
      BFRock
      blocksMovement
      1
   
   
      class
      BFGnat
      blocksMovement
      0
      moveSpeed
      0.005
      turnFrequency
      5
      turnSpeed
      20
   

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.

LicenseKeeper 1.2.4

LicenseKeeper 1.2.4 has been pushed out the door. This version takes care of some nagging issues.

Use “Check for Updates” under the LicenseKeeper main menu or download the update directly:

LicenseKeeper 1.2.4

Release Notes

  • Fixed: Purchase Price formats ‘0’ incorrectly to ‘0,0’ on computers using U.S. formatting.
  • Fixed: Email with Text as base64 Content-Transfer-Encoding was not being decoded.
  • Fixed: Attaching an Email with attached DMG not working correctly.
  • Fixed: Hangs While Scanning Noiseware Professional email.
  • Fixed: Hangs while scanning Xyle scope email.
  • Fixed: Serial Scanning for Acorn from Flying Meat.
  • Fixed: Scanned Serial Number is being logged to console.
  • Fixed: If User’s AddressBook entry has missing name values “(null)” gets inserted.
  • Fixed: Improved error handling around AddressBook lookups.
  • Fixed: Improved error handling to library migration.

LicenseKeeper Quick Tip: Backup your data


Managing your software licenses and serial numbers is easy with LicenseKeeper. Your data is there for you when you need it.

But, what do you do when your hard drive dies? How do you move your data to a new Mac or a fresh installation of Mac OS X?

The best way to accomplish this is to backup your LicenseKeeper data and then restore it when and where it is needed.

Backup

LicenseKeeper saves two sets of information. Your serial numbers and related data are stored in the following location under your home folder:

~/Library/Application Support/LicenseKeeper

Preferences and settings are stored in a file also under your home folder:

~/Library/Preferences/com.outerlevel.licensekeeper.plist

To ensure you have a backup of all LicenseKeeper related data, you’ll want to have a copy of both items in a safe and secure location.

Backing up your data is an essential part of using a computer and it should really be done automatically on a regular schedule to an external or second physical hard drive or other backup device. Personally, I backup everything once a day to an external mirrored RAID. You can never be too careful with your data.

Restore

Restoring your LicenseKeeper data simply entails copying your backups to their original locations or similar locations under your home folder on your new computer.