Multiple Components of the Same Type

For some kinds of components, one per entity is enough. An entity can’t have two transforms; it will only ever be in one place. However, a single entity may have need to have several sprites. I’ve expanded the engine to allow for multiple components of the same type.

For a quick example of when this might come in handy, consider a game where the player can customize his character by swapping out hair, clothes, armor, weapons, and so on. None of these components are really separate entities; they all depend on the character entity. We could implement this by making a bunch of entities and setting the character entity as their “parent,” so that they all follow the character when he moves. In some cases, where each sprite actually has some logic of its own (like the difference between a gun and a sword), this might be necessary. But for things like hair or clothes, that will always work the same way, this method is overkill.

A better solution is to allow the entity to add more than one of each type of component. We’ll do this by changing the base entity class to keep a dictionary of arrays rather than a dictionary of components. The componentOfType: method can stay for convenience, we will also add a new method, componentsOfType: , which systems can use for types of components of which there can be more than one. Here are the updated methods:

- (void)addComponent:(LGComponent *)component { NSString *type = [[component class] type]; NSMutableArray *array = [components objectForKey:type]; if(array == nil) { array = [NSMutableArray array]; [components setObject:array forKey:type]; } [array addObject:component]; } - (NSArray *)componentsOfType:(NSString *)type { return [components objectForKey:type]; } - (id)componentOfType:(NSString *)type { return [[components objectForKey:type] firstObject]; } - (BOOL)hasComponentOfType:(NSString *)type { return [self componentsOfType:type] != nil; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 - ( void ) addComponent : ( LGComponent * ) component { NSString *type = [ [ component class ] type ] ; NSMutableArray *array = [ components objectForKey :type ] ; if ( array == nil ) { array = [ NSMutableArray array ] ; [ components setObject :array forKey :type ] ; } [ array addObject :component ] ; } - ( NSArray * ) componentsOfType : ( NSString * ) type { return [ components objectForKey :type ] ; } - ( id ) componentOfType : ( NSString * ) type { return [ [ components objectForKey :type ] firstObject ] ; } - ( BOOL ) hasComponentOfType : ( NSString * ) type { return [ self componentsOfType :type ] != nil ; }

The change doesn’t break existing systems or calls to componentOfType: , but now we can get a list of components given a type. As more systems are implemented, developers can make use of this new functionality.

I’ve updated the sprite rendering system to make use of the new method so that entities with many sprite components are rendered correctly. The change adds an inner loop to each sprite update call. Here’s the sprite rendering system’s update method:

for(LGEntity *entity in self.entities) { NSArray *sprites = [entity componentsOfType:[LGSprite type]]; LGTransform *transform = [entity componentOfType:[LGTransform type]]; for(LGSprite *sprite in sprites) { [self updateRender:sprite withTransform:transform]; if([sprite visible] && [[sprite state] isAnimated]) { [sprite incrementAnimationCounter]; if([sprite animationCounter] == 0) { [sprite nextPosition]; } } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 for ( LGEntity *entity in self . entities ) { NSArray *sprites = [ entity componentsOfType : [ LGSprite type ] ] ; LGTransform *transform = [ entity componentOfType : [ LGTransform type ] ] ; for ( LGSprite *sprite in sprites ) { [ self updateRender :sprite withTransform :transform ] ; if ( [ sprite visible ] && [ [ sprite state ] isAnimated ] ) { [ sprite incrementAnimationCounter ] ; if ( [ sprite animationCounter ] == 0 ) { [ sprite nextPosition ] ; } } } }

I’ve already converted the tile system so that rather than creating a bazillion entities, one for each tile, a single map entity is created with several tile sprites attached. Be sure to check out the full project on GitHub to see the entire thing. You’ll also notice that I’ve replaced the tile map parser with one that supports .tmx files generated by the Tiled Map Editor. The new parser is included as part of the engine now, rather than being something developers have to implement.