Loading assets before entering the game, loading screen.

Forums Programming Programming – Everything else Loading assets before entering the game, loading screen.

This topic contains 42 replies, has 21 voices, and was last updated by  jesod 1 year, 1 month ago.

Viewing 25 posts - 1 through 25 (of 43 total)
Author Posts
Author Posts
October 2, 2009 at 9:35 am #217685

natanavra
@natanavra

Hello, don’t really know where to write this thread, but I figured it’s a general obj-C question…

OK, so here’s the situation, whenever I click the new game button in my game, it goes straight to the game and I’m experiencing a bit of slowdown for like 2-5 seconds, and then it stabilizes…

I figured that my managers (pre-allocated, reused enemy/bullet managers) are being loaded into memory.

Now, I heard something with NSOperation for asynchronous loading…

So I’m thinking on display a Loading Scene and load stuff into memory, and afterwards start the game (to avoid those slowdowns).

Maybe NSOperation isn’t the solution, but I’d like to hear which methods you people use…

Any pointers, referrals and explanations on how to do that will be greatly appreciated!!

P.S. jd I noticed that DMD! had a loading screen, maybe you can explain a bit about what you do there?

~ Natanavra.

October 2, 2009 at 10:11 am #262505

Thomvis
@thomvis

I created a few classes to handle asynchronous loading. The things (objects) that can be loaded implement the Loadable protocol. The object that receives notifications on the loading progress implements the LoadableDelegate protocol. And a Loader class is used to load any number of Loadables asynchronous.

Loadable: http://gist.github.com/199598

LoadableDelegate: http://gist.github.com/199599

Loader: .h: http://gist.github.com/199601; .m: http://gist.github.com/199600

Have a look and I hope it is of any use or inspiration.

October 2, 2009 at 1:18 pm #262506

Osiris
@osiris

@Nat – I used async loading in SPIRUS.

Stuff like:

[[TextureMgr sharedTextureMgr] addImageAsync:@"image.png" target:self selector:@selector(imageLoaded:)];

and then created an if() in imageLoaded: method to track the count of my assets. for every count, I would increase the “loading:” method and when the count was complete, I would continue on.

Since then I polished up the MVC-ness of SPIRUS and do eveything scene to scene and found I only needed to load the assets that were immediately necessary and so remove the entire loading architecture. For bigger games with lots of required assets, it would still work well.

October 2, 2009 at 1:51 pm #262507

Receptor
@receptor

I used NSOperation in my game to load stuff asynchronously and it’s pretty simple.

You just need to create an operation queue (maybe in your application delegate) and then inherit from NSOperation and reimplement few methods.

Then you create your NSOperation class and add it to the queue.

If you think you’re gonna reuse this many times, probably Thomvis code is better!

October 2, 2009 at 3:27 pm #262508

natanavra
@natanavra

@Thomis I’m having a hard time of understanding the practical usage of your code, I guess I should read up about @protocol and NSOperation as I’m not familiar with those at all.

@Osiris Thanks, this seems the easier way to go.

@Receptor I guess I gotta read about NSOperation

Thanks everyone, I guess I’ll read up a bit more and then decide on which way to go…

Right now, I’m thinking about the loadable objects, it seems like the right way.

October 2, 2009 at 9:04 pm #262509

lmauri
@lmauri

I think the thomvis way is the better way…I came from c# programming(since 2003) and I think the better way is to use interface (aka protocol here in objective-c) to get the best polished code and reusable programming model…

October 9, 2009 at 11:00 pm #262510

Aaron Koolen
Participant
@aaron-koolen

@ Thomvis if you’re still looking at this thread, I have a question. I have tried to implement the classes and protocols that you have specified and I was wondering if there’s anything else that needs taking care of. I am getting a compilation warning about loadable:reportingProgress not being found in the delegate protocol, even though it’s there for sure. Then when my reportingProgress is summed and fired to my delegate it’s receiving a 0 progress, even though I am passing in 1.

I don’t have any experience with NSOperation or threading on the iPhone and and wondering if there’s something else I need to do/handle?

Essentially I have made my graphical classes Loadable, added them to an array, with my main game scene being a Loader. Then I load with the scene as the delegate to be told of the progress. Logic seems to work, and I get into the methods OK I just don’t get the right progressed passed in.

