If you are building a game for an iPhone then most probably, at some point of time, you will realize that you need a loading screen in which some (or even all) of your game assets should be preloaded. If you are not going to preload it but rather load it on demand, then you might experience some performance issues when a resource is initially loaded.
For my game I am using a cocos2d framework which has some convenient methods for preloading both textures and sounds, those are:
[[CCTextureCache sharedTextureCache] addImage:path]
for textures, and:
[[SimpleAudioEngine sharedEngine] preloadEffect:path]
for sounds, if you are using SimpleAudioEngine from CocosDenshion.
A requirement for my game was to preload all of my game resources from an application bundle automatically during a loading screen and display a progress of assets preloading. A solution that I came up with was the following.
@protocol ResourceLoader
First of all, as described earlier, we have two different methods of preloading textures and sounds and our ResourceLoader should be generic enough to handle not only those two specific cases but also any others so it could be used also with other game frameworks. ResourceLoader specifies a contract that specific classes should implement, this is also an extension point for a custom implementations, for example if you are leveraging a different framework than cocos2d or have a custom requirements for loading your assets.
@protocol ResourceLoader
- (void)loadResource:(NSString*)path;
@end
By default, I provided two custom implementation classes:
- TextureLoader - for loading textures using cocos2d,
- SoundLoader - for loading sound effects using CocosDenshion,
We need to do some tricks with OpenGL context in TextureLoader - we will be later on executing those methods from a non-UI thread and we need to ensure that we are loading textures into the main GL context.
@interface ResourcesLoader
ResourceLoader is a main component that we will use to load resources asynchronously during a loading screen.
@interface ResourcesLoader
@property (nonatomic, retain) NSMutableSet *resources;
@property (nonatomic, retain) NSDictionary *loaders;
+ (id)sharedLoader;
- (void)addResources:(id)firstResource, ... NS_REQUIRES_NIL_TERMINATION;
- (void)loadResources:(id)delegate;
@end
and a delegate that will inform us of a progress change:
@protocol ResourceLoaderDelegate
- (void)didReachProgressMark:(CGFloat)progressPercentage;
@end
Implementation
Implementation is itself quite trivial. First of all we need to register our custom resource loaders in ResourceLoader:
self.loaders = [NSDictionary dictionaryWithObjectsAndKeys:
[TextureLoader loader], @"png",
[SoundEffectLoader loader], @"wav", nil];
Then, we need to simply iterate over resources list, and for each resource find a registered loader (by resource extension) and execute its loadResource method. Wrapping resource loading in NSBlockOperation will allow us to execute it on a NSOperationQueue in a background.
for (NSString *resource in self.resources) {
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSString *key = [resource pathExtension];
id loader = [self.loaders objectForKey:key];
[loader loadResource:resource];
...
}];
}
When a resource is loaded we need to notify a delegate that a loading progress has changed (we need to do it on a main thread):
float progress = (float)_loadedResources / [self.resources count];
[[NSThread mainThread] performBlock:^ {
[delegate didReachProgressMark:progress];
} waitUntilDone:NO];
Usage
That's the easiest part, just put the following in your loading scene init method to initialize a loader with resources:
// load resources
ResourcesLoader *loader = [ResourcesLoader sharedLoader];
NSArray *extensions = [NSArray arrayWithObjects:@"png", @"wav", nil];
for (NSString *extension in extensions) {
NSArray *paths = [[NSBundle mainBundle] pathsForResourcesOfType:extension inDirectory:nil];
for (NSString *filename in paths) {
filename = [[filename componentsSeparatedByString:@"/"] lastObject];
[loader addResources:filename, nil];
}
}
// load it async
[loader loadResources:self];
and implement a delegate method:
- (void) didReachProgressMark:(CGFloat)progressPercentage {
[_progress setPercentage:progressPercentage * 100];
if (progressPercentage == 1.0f) {
[_loadingLabel setString:@"Loading complete"];
}
}
And THAT'S IT, if you are looking for a source code, then you can find a complete XCode project attached here.