I have huge issue with my newest game for iPhone made with Games by Tutorials book's help.
NOTE : method from SpriteKit- the right way to multitask doesn't work.
So, in my ViewController.m file, I'm defining private variable SKView *_skView.
Then, I do something like this :
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
if(!_skView)
{
_skView = [[SKView alloc] initWithFrame:self.view.bounds];
_skView.showsFPS = NO;
_skView.showsNodeCount = NO;
// Create and configure the scene.
SKScene * scene = [MainMenuScene sceneWithSize:_skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[_skView presentScene:scene];
[self.view addSubview:_skView];
}
}
And I have my _skView defined, and everything works just fine.
But, when I interrupt game, it resets its state to initial, so, for example, if I'm currently playing, and somebody calls me, game switches back to main menu. It can't be like this.
Based on the site I mentioned above, I created this :
- (void)applicationWillResignActive:(UIApplication *)application
{
SKView *view = (SKView *)self.window.rootViewController.view;
view.paused = YES;
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
SKView *view = (SKView *)self.window.rootViewController.view;
view.paused = NO;
}
But game crashes as soon as it is launched, because second method is called and SKView* view is nil.
Getting it from self.window.rootViewController.view doesn't work.
I also tried to get it from self.window.rootViewController.view.subviews, but it doesn't work either.
I can't define my SKView (in ViewController.m) like this :
SKView * skView = (SKView *)self.view;
because then I have errors with my GameCenterController.
Can someone help me how correctly get actual skView and pause it properly??
You could subscribe to these notifications yourself within the ViewController that manages the SKView. Then you don't have to navigate some weird hierarchy to obtain it.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(willEnterBackground)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(willEnterForeground)
name:UIApplicationWillEnterForegroundNotification
object:nil];
- (void)willEnterForeground {
self.skView.paused = NO;
}
- (void)willEnterBackground {
// pause it and remember to resume the animation when we enter the foreground
self.skView.paused = YES;
}
Related
I am pausing my game with a willResignActive notification, and it seems to pause the game, but when didBecomeActive is called, it seems to un-pause on its own.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(applicationWillResign)
name:UIApplicationWillResignActiveNotification
object:NULL];
- (void) applicationWillResign {
self.scene.view.paused = TRUE;
NSLog(#"About to lose focus");
}
How do I get it to stay paused? Do I actually need to pause it in my AppDelegate?
Here's a way to keep the view paused after returning from background mode. It's a bit of a hack, but it does work.
1) Define an SKView subclass with a boolean named stayPaused...
#interface MyView : SKView
#property BOOL stayPaused;
#end
#implementation MyView
// Override the paused setter to conditionally un-pause the view
- (void) setPaused:(BOOL)paused
{
if (!_stayPaused || paused) {
// Call the superclass's paused setter
[super setPaused:paused];
}
_stayPaused = NO;
}
- (void) setStayPaused
{
_stayPaused = YES;
}
#end
2) In the storyboard, change the class of the view to MyView
3) In the view controller, define the view as MyView
4) Add a notifier to set the stayPaused flag
#implementation GameViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Define view using the subclass
MyView * skView = (MyView *)self.view;
// Add an observer for a method that sets the stay pause flag when notified
[[NSNotificationCenter defaultCenter] addObserver:skView selector:#selector(setStayPaused)
name:#"stayPausedNotification" object:nil];
...
5) In AppDelegate.m, post a notification to set the stay paused flag when the app becomes active
- (void)applicationDidBecomeActive:(UIApplication *)application {
[[NSNotificationCenter defaultCenter] postNotificationName:#"stayPausedNotification" object:nil];
}
I have a game where I've successfully implemented a pause feature when the home button is pressed. In my View Controller that has the main scene, I pause using:
- (void)appWillEnterBackground{
SKView *skView = (SKView *)self.view;
skView.paused = YES;
bubbleOn=NO; //turns bubble spawn off
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(appWillEnterForeground)
name:UIApplicationDidBecomeActiveNotification
object:NULL];
}
To unpause,
- (void)appWillEnterForeground{
SKView * skView = (SKView *)self.view;
skView.paused=NO;
bubbleOn=YES; Allows recursive method to run until bubbleOn = YES
[NSTimer scheduledTimerWithTimeInterval:slowMo target:scene selector:#selector(spawnNew) userInfo:nil repeats:NO]; //Recursive spawn method
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(appWillEnterBackground)
name:UIApplicationWillResignActiveNotification
object:NULL];}
This works well until my the game ends and a new scene (End Scene) is presented. After the End Scene shows a score, the user can tap again to start again and main scene is presented. The main scene's initWithSize method begins the recursive method spawnNew. If the app goes to the background, the scene pauses and spawnNew stops.
But when the app goes to foreground, the scene does resume, but the spawnNew method does not work. It gets called and outputs a correct NSLog message, but the the method doesn't spawn bubble nodes.
The spawnNew method is in my main scene's implementation:
-(void) spawnNew{
if (bubbleOn==YES){
bubble = [SKSpriteNode spriteNodeWithImageNamed:ballName];
bubble.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:bubble.size.width/2];
...
//bubble properties
...
[self addChild:bubble];
NSLog(#"Spawn!");
[NSTimer scheduledTimerWithTimeInterval:slowMo target:self selector:#selector(spawnNew) userInfo:nil repeats:NO];
return;
} else{
return;
}
I'm out of ideas at this point! Any suggestions?
The easiest way is to get rid of NSTimer. You can use SKAction instead.
In SKScene's initWithSize method:
- (instancetype)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
....
__weak GameScene *weakSelf = self;
SKAction *spawnBubble = [SKAction runBlock:^{
[weakSelf spawnNew];
}];
SKAction *wait = [SKAction waitForDuration:slowMo];
[self runAction:[SKAction repeatActionForever:
[SKAction sequence:#[spawnBubble,wait]]]];
}
return self;
}
In this case you don't even need to use bubbleOn variable, as scene stops executing any actions as soon as it's SKView is paused.
SpriteKit is supposed to clean up and pause all timers when you press the home button.
However, we have found that if you single tap the home button while displaying an active SKView, the app crashes. This occurs even if that view is already paused by the user.
Strangely, if you double tap the home button and move to the multitasking view, everything works fine.
Follow Up Note: The simulator works perfectly in both situations, no crash
Is anyone else seeing this issue with SpriteKit?
Have you found the cause / solution?
I was having the same problem and I solved it manually pausing the root SKView in the ViewController before the app moving to the background:
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view (already setup as SKView via storyboard)
SKView * skView = (SKView *)self.view;
// Create and configure the scene.
SKScene *scene = [Menu sceneWithSize:CGSizeMake(skView.bounds.size.height, skView.bounds.size.width)];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:scene];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(appWillEnterBackground)
name:UIApplicationWillResignActiveNotification
object:NULL];
}
- (void)appWillEnterBackground
{
SKView *skView = (SKView *)self.view;
skView.paused = YES;
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(appWillEnterForeground)
name:UIApplicationWillEnterForegroundNotification
object:NULL];
}
- (void)appWillEnterForeground
{
SKView * skView = (SKView *)self.view;
skView.paused = NO;
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(appWillEnterBackground)
name:UIApplicationWillResignActiveNotification
object:NULL];
}
Run Xcode's analyzer (Project > Analyze) to determine if you have memory management issues in your code.
Run this scenario in the allocations instrument of the Instruments app (Project > Profile), which can report any detected misuse of memory.
We ran into some similar inconsistent behavior with UICollectionView. In that case, instantiating the object via a Storyboard (versus programmatically) resolved the issue.
On a hunch, I tried that this morning and Viola, success!
So, it appears that Storyboards are doing some magic with SKView on creation that is unknown to us and allows them to be properly terminated on Home Button press. Frustrating, but at least it works now.
The problem can be caused by audio, if so you can check the answer here https://stackoverflow.com/a/19283721/1278463
I solved this in a game which is not using audio. Th solution is to pause SKView when entering background:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
SKView *view = (SKView *)self.window.rootViewController.view;
if (view) {
view.paused = YES;
}
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
SKView *view = (SKView *)self.window.rootViewController.view;
if (view) {
view.paused = NO;
}
}
I'm using the semi-singleton approach for the main game layer/scene of my cocos2d game as shown in the later code.
The objective is to properly restart/recreate this singleton scene using a button in a pause or gameover layers by calling: [[GameLayer sharedGameLayer] restart] method.
The problem is if I use a CCTransition effect for that, and override the GameLayer's dealloc method with sharedGameLayer = nil; line (to ensure the resetting of the static variable), the sharedGameLayer variable stays nil after the first restart (aka. after the first dealloc), so calling the restart method does nothing.
What works with suspicion is not to override the dealloc method at all, but before restarting the scene using replaceScene:, I set the sharedGameLayer to nil.
Question: Is this the right approach for restarting/recreating this semi-singleton class?
Thanks in advance.
Code:
GameLayer.m:
static GameLayer *sharedGameLayer;
#implementation GameLayer
- (id)init
{
NSLog(#"%s", __PRETTY_FUNCTION__);
// always call "super" init
// Apple recommends to re-assign "self" with the "super's" return value
if (self = [super initWithColor:ccc4(255, 255, 255, 255) width:[CCDirector sharedDirector].winSize.width
height:[CCDirector sharedDirector].winSize.height])
{
// Set the sharedGameLayer instance to self.
sharedGameLayer = self;
// Set the initial game state.
self.gameState = kGameStateRunning;
// Register with the notification center in order to pause the game when game resigns the active state.
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(pauseGame) name:UIApplicationWillResignActiveNotification object:nil];
// Enable touches and multi-touch.
CCDirector *director = [CCDirector sharedDirector];
[director.touchDispatcher addTargetedDelegate:self priority:0 swallowsTouches:YES];
director.view.multipleTouchEnabled = YES;
// Set the initial score.
self.score = 5;
// Create the spiders batch node.
[self createSpidersBatchNode];
// Load the game assets.
[self loadAssets];
// Play Background music.
if (![SimpleAudioEngine sharedEngine].isBackgroundMusicPlaying)
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:#"backgroundmusic.mp3" loop:YES];
// Preload sound effects.
[[SimpleAudioEngine sharedEngine] preloadEffect:#"inbucket.mp3"];
[[SimpleAudioEngine sharedEngine] preloadEffect:#"outbucket.mp3"];
[[SimpleAudioEngine sharedEngine] preloadEffect:#"gameover.mp3"];
// Schdule updates.
[self scheduleUpdate];
[self schedule:#selector(releaseSpiders) interval:0.7];
}
return self;
}
- (void)createSpidersBatchNode
{
NSLog(#"%s", __PRETTY_FUNCTION__);
// BatchNode. (For Animation Optimization)
self.spidersBatchNode = [CCSpriteBatchNode batchNodeWithFile:#"spiderAtlas.png"];
// Spider sprite + BatchNode + animation action.
for (int i = 0; i < 50; i++) {
Spider *spider = [[Spider alloc] init];
spider.spiderSprite.visible = NO;
}
[self addChild:self.spidersBatchNode];
}
- (void)restartGame
{
// Play button pressed sound effect.
[[SimpleAudioEngine sharedEngine] playEffect:#"button.mp3"];
// Nil'ing the static variable.
sharedGameLayer = nil;
// Restart game.
[[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:1.0 scene:[GameLayer scene]]];
}
+ (CCScene *)scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// Game Layer.
// 'gameLayer' is an autorelease object.
GameLayer *gameLayer = [GameLayer node];
// add gameLayer as a child to scene
[scene addChild: gameLayer];
// HUD Layer.
HUDLayer *hudLayer = [HUDLayer node];
[scene addChild:hudLayer];
gameLayer.hud = hudLayer;
// return the scene
return scene;
}
+ (GameLayer *)sharedGameLayer
{
return sharedGameLayer;
}
- (void)cleanup
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[CCDirector sharedDirector].touchDispatcher removeDelegate:self];
[self stopAllActions];
[self unscheduleAllSelectors];
[self unscheduleUpdate];
[self removeAllChildrenWithCleanup:YES];
self.hud = nil;
[super cleanup];
}
//- (void)dealloc
//{
// sharedGameLayer = nil;
//}
#end
I bet your new game layer is created before the first one deallocates, therefore it sets the static var to nil. Check that sharedGameLayer equals self in dealloc.
Do not use dealloc to set the static variable to nil. Dealloc happens after your object has stopped being used. Never use it for controlling the behaviour of your app.
For all you know, 30 minutes might have passed between when you set sharedGameLayer to nil and when the dealloc method gets called. Or perhaps dealloc will never be called at all.
Your code looks good, just delete the commented out "dealloc" code.
Also, this code:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Should be done in the -dealloc method in addition to your custom -cleanup method. It is perfectly fine to remove an observer twice, if it's already been removed nothing will happen.
Another note, +sharedGameLayer should return a variable of type instancetype and it should be responsible for setting the sharedGameLayer variable. If you sharedGameLayer inside -init, you will run into bugs.
For example:
+ (instancetype)sharedGameLayer
{
if (sharedGameLayer)
return sharedGameLayer;
sharedGameLayer = [[[self class] alloc] init];
return sharedGameLayer;
}
- (id)init
{
if (!(self = [super init]))
return nil;
.
.
.
return self;
}
Is there a way to disable the fullscreen button of the MPMoviePlayerController ?
Just did it:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(movieEventFullscreenHandler:)
name:MPMoviePlayerWillEnterFullscreenNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(movieEventFullscreenHandler:)
name:MPMoviePlayerDidEnterFullscreenNotification
object:nil];
self.moviePlayer.controlStyle = MPMovieControlStyleEmbedded;
}
- (void)movieEventFullscreenHandler:(NSNotification*)notification {
[self.moviePlayer setFullscreen:NO animated:NO];
[self.moviePlayer setControlStyle:MPMovieControlStyleEmbedded];
}
Depending on your needs, you can also simply disable all user interactions on the player view.
player.view.userInteractionEnabled = NO;
You can set controlStyle to Fullscreen. these controls are somewhat different, but it doesn't feature a Fullscreen button!
[_moviePlayerController setControlStyle:MPMovieControlStyleFullscreen];
You could hide the playback controls and add your own custom ones, this will prevent the default buttons being rendered at all
I.e with
[player setMovieControlMode:MPMovieControlModeNone];
Unfortunately none of above worked for me properly, so picking the above I implemented the following (and worked fine):
Hide the full screen button.
Add this code in the method where you initialise the movie player.
....
//Because we have to wait until controllers are shown
[self performSelector:#selector(hideFullscreenButton) withObject:self afterDelay:0.5];
...
Add the methods:
-(void) hideFullscreenButton{
//Hide full screen mode button
[self hideFullscreenSubview:movieClip.view.subviews];
}
-(void) hideFullscreenSubview:(NSArray*)arr{
for(UIView *v in arr){
if([v.subviews count]>0)
[self hideFullscreenSubview:v.subviews];
else
NSLog(#"%#",v);
if(v.frame.origin.x==975 ){
v.hidden=TRUE;
}
}
}
The problem relies that there is no tag to identify which view you have to hide. In my case I figure it out by the view coordinates.
Overwrite tap gestures for do not allowing fullscreen zoom.
movieClip.controlStyle = MPMovieControlStyleEmbedded;
//Disable tap for not allowing that video control set on a full screen mode.
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget: self action:#selector(doSingleTap)];
singleTap.numberOfTapsRequired = 1;
[movieClip.view addGestureRecognizer:singleTap];
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget: self action:#selector(doDoubleTap)];
doubleTap.numberOfTapsRequired = 2;
[movieClip.view addGestureRecognizer:doubleTap];
[singleTap requireGestureRecognizerToFail:doubleTap];
And add the selector methods:
-(void) doSingleTap{
//DO NOTHING!!!
}
-(void) doDoubleTap{
//DO NOTHING!!!
}
There's a cheat:
MPMoviePlayerController *mpc = (...some instance...)
UIView *fsbutton = [[mpc view] viewWithTag:512];
[fsbutton setHidden:YES];
The main catch is, you have to do it in viewDidAppear: or similar, because the MoviePlayer view sets itself up somewhere inside didMoveToWindow or didMoveToSuperview, which happen after viewWillAppear:. So you get a brief flash of the fullscreen button. Other obvious catches include: brittle vs. Apple changing that 512 tag value (although it works in 3.2 - 4.2); and of course Apple would rather you not do this.
The endorsed solution is to set the control style to MPMovieControlStyleNone and roll your own transport controls, which is more work.
No, there is no way. Hopefully with the next update.
in order to disable switch to full screen mode, either form button or pinch gesture, you can use this:
moviePlayer.controlStyle = MPMovieControlStyleNone;
moviePlayer.view.userInteractionEnabled =NO;
Wired does this. For the videos that start in fullscreen, they have the standard MPMoviePlayerController controls, but are missing the fullscreen buttons. And they're using the standard built-in ones, since they suddenly got an AirPlay button with 4.2.
Simple block to remove pinch zoom here
Hope it help
it work with me on iOS6
for (UIView *view in moviePlayer.view.subviews) {
for(UIPinchGestureRecognizer *pinch in view.gestureRecognizers){
if([pinch isKindOfClass:[UIPinchGestureRecognizer class]])
[view removeGestureRecognizer:pinch];
}
}
This worked on iOS 7, iPhone 5s.
Add Notification:
MPMoviePlayerDidEnterFullscreenNotification : #"moviePlayFullscreenNote:"
- (void)moviePlayFullscreenNote:(NSNotification*)notification
{
if (notification.object == self.videoPlayer)
{
[self.videoPlayer setFullscreen:NO animated:YES];
self.videoPlayer.controlStyle = MPMovieControlStyleEmbedded;
}
}
Notice that I only listen for "DID" and not the "WILL" notification as well as running it animated. I think this works as it gives the system time to react. When I used the "WILL" and "DID" as noted in answers above it led to a black screen with no controls. There is a slight glitch that is visible when the transition occurs, but I need the play/scrub buttons from embedded.
Fullscreen button along with pause button can be removed.
[self.videoPlayer setControlStyle:MPMovieControlStyleNone];
If the only thing you want to do is disable pinch to go full screen (i.e. keep interaction enabled and whatever control style you want), you can use this:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *set = [event allTouches];
NSArray *arr = [set allObjects];
for (int i = 0; i < arr.count; i++) {
UITouch *touch = (UITouch *) [arr objectAtIndex:i];
NSArray *recognisers = touch.gestureRecognizers;
for (UIGestureRecognizer *recogniser in recognisers) {
if (recogniser.enabled && [recogniser isMemberOfClass:[UIPinchGestureRecognizer class]]) {
recogniser.enabled = NO;
}
}
}
}
This is the Swift version of the first solution of Javier Calatrava LlaverÃa:
func hideFullScreenButton() {
self.hideFullScreenSubview((self.moviePlayerController?.view.subviews)!)
}
func hideFullScreenSubview(subviews: [UIView]) {
for view: UIView in subviews {
if view.subviews.count > 0 {
self.hideFullScreenSubview(view.subviews)
}
if view.frame.origin.x == 631 {
view.hidden = true
}
}
}
And when the user taps on Play:
self.performSelector(#selector(VideoViewController.hideFullScreenButton), withObject: self, afterDelay: 0.5)
(VideoViewController is the view controller in which I have the MPMoviePlayerController)
I know, it's a little outdated, but anyway. I did some research in that direction, and looks like a found an answer. I do not know, why it's working, but it is.
-(void) playMovieAtURL: (NSURL*) theURL {
MPMoviePlayerController* theMovie =
[[MPMoviePlayerController alloc] initWithContentURL: theURL];
//That line is for ARC. Without it, it may not work.
self.moviePlayer = theMovie;
theMovie.scalingMode = MPMovieScalingModeAspectFill;
theMovie.controlStyle = MPMovieControlStyleFullscreen;
theMovie.repeatMode = MPMovieRepeatModeOne;
//Here you'd better use your custom ViewController subclass, if you want autorotating and all that stuff.
UIViewController * vc = [UIViewController new];
[vc.view addSubview:theMovie.view];
theMovie.fullscreen = YES;
theMovie.view.frame = vc.view.bounds;
vc.view = theMovie.view;
[self presentModalViewController:vc animated:YES];
theMovie.fullscreen = YES;
[theMovie prepareToPlay];
[theMovie play];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myMovieFinishedCallback:) name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
}
// When the movie is done, release the controller.
-(void) myMovieFinishedCallback: (NSNotification*) aNotification
{
[self dismissModalViewControllerAnimated:YES];
MPMoviePlayerController* theMovie = [aNotification object];
[[NSNotificationCenter defaultCenter]
removeObserver: self
name: MPMoviePlayerPlaybackDidFinishNotification
object: theMovie];
[self.moviePlayer.view removeFromSuperview];
self.moviePlayer = nil;
// Release the movie instance created in playMovieAtURL:
}
Put a UIView or UIButton with transparent background on top of the view that shows the video, so that the user won't be able to tap on the view that contains the video.