Any thoughts.

iPhone 3gs although I’m testing on simulator

XCOde 3.2

October 10, 2009 at 10:57 am #262511

Thomvis
@thomvis

Without seeing your code I can’t say what your problem is. But if I understand you well, you subclassed Loader, which is not what you should.

I created a small sample project that illustrates the use of my way of loading. I hope it is similar to your situation so it will help you. These aspects are shown:

  • The use of Loadable and LoadableDelegate
  • The use of Loader to load one Loadable asynchronously
  • The use of Loader to load several Loadables synchronously
  • An Loadable being a LoadableDelegate at the same time (happens a lot)
  • Using a Loader as part of the loading progress of a Loadable and redirect the Loader’s progress reports

GameScene: .h http://gist.github.com/206771, .m http://gist.github.com/206772

LoadProgressLayer: .h: http://gist.github.com/206773, .m: http://gist.github.com/206775

GameLayer: .h: http://gist.github.com/206776, .m: http://gist.github.com/206777

GraphicObject: .h: http://gist.github.com/206779, .m: http://gist.github.com/206780

And somewhere in your AppDelegate (main class):

GameScene *gs = [[GameScene alloc] init];
[[Director sharedDirector] runWithScene: gs];
[gs loadAndStart];

Have a look, I hope it will help you. Let me know if you have any questions (accompanied with your code, if necessary).

November 16, 2009 at 9:33 am #262512

DonomaGames
Participant
@donomagames

I came up on this thread searching for ways to implement a loading screen. (I wanted something better than my activity indicator). I ended up using NSOperation and it works beautifully.

AFAIK, you can’t inherit from 2 different classes so inheriting from NSOperation when most of my objects are inherited from Layer wouldn’t work. Instead I created a small class that handled loading of the actual layer.

Also remember that cocos2d is NOT thread safe! So always use something like this

-(void) main

{

[wpnLayer

performSelectorOnMainThread:@selector(loadResources:)

withObject:self

waitUntilDone:YES];

}

Took me quite a while to figure out why I was loading white sprites.

November 16, 2009 at 12:08 pm #262513

natanavra
@natanavra

@DonomaGames I had white sprites myself, when using NSOperation… Could you share a bit with us about your NSOperation implementation.

~ Natanavra.

November 16, 2009 at 12:20 pm #262514

DonomaGames
Participant
@donomagames

The white sprite problem was fixed when I used performSelectorOnMainThread in the main method.

Basically I would have something like this:

@interface WeaponLayerLoader : NSOperation
{
WeaponLayer *wpnLayer;
}

@implementation WeaponLayerLoader
@synthesize wpnLayer;

-(void) main
{
[wpnLayer
performSelectorOnMainThread:@selector(loadResources:)
withObject:self
waitUntilDone:YES];
}

Before doing this, I did this which caused the white sprites.

@implementation WeaponLayerLoader
@synthesize wpnLayer;

-(void) main
{
[wpnLayer loadResources];
}

Drove me almost insane for an hr trying to figure out what happened.

In the WeaponLayer object, I have this:

-(id) init{

if(self = [super init]){

isTouchEnabled = YES;
WeaponLayerLoader *loader = [[WeaponLayerLoader alloc] init];
loader.wpnLayer = self;
[[[[UIApplication sharedApplication] delegate] queue] addOperation:loader];
[loader release];
}
return self;
}

-(void) loadResources:(id)sender
{
//Load the sprites here!

[[[UIApplication sharedApplication] delegate]
performSelectorOnMainThread:@selector(reportProgressDone:)
withObject:self
waitUntilDone:YES];
}

November 16, 2009 at 12:51 pm #262515

natanavra
@natanavra

@DonomaGames Thank you, seems the simplest way… I used asynLoading and some what of fake loading in my game…

~ Natanavra.

November 20, 2009 at 1:44 pm #262516

badawe
Participant
@badawe

@DonomaGames can you explain to me some things?!

1 – I really don’t understand how things work in your example: You have on layer loading everything you need?

– When the “Gameplay layer” comes you call the loading resource, and in this you have on async load? or just loading in the normal way?

- This callback:

[[[UIApplication sharedApplication] delegate]
performSelectorOnMainThread:@selector(reportProgressDone:)
withObject:self
waitUntilDone:YES];

