Calling replaceScene: with a CCScene on CCDirector which is during a CCTransition results in dealloc not being called on any scene used, moreover, after this operation any scene is not being displayed.
Here is a link to the sample project
Shortest way to obtain this behavior is such method (I mean reproducing the problem):
SceneOne *destScene = [SceneOne node];
CCTransitionFade *transition = [[CCTransitionFade alloc]initWithDuration:2 scene:destScene];
[[CCDirector sharedDirector]replaceScene:transition];
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[[CCDirector sharedDirector]replaceScene:[SceneTwo node]];
});
My question is: is there any feasible way to replace scenes when the CCTransition is being performed by the CCDirector?
I have implemented a delegate callback from CCDirector informing me about ending scene replacement, but this is never called if I push the iPhone home button during CCTransition.
If the answer is no, is there a cocos2d-iphone way to achieve goal described below?
Originally, this problem arised when I wanted to add "loading scene" when applicationDidEnterBackground (as a background task) or applicationWillEnterForeground, but I realized it has nothing to do with background execution. My ultimate goal is to provide seamless user experience while a game is waiting for Game Center authentication handler being called, and a "loading scene" being displayed from the very start of the app awaked from background would be sufficient (not only preventing user interaction which can be done many ways, but not showing previous game UI to the user). However, this solution is susceptible to the problem described above - if user taps the home button during scene transition he is going to get a very strange screen after bringing the game from background.
EDIT: After some more research I didn't find any satisfactory solution to replacing scene during CCTransition, however, problem described above was solved by not calling replaceScene but by adding a "loading" CCLayer to the visible CCScene, as #LearnCocos2D (thanks Steffen) suggested. This is not perfect, since adding a child during scene transition still has some narrow window (in the sense of running time) when strange results occur, but it is much better than replacing the scene. I would like to mention that it only concerns me when testing my game on 3GS, since newer devices are significantly faster and it is very difficult to reproduce the problem of "home button" clicking during CCTransition on something faster than 3GS.
Related
I have a very simple game using Xcode v4.2.1, Cocos2d v5.0.1. I've tried both compilers in Xcode (LLVM GCC 4.2 and Apple LLVM compiler 3.0. Is there a preference??) On the game screen is a UIKit button that presents the user with a Interface Builder (nib) Settings/Options screen to customize the game a bit. This is all based on what I learned in Ray Wenderlich's tutorial (http://www.raywenderlich.com/4817/how-to-integrate-cocos2d-and-uikit).
After the user makes their changes, they are returned to the game and the changes are in place.
This all works as I want in the simulator, however, when I test the game on an iPad I get the following errors in the debug window:
2012-01-27 18:25:27.305 BonkBonk[1082:707] failed to call context
2012-01-27 18:25:27.310 BonkBonk[1082:707] cocos2d: surface size: 1024x768
2012-01-27 18:25:27.316 BonkBonk[1082:707] Failed to make complete framebuffer object 8cdd
OpenGL error 0x0506 in -[EAGLView swapBuffers]
OpenGL error 0x0506 in -[EAGLView swapBuffers]
OpenGL error 0x0506 in -[EAGLView swapBuffers]
The OpeenGl errors continue on indefinitely.
I use the function viewWillAppear to capture the return from the settings/options screen so that I can pass the new settings to the game layer. If I comment out this code the problem goes away, however, I am not able to get the new user settings to the game layer.
Here is the code:
- (void) viewWillAppear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:YES animated:animated];
//CCScene *scene = [[CCDirector sharedDirector] runningScene];
CCScene *scene = [BonkBonkLayer scene];
id layer = [scene getChildByTag:1];
[layer userSettings];
[super viewWillAppear:animated];
}
The commented out line //CCSene *scene... was another failed attempt at getting the layer object from the scene so that I could call the userSettings method where the game layer then can assimilate it into the game.
OK, so I've found something that alleviates my issue.
If anyone knows anything about this, please let me know. I will continue to use this fix unless I hear a reason from someone more knowledgable than myself (and that's not that difficult).
The solution is found in the comment by "psionic" at the end of the following discussion:
http://www.cocos2d-iphone.org/forum/topic/7068.
Basically, I created a static bool in the EAGLView class (EAGLView.m) that surrounds the call to _resizeFromLayer in the layoutSubViews member function. The call to _resizeFromLayer is only called the first time through, and then never again.
Please read the above discussion and let me know what you like/dislike about this solution, other than the obvious... it's a hack.
A hack,that works. I think.
I had a similar problem. I've integrated cocos2d with UIKit. I added adMob. The problem shows up when user clicks on Ads that presents the google BrowserView. If you dismiss the View using the Done button, the App works fine. However, if user press the Home button when in Browser View, and resume the App, I got the same exception.
OpenGL error 0x0506 in -[EAGLView swapBuffers]
UIKit buttons and the Ads are shown, but it does not render the cocos2d layer.
I got the solution from https://github.com/cocos2d/cocos2d-iphone/pull/198, but I did not change the cocos2d source. Instead, I added a boolean ivar called isAnimating in AppDelegate.m, and expose the property to the Layers.
To solve this, I stop animation on CCDirector before entering google Browser View. and start animation once resume to the App. isAnimating ivar is used to check that start animation is not called twice.
Hope it helps,
I had similar errors in my app when implementing Cocos2D via CCGLView.
The solution that worked for me was to call
[[CCDirector sharedDirector] popScene]
when presenting the Interface Builder Viewcontroller.
Hope there are some GLKViewController experts out there because I have some problems :)
Just a quick description of my app. I have a UINavigationController in which I push different screens.
At some point, I get to my game screen which is a subclass of UINavigationController. In this screen, in viewDidLoad I manually create a EAGLContext, GLKView and instantiate a new GLKViewController (to handle my update&draw calls).
I am setting a preferred fps of 30.
The problem is that the first 3-4 update calls come with the correct DT, but then I have 2-3 frames with 1 second between them. I measure the DT using controller.timeSinceLastUpdate.
So I get like:
dt=0.33
dt=0.33
dt=0.33
dt=1.07
dt=1.05
dt=0.33
dt=0.33
After this, I get valid only DT times. I have no idea why those frames have that kind of delay. I measured the time it takes me in the update & draw method, and it's nowhere near 1 second.
Also, I'm not loading any textures/creating any geometry. Everything is done at loading since it is a rather small game.
Also, if I pop the game screen controller and then push back another instance of the game screen, this new GLKViewController will only call my update method aproximately every 1 second.
Did anybody have a problem with the framerate when using GLKViewController?
Thanks,
The problem is that you don't know what else the device is doing between your refreshes :)
You might only spent 0.1s working on the next frame but if there is a memory warning then other bits of your app will also take time to process. I guess that the gl controller will do it's best to keep to the preferred frame rate but if lots is going on in the background then there's not much it can do about it.
As long as you make sure that your code is rendering as fast as possible and isn't spiking in the same way as the framerate then it's not your render path. From your question it sounds like you've already tested that.
The other thing you might want to do is to watch out for other notifications that might be passed into your app (i.e. memory warnings).
Finally, is there a pattern to the slow frames - do they coincide with a new image being loaded or a file access? Have you done as much as possible beforehand? EDIT - rereading your question makes me think that you've already done this, sorry!
Sorry I can't be any more use :(
Ok, so I finally figured it out. It turns out that it's not even related to the GLKViewController (surprise surprise!).
It had something to do with the way I'm displaying the game screen view controller, like this:
GameAreaViewController* gameController = [[GameAreaViewController alloc] init];
[UIView beginAnimations:#"animation" context:nil];
[self.navigationController pushViewController: gameController animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
[UIView setAnimationDuration:0.7f];
[UIView commitAnimations];
SAFE_DEL(gameController);
If I use an animation duration of 0.3f, then I don't get any lag. At 0.5f sometimes I get it and at 0.7 I was always getting it.
I'm trying to do an app where a short sound sample is supposed to be played while the person using the app is dragging his/her finger(s) across the screen. When the finger(s) are lifted away from the screen - the sound will stop.
This is the current function that triggers the sound (I've tried various methods):
-(BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event{
NSLog(#"Ja, den börjar...");
return YES;
}
-(void)ccTouchMoved:(NSSet *)touch withEvent:(UIEvent *)event{
soundFile = [[CDAudioManager sharedManager] audioSourceForChannel:kASC_Right];
[soundFile load:#"sound.wav"];
soundFile.backgroundMusic = NO;
soundSourceForFile:#"sound.wav"]retain];
}
This is the function that stops the sound:
-(void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event{
[soundFile stop];
}
I first started out using the ccTouchBegan (Just to get some kind of sound working), which looped the sound seamlessly. At this point the ccTouchEnded worked together with the "Touch Up Inside" event.
The point, as I said, is the the sound is supposed to be played when the user drags his/her finger(s) across the screen. But when I tried to the tie the playSound function to the "ccTouchMoved" the sound loops repeatedly over itself, instead of one at the time, making it hell to use. The stopSound function doesn't work after i changed to the ccTouchMoved.
I tried to use NSTimer to create some kind of way to handle the loops, but without any success.
I started this project with the regular iOS SDK, and found my limitations when i found out i wasn't able to handle pitch & gain manipulation without Cocos2d.
I got everything working in the regular SDK by wrapping it in a if-statement:
if(![mySound isPlaying]{
[mySound play];
}
This, as I said, worked perfectly fine in the regular SDK, but not now when I'm using Cocos2d.
ccTouchMoved will be called continuously as the finger moves along the screen. The problem you are having here is that each time this is called you are loading a new sound file and they are overlapping because they are newly created individual objects. You only have a reference to the final sound you load (which is what soundFile is pointing at) and you aren't freeing up the memory either.
Example:
(as you drag your finger)
LoadedSoundA created and starts playing
soundfile points to LoadedSoundA
// finger moves
LoadedSoundB created and starts playing
soundfile points to LoadedSoundB
// finger moves
LoadedSoundC created and starts playing
soundfile points to LoadedSoundC
... etc
the only sound you have a pointer to at the moment is the last created sound, since you reassign soundfile each time. So you can only 'stop' the sound you created last.
You are also leaking a lot of memory since you are retaining all of these sounds and never releasing them.
I would suggest a different tactic:
In touchesBegan you should load the sound and have it play on loop and record the time of the touch into a class level iVar.
Now, in TouchesMoved you should get the time of the current touch and see if it is close enough to the time you recorded. If it is within say, 0.5 seconds then just update the recorded timestamp and continue; However, if it has been too long since the last touch you stop the sound that is playing.
This way you have a seamless sound being played, it is only created once and you maintain your ownership of it.
Hope this helps
There are a few similar questions out there on SO (links at end), but none of them has allowed me to fix my problem, so here goes:
I'm using OpenGL rendering to make an image tiling and caching library for use in a game project, and I want to hijack the physics of the UIScrollView to allow the user to navigate around the images (since it has nice bounce behaviour, might as well use it). So I have a UIScrollView which I'm using to get the rendering view for my textures, but there's a problem - moving around on the scroll view prevents the CADisplayLink from firing until the user has finished scrolling (which looks horrible). One temporary fix has been to use NSRunLoopCommonModes instead of the default run mode, but unfortunately this breaks some aspects of scroll view behaviour on certain phones I'm testing on (the 3GS and simulator seem to work fine, while the iPhone4 and the 3G don't).
Does anyone know how I could get around this clash between the CADisplayLink and the UIScrollView, or know how to fix the UIScrollView working in other run modes? Thanks in advance :)
Promised links to similar questions:
UIScrollView broken and halts scrolling with OpenGL rendering (related CADisplayLink, NSRunLoop)
Animation in OpenGL ES view freezes when UIScrollView is dragged on iPhone
It's possible that slow updates on the main thread triggered by the CADisplayLink are what's breaking UIScrollView's scrolling behavior here. Your OpenGL ES rendering might be taking long enough for each frame to throw off the timing of a UIScrollView when using NSRunLoopCommonModes for the CADisplayLink.
One way around this is to perform your OpenGL ES rendering actions on a background thread by using a Grand Central Dispatch serial queue. I did this in my recent update to Molecules (source code for which can be found at that link), and in testing with using NSRunLoopCommonModes on my CADisplayLink, I don't see any interruption of the native scrolling behavior of a table view that's onscreen at the same time as the rendering.
For this, you can create a GCD serial dispatch queue and use it for all of your rendering updates to a particular OpenGL ES context to avoid two actions writing to the context at the same time. Then, within your CADisplayLink callback you can use code like the following:
if (dispatch_semaphore_wait(frameRenderingSemaphore, DISPATCH_TIME_NOW) != 0)
{
return;
}
dispatch_async(openGLESContextQueue, ^{
[EAGLContext setCurrentContext:context];
// Render here
dispatch_semaphore_signal(frameRenderingSemaphore);
});
where frameRenderingSemaphore is created earlier as follows:
frameRenderingSemaphore = dispatch_semaphore_create(1);
This code will only add a new frame rendering action onto the queue if one isn't in the middle of executing. That way, the CADisplayLink can fire continuously, but it won't overload the queue with pending rendering actions if a frame takes longer than 1/60th of a second to process.
Again, I tried this on my iPad here and found no disruption to the scrolling action of a table view, just a little slowdown as the OpenGL ES rendering consumed GPU cycles.
My simple solution is to halve the rendering rate when the run loop is in tracking mode. All my UIScrollViews now work smoothly.
Here is the code fragment:
- (void) drawView: (CADisplayLink*) displayLink
{
if (displayLink != nil)
{
self.tickCounter++;
if(( [[ NSRunLoop currentRunLoop ] currentMode ] == UITrackingRunLoopMode ) && ( self.tickCounter & 1 ))
{
return;
}
/*** Rendering code goes here ***/
}
}
The answer at the following post works very well for me (it appears to be quite similar to Till's answer):
UIScrollView pauses NSTimer until scrolling finishes
To summarize: disable the CADisplayLink or GLKViewController render loop when the UIScrollView appears and start a NSTimer to perform the update/render loop at the desired framerate. When the UIScrollView is dismissed/removed from the view hierarchy, re-enable the displayLink/GLKViewController loop.
In the GLKViewController subclass I use the following code
on appear of UIScrollView:
// disable GLKViewController update/render loop, it will be interrupted
// by the UIScrollView of the MPMediaPicker
self.paused = YES;
updateAndRenderTimer = [NSTimer timerWithTimeInterval:1.0f/60.0f target:self selector:#selector(updateAndRender) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:updateAndRenderTimer forMode:NSRunLoopCommonModes];
on dismiss of UIScrollView:
// enable the GLKViewController update/render loop and cancel our own.
// UIScrollView wont interrupt us anymore
self.paused = NO;
[updateAndRenderTimer invalidate];
updateAndRenderTimer = nil;
Simple and effective. I'm not sure if this could cause artifacts/tearing of some sort since the rendering is decoupled from screen refreshes, but using CADisplayLink with NSRunLoopCommonModes totally breaks the UIScrollView in our case. Using NSTimer looks just fine for our app and definitely a whole lot better than no rendering.
Even though this is not the perfect solution, it still might work as a workaround;
You could ignore the display link availability and use NSTimer for updating your GL-layer instead.
I have an animated transparent OpenGL ES subview (a modification of Apple's template EAGLView class) which draws a rotating sphere. Just like Apple's example, CADisplayLink is used on devices where available.
On the same screen, there is a UIScrollView containing UIButtons that can be selected. When the user scrolls the UIScrollView, the animation of my EAGLView freezes. This behavior is reproduced on iOS Simulator 4.2 and on iPhone OS 3.1.3 on an iPhone 2G device.
Any ideas on what to do to prevent pause of the EAGLView, apart from coding my own scroll view?
Whether CADisplayLink fires during scrolls depends on the mode with which you add it to the run loop. Probably you have this, somewhere:
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
UIApplication adds a run loop mode, UITrackingRunLoopMode, for 'tracking in controls', which includes when a scrollview is scrolling. So at that point the runloop is switched out of the default mode and hence your display link (and also any timers, NSURLConnections, etc, added in the default mode) will fail to fire until default mode is restored.
Quick fix: change your code to:
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
UITrackingRunLoopMode is considered one of the common modes.
Spending too much time interrupting UIKit can lead to some very poor control responsiveness, so you need to be careful. It'd be to diverge from the topic massively, but although OpenGL is modal and therefore not particularly threading friendly, you can use an EAGLSharegroup to do rendering on a separate thread and then push it onto the main thread.
An example in (2016) Swift3...
let d = CADisplayLink(target: self, selector: #selector(ThisClassName.updateAlpha))
d.add(to: RunLoop.current, forMode: RunLoopMode.commonModes)
//and then, for example...
func updateAlpha() {
let a = leader.layer.presentation()?.value(forKey: "opacity") as! CGFloat
follower.alpha = a
}