
Keep your game classes organized and logically ordered. Create a GameObject class, a GameCharacter class and then subclass these.
Its important to keep your Game Objects ordered as well as your code. The more you order your objects, the cleaner your code will be as well. A GameObject is anything used in a game from labels to sprites to power ups.
Its important to create a base class for all objects because, as a simple example, you want to be able to constrain all game objects to your screen. Its not unthinkable to believe that at some point you might inadvertently cause a game object to be pushed off screen. Thus it would be nice to have a method, something like keepObjectInBounds, to make sure you can call it on any or all objects to make sure they are not halfway or all the way offscreen.
After you build you base class, GameObject, you will subclass it to build objects. But you can also subclass GameObject to build functionally similar types of objects like a Powerups subclass or an Enemy subclass. For example, you might want all Powerup class objects to have a setAutoDestroyTime method that gives any object of that class a specific time to live onscreen before it disappears.
So let’s look at init methods, designated initializers, self and super.
You can create a GameObject Class in 1 file set or in different file sets. This is where self and super take hold in your brain. Self means, that very same class you are currently in. Super means its super or parent class. For example, lets say we create this hierarchy:
- GameObject (Everything is a GameObject)
- GameCharacter (Distinguish between GameCharacters and GamePowerUps)
- GameEnemy (Some GameCharacters are bad and some are good)
- GameBigBossEnemy
- GamePlayer
- GamePlayerHero
- GamePlayerFriend
- GameEnemy (Some GameCharacters are bad and some are good)
- GamePowerup
- GameCharacter (Distinguish between GameCharacters and GamePowerUps)
GameCharacter is a subclass of GameObject. Which is to say, GameObject is the parent of GameCharacter. This makes sense because your GameObjects may be characters that have a personality and animation and perform actions. But some GameObjects will just sit there, like trees and clouds. Clouds may be moved by wind and that is about it! But a GamePlayer, our hero, needs to have a lot more internal logic, his own Artificial Intelligence logic if you will.
By the same token, GamePlayer may include our hero instance but it may also include a friend instance, which may be like his sidekick or something. The logic for a friend will not include any methods to follow or attach our hero, whereas enemies will have to have that sort of logic.
This is the reason we subclass objects, in order to use a base functionality for all (all game objects must remain onscreen at all times) but give specific functionality to others (followMainPlayer method).
Lets say you’re inside GamePlayerHero and you are coding away. If you say
[self blowHisNose];
You are calling the blowHisNose method which should exist in our GamePlayerHero class. If it doesn’t exist in self, that object will look to its super for a corresponding method. You don’t need to call:
[super blowHisNose];
That is what object inheritance in OOP is all about. So if the method doesn’t exist in GamePlayerHero, the hero object looks to its parent class or super class, GamePlayer. If GamePlayer doesn’t have that method then it will keep going until it finds that method. If it doesn’t, your game will crash :).
So lets take a look at a practical example, an initializer. An initializer is just another name for a Class’ init method. Every class must have an init method. Here is a typical one:
-(id) init {
if((self=[super init]);
}
return self;
}
This means, if this class is equal to super’s init, then do this and return self. Since a class will always have a super class, this basically says, if I have a super class (which is always) then return myself.
Now if a child class (lets call it GameCharacter class) of this previous class (GameObject class) wants to add functionality to its parent class, then it will NOT OVERRIDE its parent class methods, it will simply add methods. So the new GameCharacter class could just have a method like this:
-(void)checkAndClampSpritePosition {
CGPoint currentSpritePosition = [self position];
CGSize levelSize = [[GameManager sharedGameManager]
getDimensionsOfCurrentScene];
float xOffset;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
// Clamp for the iPad
xOffset = 30.0f;
} else {
// Clamp for iPhone, iPhone 4, or iPod touch
xOffset = 24.0f;
}
if (currentSpritePosition.x < xOffset) {
[self setPosition:ccp(xOffset, currentSpritePosition.y)];
} else if (currentSpritePosition.x > (levelSize.width – xOffset)) {
[self setPosition:ccp((levelSize.width – xOffset),
currentSpritePosition.y)];
}
}
Which says, Im not initializing anything (because if it does, it will erase its parent’s init method), Im just adding this method to any instance of GameCharacter AND every instance of GameCharacter will still have to be initialized from GameObject’s init.
Let’s see a more practical example. Let’s look at an Enemy Class which can generate many different kinds of instances, all from the same code:
@interface ShipTarget : CCSprite {
int _curHp;
int _minMoveDuration;
int _maxMoveDuration;
NSString *filename;
}
@property (nonatomic, assign) int hp;
@property (nonatomic, assign) int minMoveDuration;
@property (nonatomic, assign) int maxMoveDuration;
@property (nonatomic, retain) NSString *filename;
@end
@interface Asteroid : ShipTarget {
}
+(id)target;
@end
This says, Im a ShipTarget class and my instances will all be based off of CCSprite. This means all my instances will have any methods and properties of CCSprite unless I override them (create new ones). At the same time, I wish to create an Asteroid class which derives from this ShipTarget class and it will return an object by calling its class method named target. Then in its implementation you have:
@implementation ShipTarget
@synthesize hp = _curHp;
@synthesize minMoveDuration = _minMoveDuration;
@synthesize maxMoveDuration = _maxMoveDuration;
@synthesize filename;
@end
@implementation Asteroid
+ (id)target {
Asteroid *target = nil;
if ((target = [[[super alloc] initWithFile:@”Comet.png”] autorelease])) {
target.hp = 1;
target.minMoveDuration = 2;
target.maxMoveDuration = 4;
}
return target;
}
@end
Notice again, the use of self, or target. If there is such a thing returned from calling target’s super class, (target is ShipTarget class and ShipTarget’s super class is CCSprite) initWithFile… then set those properties and return it. There is such a thing as target’s super class initWithFile method. CCSprite has an initWithFile method. Don’t believe me? Then don’t, right click over initWithFile and select Jump to Definition and watch Xcode take you to CCSprite:
-(id) initWithFile:(NSString*)filename
Great post, great blog! Thanks!
Thx