Change what in the delegate? When its complete? And in the App Delegate what you call?

I can do something like that:

From menu, i replace scene with my gameplay, in my init i call this method:

[wpnLayer
performSelectorOnMainThread:@selector(loadResources:)
withObject:self
waitUntilDone:YES];

in my loadResouces:

-(void) loadResources:(id)sender
{
//Load the sprites here!

asmWeb1 = [AtlasSpriteManager spriteManagerWithFile:@"Teia1_SpriteSheet.png"];
//OR?
[[TextureMgr sharedTextureMgr] addImageAsync:@"Teia1_SpriteSheet.png"d target:self selector:@selector(imageLoaded:)];

[self
performSelectorOnMainThread:@selector(reportProgressDone:)
withObject:self
waitUntilDone:YES];
}

can work this way?

November 26, 2009 at 8:37 pm #262517

eoin
Participant
@eoin

I think that

[wpnLayer
performSelectorOnMainThread:@selector(loadResources:)
withObject:self
waitUntilDone:YES];

removes the usefulness of the secondart thread, right? you are calling into loadResources on the main thread and waiting for it.

As far as I can see the init method in my main screen is the place to do this. My loading of actions and textures seems to take about a second so I am ok with that, the splash screen just appears for longer.

December 23, 2009 at 4:30 pm #262518

DonomaGames
Participant
@donomagames

@badawe

I don’t check the forum everyday so I didn’t see your question. Hopefully you’ve solved it by now.

- When the “Gameplay layer” comes you call the loading resource, and in this you have on async load? or just loading in the normal way?

Just load it the normal way. NSOperation will take care of loading it in the background.

[[[UIApplication sharedApplication] delegate]
performSelectorOnMainThread:@selector(reportProgressDone:)
withObject:self
waitUntilDone:YES];

Change what in the delegate? When its complete? And in the App Delegate what you call?

This changes my progress bar. I would then call the current running scene which is the loading scene. I have this:

-(void) reportProgressDone:(id)obj
{
[loadingScene increasePercentage:6];
[self showGameplayScene];
}

I have another function called reportProgress that only increments the percentage and not change the scene. It’s used for everything else. I’m sure there’s a better way to structure this, but I haven’t refactored my code yet.

@eorin

When loading stuff, my FPS drops to about 1-4, but that’s fine since the loading screen is just a static image with a progress bar that moves slowly. If your load time is only a second or two, don’t even bother with a load screen w/ progress bar. Mine takes 5+ seconds and sometimes even I don’t know if it’s crashed/hung so I decided to implement a progress bar.

January 27, 2010 at 12:38 pm #262519

tanis
Participant
@tanis

How did you solve the white sprite problem?

I have the following code that seems to work but I keep getting the white sprite even though the correct texture is actually being loaded :-P

-(void)addInfo:(id)sender
{
CGSize size = [[Director sharedDirector] winSize];

// Donate Sprite
donateSprite = [[AdSprite alloc] initWithImage:wsFinalImage link:wsFinalLink];
donateSprite.position = ccp(size.width/2, 480-287);
[self performSelectorOnMainThread:@selector(addChild:)
withObject:donateSprite
waitUntilDone:YES];
}

-(void) onEnter
{
[super onEnter];

NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(addInfo:) object:nil];
[[[ShakeQuakeAppDelegate get] operationQueue] addOperation:op];
[op release];

}

March 5, 2010 at 5:58 am #262520

Tiny Technician
@nobody

I’ve been trying to implement the WeaponLayerLoader but with no success. Could someone post a code example that shows how the scene loads with everything talking correctly to each other.

I have things setup similar to the example code given but I keep getting compile errors/warnings, especially on the

[[[UIApplication sharedApplication] delegate]

performSelectorOnMainThread:@selector(reportProgressDone:)

withObject:self

waitUntilDone:YES];

I have this in my gamelayer but it gives me a warning, am I missing header information etc?

Thanks

March 5, 2010 at 6:10 am #262521

DonomaGames
Participant
@donomagames

Ack… I don’t check the forum everyday… but if you PM me or email me, I’d be able to reply sooner.

@tanis

Hope you’ve already fix your problem.

I think you need to restructure your code a little bit.

