In my game I have thousands of "tile" nodes which make up a game map (think simcity), I am wondering what the most frame-rate/memory efficient route for texturing and animating each node would be? There a a handful of unique tile "types" which each have their own texture atlas / animations, so making sure textures are being reused when possible is key.
All my tile
nodes are children of a single map
node, should the map node handle recognising a tile type and loading the necessary atlas & animations (e.g. by loading texture & atlas names from a plist?)
Alternatively, each tile type is a certain subclass. Would it be better for each SKSpriteNode tile to handle their own sprite atlas loading e.g. [tileInstance texturise];
(how does sprite kit handle this? would this method result in the same texture atlas being loaded into memory for each instance of a certain tile type?)
I have been scrounging the docs for a deeper explanation of atlases and texture reusage but I don't know what the typical procedure is for a scenario like this. Any help would be appreciated, thanks.
Memory first: there won't be any noticeable difference. You have to load the tile's textures, textures will account for at least 99% of the memory of the Map+Tiles and that's that.
Texture reuse: textures are being reused/cached automatically. Two sprites using the same texture will reference the same texture rather than each having its own copy of the texture.
Framerate/Batching: this is all about batching properly. Sprite Kit approaches batching children of a node by rendering them in the order they are added to the children array. As long as the next child node uses the same texture as the previous one, they will all be batched into one draw call. Possibly the worst thing you could do is to add a sprite, a label, a sprite, a label and so on. You'll want to add as many sprites using the same texture in consecutive order as is possible.
Atlas Usage: here's where you can win the most. Commonly developers try to categorize their atlases, which is the wrong way to go about it. Instead of creating one atlas per tile (and its animations), you'll want to create as few texture atlases as possible, each containing as many tiles as possible. On all iOS 7 devices a texture atlas can be 2048x2048 and with the exception of iPhone 4 and iPad 1 all other devices can use textures with up to 4096x4096 pixels.
There are exceptions to this rule, say if you have such a large amount of textures that you can't possibly load them all at once into memory on all devices. In that case use your best judgement to find a good compromise on memory usage vs batching efficiency. For example one solution might be to create one or two texture atlases per each unique scene or rather "scenery" even if that means duplicating some tiles in other texture atlases for another scene. If you have tiles that almost always appear in any scenery it would make sense to put those in a "shared" atlas.
As for subclassing tiles, I'm a strong proponent to avoid subclassing node classes. Especially if the main reason to subclass them is to merely change which texture they are using/animating. A sprite already is a container of a texture, so you can as well change the sprite texture and animate it from the outside.
To add data or additional code to a node you can peruse its userData property by creating your own NSMutableDictionary and adding any object you need to it. A typical component-based approach would go like this:
SKSpriteNode* sprite = [SKSpriteNode spriteWithWhatever..];
[self addChild:sprite];
// create the controller object
sprite.userData = [NSMutableDictionary dictionary];
MyTileController* controller = [MyTileController controllerWithSprite:sprite];
[sprite.userData setObject: forKey:@"controller"];
This controller object then performs any custom code needed for your tiles. It could be animating the tile and whatever else. The only important bit is to make the reference to the owning node (here: sprite) a weak reference:
@interface MySpriteController
@property (weak) sprite; // weak is important to avoid retain cycle!
@end
Because the sprite retains the dictionary. The dictionary retains the controller. If the controller would retain the sprite, the sprite couldn't deallocate because there would still be a retaining reference to it - hence it will continue to retain the dictionary which retains the controller which retains the sprite.
The advantages of using a component-based approach (also favored by and implemented in Kobold Kit):
This is a good introduction on component-based design. The hybrid approach is certainly the way to go. Here are more resources on component based design but I strongly advice against straying from the path and looking into FRP as the "accepted answer's author" suggests - FRP is an interesting concept but has no real world application (yet) in game development.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With