When I hit the retry button in my game, I want it to reload the MainScene. I am doing this with:
-(void)retry
{
SKTransition *transition = [SKTransition fadeWithDuration:.4];
MainScene *gameOver = [[MainScene alloc] initWithSize:self.size];
[gameOver didMoveToView:self.view];
[self.scene.view presentScene:gameOver transition:transition];
}
However, this is causing the memory/CPU usage to increase (by a lot) each time I hit retry. After about 10-20 retries, there is a noticeable lag.
I made all my SKEmitterNode and SKSpriteNode static and that fixed the memory problem, so I suspect that my sprites, emitters, etc are not being released from the memory and are being re-loaded every time I hit retry, doubling it.
I am loading the sprites/emitters like this:
#implementation MainScene {
SKEmitterNode *_bubbleEmitter;
SKSpriteNode *_sunglasses;
...
}
- (id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
_sunglasses = [SKSpriteNode spriteNodeWithImageNamed:#"sunglasses"];
[_sunglasses setPosition:CGPointMake(self.size.width/2, self.size.height + 10)];
[self addChild:_sunglasses];
...
}
return self;
}
Am I loading the sprites or the retry wrong?
This may or may not be the cause, but it's certainly wrong to call this method yourself:
[gameOver didMoveToView:self.view];
The didMoveToView: method is sent to the scene by the SKView when you present the scene. That means this method will actually run twice.
Also verify that your scenes are deallocating properly by implementing:
-(void) dealloc
{
NSLog(#"dealloc: %#", self);
}
Watch for the log or set a breakpoint to confirm the scene deallocates. If it isn't, check for memory leaks and retain cycles.
Related
I have a viewController with cocos scene which I push in my navigation controller. In this view controller I have this methods:
-(void) viewDidLoad
{
[super viewDidLoad];
[_cc3FrameView addSubview: [self createGLView]];
CC3Backgrounder.sharedBackgrounder.shouldRunTasksOnRequestingThread = YES;
}
- (void) viewWillAppear:(BOOL)animated
{
if (!sceneInitialized) {
sceneInitialized = YES;
[CCDirector.sharedDirector runWithScene: [[self makePanoramaScene] asCCScene]];
} else {
[CCDirector.sharedDirector resume];
}
[CCDirector.sharedDirector startAnimation];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[self runningPanoramaScene] sceneWillShow];
});
}
-(AxPanoramaScene *)runningPanoramaScene
{
CCScene *scene = [CCDirector.sharedDirector runningScene];
AxPanoramaLayer *panoramaLayer = [scene.children lastObject];
AxPanoramaScene *panoramaScene = (AxPanoramaScene *)panoramaLayer.cc3Scene;
return panoramaScene;
}
-(void)viewWillDisappear:(BOOL)animated
{
[CCDirector.sharedDirector pause];
}
While I push this controller - everything is working, but when I pop this controller and push it again - I got the bright pink screen and continuos messages in log:
2014-12-12 19:30:06.447 UniversalMapExample[2262:258353] cocos2d: animation started with frame interval: 60.00
2014-12-12 19:30:06.452 UniversalMapExample[2262:258353] cocos2d: surface size: 768x973
OpenGL error GL_INVALID_OPERATION detected at -[CCES2Renderer resizeFromLayer:] 161
2014-12-12 19:30:06.452 UniversalMapExample[2262:258353] Failed to make complete framebuffer object 0x8219
2014-12-12 19:30:06.453 UniversalMapExample[2262:258353] cocos2d: surface size: 768x973
OpenGL error GL_INVALID_OPERATION detected at -[CCES2Renderer resizeFromLayer:] 161
2014-12-12 19:30:06.453 UniversalMapExample[2262:258353] Failed to make complete framebuffer object 0x8219
OpenGL error GL_INVALID_OPERATION detected at -[CCRenderer(NoARCPrivate) setRenderState:] 232
OpenGL error GL_INVALID_OPERATION detected at -[CCRenderer(NoARCPrivate) setRenderState:] 232
OpenGL error GL_INVALID_OPERATION detected at -[CCRenderer(NoARCPrivate) setRenderState:] 232
OpenGL error GL_INVALID_OPERATION detected at -[CCRenderer(NoARCPrivate) setRenderState:] 232
[***GL ERROR***] GL_INVALID_VALUE: Numeric argument is out of range from glUseProgram(15).
[***GL ERROR***] GL_INVALID_OPERATION: Operation not allowed in current state from glUniform3fv(7, 4, (0.329, 0.944, 0.000)) setting u_cc3LightSpotDirectionModel.
[***GL ERROR***] GL_INVALID_OPERATION: Operation not allowed in current state from glUniformMatrix4fv(12, 1, GL_FALSE,
[0.021050, -0.007339, -0.000000, 0.210503
0.000000, -0.000000, 0.017596, -0.977556
0.005937, 0.017031, 0.000000, -0.660065
0.005926, 0.016997, 0.000000, 1.339256]) setting u_cc3MatrixModelViewProj.
How to correctly push the controller with cocos scene several times? I changed the example from cocos sources to this code. What am I doing wrong here? Please, note - that my controller is not CCDirector - it just contains a view with Cocos scene - the realization is like CC3DemoMultiScene. Thanks!
Remember that the CCDirector is a UIViewController, but it is also a singleton, which give it some unique nuances.
For example, you seem to be invoking the createGLView method every time you want to replace your controller. If it follows the CC3DemoMultiScene design, this will try to recreate another CCGLView before the old one has been released from the CCDirector singleton.
It's generally best to treat the CCDirector and the CCGLView that you create for it as a self-contained reusable unit. As you pop the containing controller, leave everything as is, and simply add and remove the CCGLView from the view hierarchy each time.
...Bill
This is a copy/paste from my blog where I covered a similar issue. The only difference is that I wanted full UIKit integration. I did have the problem where the 2nd time through. Perhaps it will help you.
http://www.notthepainter.com/full-cocos2d-uikit-integration/
I was working on a cocos2d based tapping game and I wasn’t able to run my game twice. It was clear that I wasn’t shutting the 1st game down correctly or building the 2nd game correctly, or both!
There are a lot of tutorials on the web out there teaching you how to add UIKit buttons to your Cocos2D app, or how to launch Cocos2D from your UIKit based app. But I needed to do both. I wanted a UIViewController under my game and UIKit widgets on top of my game. I spent a lot of time reading and this is what I came up with.
First, building the Xcode project was a nightmare. I eventually used the cocos2d/box2d template and then ripped out the files I didn’t needed, and added all my original files back in. The AppDelegate.m file looks just like a non-cocos2d app should look. This goes against the grain of many of the tutorials which advise you to build your cocos2d environment in the AppDelegate. I struggled with that, didn’t have luck for most of a Friday and then on Monday I put in a Cocos2DSingleton and it pretty much ran first time.
Here is my GameViewController’s viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:NO];
TTCocos2DSingleton *shared = [TTCocos2DSingleton sharedCocos2D];
CCGLView *glView = [shared currentGLView];
[self.view insertSubview:glView atIndex:1];
}
There are a view things to note. GameViewController has game UIButtons, score UILabels, and other game type of UI widgets. This lets me do a lot of the game controls in Interface Builder, not laying them out by hand. Notice I hide the status bar since the game is full-screen.
I get my cocos2d instance via the singleton, get its glView and insert this into the GameViewController’s view at index 1. This puts it below all the game controls. I’ll show you the sharedCocos2D method later, lets look at viewWillAppear.
- (void) viewWillAppear:(BOOL)animated
{
if(![[CCDirector sharedDirector] runningScene]){
CCScene *scene = [MyGameLayer scene];
myGame = [MyGameLayer node];
myGame.delegate = self;
[scene addChild: myGame];
[[CCDirector sharedDirector] runWithScene:scene];
} else {
// we have a scene already, replace the original to get a new game
[[CCDirector sharedDirector] startAnimation];
CCScene *scene = [MyGameLayer scene];
myGame = [MyGameLayer node];
myGame.delegate = self;
[scene addChild: myGame];
[[CCDirector sharedDirector] replaceScene:scene];
}
}
Notice how we treat the first run differently from the second run. For the second, and subsequent runs, we replace the scene with a new one. This avoids all “restarting” problems. Also notice that I set a delegate. I use a delegate protocol to communicate between my game layer and my UIViewController.
My singleton pattern comes from the Duck Rowing blog which I must admit is a pretty awesome name for a blog. I’m not going to show all the singleton code here, this blog is about cocos2d, but here is how I build my cocos2d environment.
+ (TTCocos2DSingleton *) sharedCocos2D;
{
static dispatch_once_t onceQueue;
dispatch_once(&onceQueue, ^{
if (sharedInstance) {
return;
}
sharedInstance = [[TTCocos2DSingleton alloc]init];
// Create an CCGLView with a RGB565 color buffer, and a depth buffer of 0-bits
sharedInstance->glView = [CCGLView viewWithFrame:[[UIScreen mainScreen] bounds]
pixelFormat:kEAGLColorFormatRGB565 //kEAGLColorFormatRGBA8
depthFormat:0 //GL_DEPTH_COMPONENT24_OES
preserveBackbuffer:NO
sharegroup:nil
multiSampling:NO
numberOfSamples:0];
[sharedInstance->glView setMultipleTouchEnabled:YES];
[sharedInstance setupDirector];
});
return sharedInstance;
}
The singleton sets up the CCGLView, enables multi-touch and then sets up the director. (I put that in another method since I thought, erroneously, that I’d need to call it elsewhere. Turns out I didn’t need to.)
- (void)setupDirector
{
CCDirectorIOS *director = (CCDirectorIOS*) [CCDirector sharedDirector];
[director setView:glView];
[director enableRetinaDisplay:YES];
director.wantsFullScreenLayout = YES;
[director setDisplayStats:NO];
[director setAnimationInterval:1.0/60];
}
And in setupDirector we set the usual suspects needed for a cocos2d app. Now the game can be played multiple times, I have a full UIViewController/UINavController underneath it, and I have UIKit widgets on top of my game. Nirvana.
I'm building a fairly simple game in SpriteKit. This is my first experience with SpriteKit and so far it has gone smoothly. I have gotten to the point now that I want to present a new SKScene when the player completes the game. I'm getting a Bad Access crash that I can't seem to diagnose.
I think I am presenting the scene correctly:
UnlockRockets *scene = [[UnlockRockets alloc] initWithSize:self.scene.size];
[self.view presentScene:scene];
Every time I get the following error on the presentScene: line - Thread 1: EXC_BAD_ACCESS (code=1, address = 0x10)
Looking at the thread trace it appears the crash might be originating at [SKNode isPaused]
Any advice would be great, I'm completely lost on this one.
i think problem in your initWithSize method inside UnlockRockets class
I have had the same issue with SKView present scene, even when scene was absolutely new without any configurations. So I solved it by using this.
myScene *newScene = [myScene sceneWithSize:size];
newScene.scaleMode = SKSceneScaleModeResizeFill;
SKView *currentskView = (SKView*) self.scene.view;
SKScene *currentScene = (SKScene*) self.scene;
[currentScene removeAllChildren];
[currentScene removeFromParent];
[currentskView presentScene:newScene];
also I've noticed that if declare strong reference for the scene - it's works in the way you did, but in that case scene live in memory even if it's invisible, and xCode notifies that there are memory warnings.
I stumbled upon a problem and I can't find the answer for it. I am working with the SK template from Xcode to create an iOS game. I am a beginner, so bear with me.
Basically I have this code:
SKAction *releaseBubbles = [SKAction sequence:#[
[SKAction performSelector:#selector(createBubbleNode)onTarget:self],
[SKAction waitForDuration:speed]]];
[self runAction: [SKAction repeatAction:releaseBubbles
count:300]];
which executes in
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
I change the level to my game in -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { and when I change the level it should also change that speed parameter. Of course, this doesn't work because I believe that my action is starting when the scene is initialised and I never get to switch the parameter.
What I need to do is populate the screen continuously with bubbles appearing at a certain pace (relative to the level).
I really have no clue how to fix this, because it seems to me like I need to stop and restart the action sequence somehow...
Looking forward to your valuable input.
To continuously populate the screen with bubbles you can use the update: method of your SKScene. Here is how to do it.
First, add a property that will store a date when you last added a bubble.
#property(nonatomic, strong) NSDate *lastBubbleCreationDate;
Then, change your update: method to:
-(void)update:(CFTimeInterval)currentTime
{
// Create new bubble every 5s.
if (ABS([_lastBubbleCreationDate timeIntervalSinceNow]) > 5)
{
[self createBubbleNode];
}
}
Finally, in your createBubbleNode method you have to store the time when you created last bubble:
-(void)createBubbleNode
{
// Your code here
// Set the date to now.
_lastBubbleCreationDate = [NSDate date];
}
You also need to call createBubbleNode to set the initial value of the _lastBubbleCreationDate. You can do this in didMoveToView: method. Just add this method to your scene implementation:
- (void)didMoveToView:(SKView *)view
{
// Creates first bubble and sets the initial value of the _lastBubbleCreationDate
[self createBubbleNode];
}
In next levels you can just change the 5s value to create bubbles more often which will make the game more difficult.
Does running a SKTransition on a SKScene destroy the origin SKScene?
For example:
SKTransition *reveal = [SKTransition revealWithDirection:SKTransitionDirectionDown duration:1.0];
GameConfigScene *newScene = [[GameConfigScene alloc] initWithSize: CGSizeMake(1024,768)]];
// Optionally, insert code to configure the new scene.
[self.scene.view presentScene: newScene transition: reveal];
Will the current scene be destroyed when the transition is executed? Or is still on memory? Has the new scene a reference to the old scene?
Lets assume that your scene property is like this #property(weak) SKScene *scene; then the answer is YES, will be destroyed when you present another scene or pop it from the stack.
If you have your property like #property(strong) SKScene *scene, then the answer is NO, your scene will stay in memory until you do this self.scene = nil;
But remember that the SKView retinas the presented scene so you should nil it somewhere in your app to avoid retain cycles (when you have strong property).
Adding to the previous answers, if you're still unsure and want to easily check when/if the original scene is deallocated, you can do this simply by overriding the dealloc method and logging a message/setting a breakpoint to see if it was invoked by the runtime. Put this in your old scene:
-(void)dealloc {
NSLog(#"Old scene deallocated");
}
By default (if not strongly referenced elsewhere), it will be deallocated after the entire transition has finished and the new scene has fully moved to your view. So, for the duration of the transition, both scenes will exist in the memory.
ARC will deallocated the old scene, unless you specifically strong referenced it elsewhere.
It is not referenced in the new scene by default.
Most of the tutorials that I've read only explain Cocos2D examples in the HelloWorld class, but as I've started to build a simple game I need to know how to send an event to different classes, and for them to respond whenever it happens.
I have GameSceneLayer, a CCLayer class which loads in my different CCLayers of Sprites:
#implementation GameSceneLayer
+ (CCScene *)scene {
CCScene *scene = [CCScene node]; // Create a container scene instance
GameSceneLayer *gameLayer = [GameSceneLayer node]; // Create an instance of the current layer class
[scene addChild:gameLayer]; // Add new layer to container scene
return scene; // Return ready-made scene and layer in one
}
-(id)init
{
self = [super init];
if (self != nil)
{
Background *background = [Background node];
[self addChild:background z:0];
Player *player = [player node];
[self addChild:player z:1];
MainMenu *mainMenu = [MainMenu node];
[self addChild:mainMenu z:2];
}
return self;
}
#end
However, when my MainMenu CCLayer START sprite is touched I would like it to spawn in the PLAYER sprite from the Player CCLayer.
I'm guessing that I need a GlobalVariables.h with something like:
#define gameStart #"0"
So when the START sprite is pressed it changes gameStart to 1, and somewhere in the PLAYER sprite there is
if (gameStart == 1)
{
[self addChild:PLAYER];
}
However I'm not sure how to set up the code so that the PLAYER sprite is always looking for that information.
You have objects (instances of classes). You want one object to communicate with another. In Objective-C, that's called sending messages. In other languages it's simply calling a method.
You don't need global variables. Instead, the receiving object MainMenu needs to send a message to (call a method on) the Player object. How do you get two objects to know each other? You could put them in a stinky, loud, overcrowded discotheque and hope for the best.
Or you could simply let them talk to each other, but alas, they shouldn't. Since both are siblings of GameSceneLayer, they shouldn't hold references to each other themselves (danger of creating retain cycles unless you're using weak references).
But both have the same parent. So what does a good parent do when two siblings won't talk to each other? It relays the message!
In MainMenu, send a message to the parent GameSceneLayer:
[(GameSceneLayer*)self.parent gameWillStart];
The GameSceneLayer implements that selector, and forwards the message to any other object that should be informed about starting the game:
-(void) gameWillStart
{
[player gameWillStart];
[ingameUI gameWillStart];
// etc.
}
And Player also implements said selector:
-(void) gameWillStart
{
[self addChild:PLAYER];
}
Just one other thing: in GameSceneLayer, make player and all other objects ivars (instance variables) so that GameSceneLayer has these references readily available. The alternative would be to tag (less often used) objects and then use getChildByTag:
PS: adding PLAYER as child looks dubious to me. If you have already created whatever node PLAYER is, you should add it right away and if necessary, set it to be invisible and/or paused while the game hasn't started yet.
You can use a singleton GameState Manager, which keeps information on the game state.
Here is a little snippet:
+(GameManager*) sharedGameManager {
if (!_sharedGameManager) {
_sharedGameManager = [[self alloc] init];
}
return _sharedGameManager;
}
+(id) alloc {
NSAssert(_sharedGameManager == nil, #"Cannot create second instance of singleton Game Manager");
return [super alloc];
}
- (id)init
{
self = [super init];
if (self) {
}
return self;
}
in that game manager you can have an enum with the game states, and set a property for it.
//Header
typedef enum {
kGameStarted,
kGameEnded
}GameState
#interface GameManager : CCNode {
}
#property (nonatomic) GameSate gameState;
Then back in your implementation file, you synthesize that GameState property, and create your own setter
//Implementation
#synthesize gameState = _gameState;
//Create your own setter, so you can notify all listeners
-(void) setGameState:(GameState) newGameState {
if (newGameState != _gameState) {
_gameState = newGameState;
//Notify listeners via NSNotification
[[NSNotificationCenter defaultCenter] postNotificationName:#"gameState" object:nil];
}
}
in the classes where you want to get the messages you just have to subscribe to the "gameState" notification like so:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(onGameStateChange:)
name:#"gameState"
object:nil];
-(void) onGameStateChange{
if ([GameManager sharedManager].gameState == kGameStarted){
//start game
}
}