-(void)addInfo:(id)sender
{
[self performSelectorOnMainThread:@selector(loadSprites:)
withObject:self
waitUntilDone:YES];
}

-(void)loadSprites:(id)sender
{
CGSize size = [[Director sharedDirector] winSize];

// Donate Sprite
donateSprite = [[AdSprite alloc] initWithImage:wsFinalImage link:wsFinalLink];
donateSprite.position = ccp(size.width/2, 480-287);
}

@nobody

I just finished updating my game so I have some time. I’ll put together a simple example project and post it. After lunch. :)

March 5, 2010 at 10:19 am #262522

DonomaGames
Participant
@donomagames

loading screen sample

Just a sample, shouldn’t be used for anything real, no guarantee, etc, etc.

Really, it’s just shows how to use NSOperation. I’m sure there are a ton of better ways of doing it where it’s more flexible, etc.

March 5, 2010 at 7:53 pm #262523

Tiny Technician
@nobody

@DonomaGames

Awesome, thanks so much. I see you’re using the updated cocos2d framework. I’m too close to release to switch to the new version but I’ll be able to convert it back. From looking at your code briefly, I can see the areas I messed up on. So close, about 90% setup properly.

Thanks again.

March 6, 2010 at 8:13 pm #262524

Tiny Technician
@nobody

Got it working, yay. Thanks so much for the example.

The only thing I can’t figure out how to do (which is fine) is to get an animation playing on the loading screen. It just seems to display the sprites I add in the init. I add my own sprite class on the loadingscene init called loadersprite that loops through a little loading animation but it doesn’t seem to play. If I add it anywhere else it plays fine. I’m guessing it’s how the loading works (somehow blocking my sprite class from playing the animation). If anyone has a little loading animation working on their loading scene let me know.

Thanks

March 8, 2010 at 10:05 am #262525

Joao Caxaria
Participant
@caxaria

Hi guys,

I see this has been solved but just wanted to share a bit more. The way I do you won’t need special method to exist (loadAsync’s, etc), so it’s usable more or less with any framework you may be working on.

The good thing about this, I think, is that you can just develop as you normally would and, when the need to have a loading screen comes up, just cut and paste the code in the init method into here and it will just work…

-(id) init

{

[super init];

NSThread* thread = [[[NSThread alloc] initWithTarget:self selector:@selector(initialLoading) object:nil] autorelease];

[thread start];

return self;

}

-(void) initialLoading

{

NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];

EAGLContext *k_context = [[[EAGLContext alloc]

initWithAPI :kEAGLRenderingAPIOpenGLES1

sharegroup:[[CCDirector sharedDirector] openGLView] context] sharegroup autorelease];

[EAGLContext setCurrentContext:k_context];

//HERE JUST LOAD YOUR SPRITES, MUSIC, JUST LIKE YOU PROBABLY HAVE IN YOUR INIT METHOD

//NOTIFY YOUR LOADING SCREEN THAT LOADING IS FINISHED (NSNOTIFICATIONCENTER, SIMPLE DELEGATE, ...)

[autoreleasepool release];

}

The code was done long ago with help from the forum members so I can’t be credited for most of it. Hope it helps someone.

April 15, 2010 at 6:21 pm #262526

Tiny Technician
@nobody

Just FYI, I was able to get an animation playing on the loading screen. Here’s the code I did to get it working.

//
// LoadingScene.m
// Pet Tricks
//
// Created by TinyTechnician on 8/21/09.
// Copyright 2009 Tiny Tech Studios. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "LoadingScene.h"
#import "cocos2d.h"

#define degreesToRadian(x) (M_PI * (x) / 180.0)

@implementation LoadingScene

@synthesize loadingView;

