Introduction to Component Based Architecture in Games

This is a blog post by site administrator Ray Wenderlich, an independent software developer and gamer. When you’re making a game, you need to create objects to represent the entities in your games – like monsters, the player, bullets, and so on. When you first get started, you might think the most logical thing is […] By Ray Wenderlich.

Leave a rating/review
Save for later
Share

This is a blog post by site administrator Ray Wenderlich, an independent software developer and gamer.

When you’re making a game, you need to create objects to represent the entities in your games – like monsters, the player, bullets, and so on.

When you first get started, you might think the most logical thing is to create a base class called “Game Object” that contains the common code. Then you can create subclasses for Monsters, and maybe subclasses of Monsters for specific types of monsters.

For simple games, this works quite fine and is quite easy to program. However, as your game get larger and more complex, this architecture begins to cause some problems in practice.

In this tutorial, you’re going to:

  • See for yourself why the inheritance-based architecture causes problems in practice
  • Learn how something called “component based architecture” can solve this problem
  • Learn about of some of the different ways to implement component based architecture
  • Implement one such method yourself in an example game, known as “entity systems!”

In this tutorial you will be working on a game made with Cocos2D for the iPhone. It is helpful (but not absolutely necessary) if you have some prior experience with this framework before going through this tutorial. To learn more about Cocos2D, check out some of the other Cocos2D tutorials on this site.

Without further ado, let’s get started!

Introducing MonsterWars

In this tutorial, you will be working on a simple game called MonsterWars. Download the starter project open it in Xcode, and build and run to check it out. You’ll see something like the following:

Sample game with inheritance based architecture

I didn’t have time to add instructions, so here’s how you play:

  • You get money every so often, which you can see in the upper left. You can use it to buy units, by tapping the buttons at the bottom.
  • There are three enemy types: Quirk (fast and cheap), Zap (ranged attackers), and Munch (slow but has AOE chomp).
  • You can switch your units from attacking to defending by tapping on your castle (the green one on the left).
  • If your units destroy the enemy’s castle, you win!

Play the game, and see if you are smart enough to beat the AI!

Note: This game comes from a previous tutorial on this site, where we showed you how to develop the artificial intelligence for this game. If you’re curious curious how the AI works, check it out!

Once you win (or give up due to the AI’s brilliant strategy), look at the code in the project to get familiar with how things are put together. Then keep reading to start seeing some of the drawbacks of this object-oriented game object method!

A Shooting Castle: Overview

This game is pretty good so far, but one thing that would make the game even more fun (IMHO) is if the castle could shoot at incoming enemies.

This would be nice for the game design for a number of reasons:

  • If you’re losing due to a big attack on your castle, you have a better chance to come back since your castle is helping out by attacking.
  • It adds some additional strategy go the game – you want the enemy to attack your castle because you can get some “free hits” on them by your castle.
  • It will make the games last a bit longer, for more fun and back and forth.

Let’s take a look at the current game and see how you could fit this in.

Right now, the game has the following object oriented architecture:

Inheritance based architecture

As you can see, when this was developed, the design only called for Monsters to shoot, so the properties related to shooting were put inside the Monster class.

However, now you want the castles to shoot (which are represented by the Player object). So you have two options:

  1. Rework the object hierarchy. You could switch around the object hierarchy – maybe make Player a subclass of Monster.
  2. Move the code to a common base class. Alternatively you could move the ranged properties and code up to a common base class. The common base class of Monster and Player is GameObject.

Both of these options are equally bad, but for this tutorial you’re going to try option #2 so you can see what it looks like. I already did #1 for you in the sample project to show you what it looks like – Laser derives from Monster, even though arguably a Laser is not really a monster!

A Shooting Castle: Implementation

OK, so you’re going to move the shooting code out of Monster and into a common base class: GameObject.

To do this, open Monster.h and comment out these properties:

/*
@property (assign) BOOL isRanged;
@property (assign) float rangedRange;
@property (assign) float rangedDamage;
@property (assign) float rangedDamageRate;
@property (assign) float rangedLastDamageTime;
@property (strong) NSString * rangedSound;
*/

Then open Monster.m and comment out the updateRanged: method:

/*
- (void)updateRanged:(ccTime)dt {    
    // Lots of code here...    
}
*/

Finally, inside update: comment out the call to updateRanged:

[self updateRanged:dt];

Then, open GameObject.h and add the properties you removed from Monster.h:

@property (assign) BOOL isRanged;
@property (assign) float rangedRange;
@property (assign) float rangedDamage;
@property (assign) float rangedDamageRate;
@property (assign) float rangedLastDamageTime;
@property (strong) NSString * rangedSound;

And open GameObject.m and add the method you removed from Monster.m:

- (void)updateRanged:(ccTime)dt {
    
    if (!self.isRanged) return;
    
    GameObject * enemy = [self.layer closestEnemyToGameObject:self];
    if (!enemy) {
        enemy = [self.layer playerForTeam:[self.layer oppositeTeam:self.team]];
    }
    if (!enemy) return;
    
    float distance = ccpDistance(self.position, enemy.position);
    static float WIGGLE_ROOM = 5;
    if (ABS(distance) <= (self.rangedRange + WIGGLE_ROOM) && CACurrentMediaTime() - self.rangedLastDamageTime > self.rangedDamageRate) {
        
        [[SimpleAudioEngine sharedEngine] playEffect:self.rangedSound];
        
        self.rangedLastDamageTime = CACurrentMediaTime();
        
        Laser * laser = [self.layer createLaserForTeam:self.team];
        laser.position = self.position;
        laser.meleeDamage = self.rangedDamage;
        
        CGPoint direction = ccpNormalize(ccpSub(enemy.position, self.position));
        static float laserPointsPerSecond = 100;
        static float laserDistance = 1000;
        
        CGPoint target = ccpMult(direction, laserDistance);
        float duration = laserDistance / laserPointsPerSecond;
        
        laser.rotation = -1 * CC_RADIANS_TO_DEGREES(ccpToAngle(direction));
        laser.zOrder = 1;
        
        [laser runAction:
         [CCSequence actions:
          [CCMoveBy actionWithDuration:duration position:target],
          [CCCallBlock actionWithBlock:^{
             [laser removeFromParentAndCleanup:YES];
         }], nil]];
    }
    
}

And finally call this in update:, right after the check if maxHp is 0:

[self updateRanged:delta];

Now that the properties and logic are in the base class, you can set up your castles to shoot. Open Player.m and add the following code inside initWithSpriteFrameName:

self.isRanged = YES;
self.rangedRange = 200;
self.rangedDamageRate = 2.0;
self.rangedDamage = 5;
self.rangedSound = @"pew.wav";

Build and run, and now your castle should shoot at the enemies, adding a new layer of fun and strategy!

A shooting castle

However, your code is a big mess! Let’s take a look and discuss why it’s getting messy – and likely to get even messier in the future.

Contributors

Over 300 content creators. Join our team.