-(id)init
{
self = [super init];
if (self != nil)
{
Sprite *bg = [Sprite spriteWithTexture:TextureMgr sharedTextureMgr] addImage:@"bg_loading.png";
[bg setPosition:ccp(240, 160)];
[self addChild:bg z:0];

NSArray *tempImages = [NSArray arrayWithObjects:[UIImage imageNamed:@"loader_01.png"],
[UIImage imageNamed:@"loader_02.png"],
[UIImage imageNamed:@"loader_03.png"],
[UIImage imageNamed:@"loader_04.png"],
[UIImage imageNamed:@"loader_05.png"],
[UIImage imageNamed:@"loader_06.png"],
[UIImage imageNamed:@"loader_07.png"],
[UIImage imageNamed:@"loader_08.png"],
[UIImage imageNamed:@"loader_09.png"],
[UIImage imageNamed:@"loader_10.png"],
[UIImage imageNamed:@"loader_11.png"],
[UIImage imageNamed:@"loader_12.png"],
[UIImage imageNamed:@"loader_13.png"],
[UIImage imageNamed:@"loader_14.png"],
[UIImage imageNamed:@"loader_15.png"],
[UIImage imageNamed:@"loader_16.png"],
[UIImage imageNamed:@"loader_17.png"],
[UIImage imageNamed:@"loader_18.png"],
[UIImage imageNamed:@"loader_19.png"],
[UIImage imageNamed:@"loader_20.png"],
[UIImage imageNamed:@"loader_21.png"], nil];

UIImageView *tempAnimatedView = [UIImageView alloc];
[tempAnimatedView initWithFrame:CGRectMake(240 , 40, 480, 64)];
tempAnimatedView.animationImages = tempImages;
tempAnimatedView.animationDuration = 2.0; // seconds
tempAnimatedView.animationRepeatCount = 0; // 0 = loops forever
self.loadingView = tempAnimatedView;
self.loadingView.center = ccp(40.0, 240.0);
self.loadingView.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
[tempAnimatedView release];
[[[Director sharedDirector] openGLView] addSubview:loadingView];

}
return self;
}

-(void)Start
{
[loadingView startAnimating];
}

-(void)Stop
{
[loadingView stopAnimating];
}

-(void)dealloc
{
[loadingView release];
[[TextureMgr sharedTextureMgr] removeAllTextures];
[super dealloc];
}

@end

[/code]

And then in my AppDelegate I start and stop the animation, using the loading code template by DonomaGames

// Called when player starts game on MainMenuScene -(void)startGame:(id)sender { // Display loading screen while loading stuff in the background [[Director sharedDirector] replaceScene:loadingScene]; [loadingScene Start]; // Load all the resources. This will not block the main thread bgLayer = [BackgroundLayer node]; gameLayer = [GameLayer node]; hudLayer = [HUD_Layer node]; } // Called when everything is done loading from GameLayer -(void)reportProgressDone:(id)obj { [loadingScene Stop]; GameScene *gameScene = [[GameScene node] retain]; [gameScene initWithBackground:bgLayer AndGame:gameLayer AndHUD:hudLayer]; // Everything is now loaded, start game [[Director sharedDirector] replaceScene:gameScene]; [loadingScene release]; }

// Called when player starts game on MainMenuScene
-(void)startGame:(id)sender
{
// Display loading screen while loading stuff in the background
[[Director sharedDirector] replaceScene:loadingScene];
[loadingScene Start];

// Load all the resources. This will not block the main thread
bgLayer = [BackgroundLayer node];
gameLayer = [GameLayer node];
hudLayer = [HUD_Layer node];
}

// Called when everything is done loading from GameLayer
-(void)reportProgressDone:(id)obj
{
[loadingScene Stop];

GameScene *gameScene = [[GameScene node] retain];
[gameScene initWithBackground:bgLayer AndGame:gameLayer AndHUD:hudLayer];

// Everything is now loaded, start game
[[Director sharedDirector] replaceScene:gameScene];

[loadingScene release];
}

April 17, 2010 at 4:26 am #262527

reedolsen
@reedolsen

@DonomaGames Thanks for the code sample. It appears as though there is a bug that is causing a major memory leak.

The WorldLayerLoader retains a copy of the WorldLayer object and never releases it. This is remedied by simply writing a dealloc method for the WorldLayerLoader class:

- (void) dealloc
{
[worldLayer release];
[super dealloc];
}

I hope this helps someone out!

April 17, 2010 at 5:58 pm #262528

ShempLock
@shemplock

@DonomaGames – Don’t take this the wrong way, but in reading your code, you are blocking and not really using multiple threads. Whenever you use “waitUntilDone:YES”, that is a sign you are blocking (blocking the thread that called the function).

Viewing 25 posts - 1 through 25 (of 43 total)

You must be logged in to reply to this topic.