SpriteKit Scene Pause - ios

I am trying to pause the current scene whenever it goes in the background and whenever an iAD is opened.
With my Code, it acts strange, like after the scene unpauses, it runs everything what should be done while it was paused in a second. I recognized this happens most likely if I pause and unease the scene very fast, so I tried to add a NSTimerbut in the end it didn't work.
Also, it doesn't pause the whole scene.For e.g. I got 2 Nodes, running actions. The first node is paused, but the second doesn't.
Edit: Thanks to LearnCocos2D, you can't pause the update: method. Instead of skView.scene.paused = YES; you write skView.paused = YES;
My Code :
AppDelegate.m
- (void)applicationWillResignActive:(UIApplication *)application
{
SKView *view = (SKView *)self.window.rootViewController.view;
view.paused = YES;
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(UnpauseGame)
userInfo:nil
repeats:NO];
}
- (void)UnpauseGame
{
SKView *view = (SKView *)self.window.rootViewController.view;
view.paused = NO;
}
ViewController.m
- (BOOL)bannerViewActionShouldBegin:(ADBannerView *)banner willLeaveApplication:(BOOL)willLeave
{
SKView *skView = (SKView *)self.view;
skView.scene.paused = YES;
return YES;
}
- (void)bannerViewActionDidFinish:(ADBannerView *)banner
{
SKView *skView = (SKView *)self.view;
skView.scene.paused = NO;
}
The SpriteNode that doesn't pause in MyScene.m
-(SKSpriteNode *)createBackground {
Baumstamm = [SKSpriteNode spriteNodeWithImageNamed:#"BaummitLeiter.png"];
Baumstamm.size = CGSizeMake(280, 570);
Baumstamm.position = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
Baumstamm.zPosition = 2;
Baumstamm.name = BACK_GROUND;
return Baumstamm;
}
-(void)update:(CFTimeInterval)currentTime {
[self enumerateChildNodesWithName:BACK_GROUND usingBlock:^(SKNode *node, BOOL *stop) {
node.position = CGPointMake(node.position.x, node.position.y - 5);
if (node.position.y < -(node.frame.size.height + 100))
{
[node removeFromParent];
}
}];
if (self.currentBackground.position.y < (self.frame.size.height / 2))
{
SKSpriteNode *temp = [self createBackground];
temp.position = CGPointMake(self.frame.size.width / 2, self.currentBackground.position.y + self.currentBackground.frame.size.height);
[self addChild:temp];
self.currentBackground = temp;
}

For pausing in scenarios like this, I put a conditional in my update method that makes sure the scene isn't paused before it executes everything. That way, you can selectively choose what keeps happening during pause and what doesn't.

Related

Bonbons falling continuously from random positions

I have got the following problem in SpriteKit: My aim is to let bonbons fall continuously from the top of the screen. These bonbons can be collected by the player. Every 0.8 seconds or so, another bonbon should fall down and when the player collides with one of the bonbons, it should disappear. This is my code:
-(void)populate {
for (int i = 0; i < 2; i++) {
[self generate];
}
}
-(void)generate {
Y = (arc4random() % 280) - 140;
bonbon = [SKSpriteNode spriteNodeWithImageNamed:#"Bonbon.png"];
bonbon.size = CGSizeMake(20, 20);
bonbon.name = #"bonbon";
bonbon.physicsBody.categoryBitMask = bonbonCategory;
bonbon.position = CGPointMake(Y, 500);
bonbon.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:bonbon.size.width/2];
bonbon.physicsBody.dynamic = YES;
[bonbon addChild:bonbon];
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
if ([contact.bodyA.node.name isEqualToString: #"wallLeft"] || [contact.bodyB.node.name isEqualToString: #"wallLeft"])
{
[hero.physicsBody applyImpulse: CGVectorMake(100, 60)];
self.jumpDirection = YES;
}
else if ([contact.bodyA.node.name isEqualToString: #"bonbon"] || [contact.bodyB.node.name isEqualToString: #"bonbon"])
{
[world enumerateChildNodesWithName:#"bonbon" usingBlock:^(SKNode *node, BOOL *stop) {
PointsLabel *pointsLabel = (PointsLabel *)[self childNodeWithName:#"pointsLabel"];
[pointsLabel increment];
NSLog(#"didContactBonbon");
[bonbon removeFromParent];
NSLog(#"removedFromParent");
}];
}
else {
[hero.physicsBody applyImpulse: CGVectorMake(-100, 60)];
self.jumpDirection = NO;
}
}
I hope you understand my problem. Currently, no bonbons are falling at all. If you need more information, please do not hesitate to ask.
Greets
edit: I hope the formatting is better now. I am quite a newbie, i'm sorry for foolish mistakes :) This is in my MyScene.m, when is call the populate in the initWithSize method with [self populate]; I receive the message signal SIGABRT. What did I do wrong?
I think the problem is that you are adding your SKSpriteNode named bonbon to itself with :
- (void)generate {
...
bonbon = [SKSpriteNode spriteNodeWithImageNamed:#"Bonbon.png"];
...
[bonbon addChild:bonbon];
}
Try to add bonbon to your SKScene instance.
To use an SKView instance you have to create a UIViewController and give its view the SKView class. You can do that in your storyboard.
Then, in your viewController :
#implementation GameViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view.
SKView * skView = (SKView *)self.view;
// Configure the scene
SKScene *scene = [SKScene sceneWithSize:self.view.bounds.size];
// You can add objects to your scene
[scene addChild:bonbon];
// Present the scene.
[skView presentScene:scene];
}
#end
This is a very simple example but if you want more information, I suggest you read this tutorial: Sprite Kit Tutorial for Beginners.

How do I initialize all scenes at the start in Sprite Kit?

Whenever I want to transition to a certain scene, it takes a couple of seconds before the SKTransition even starts. Is it possible to have all scenes initialized before the game starts?
NewScene *newScene = [NewScene sceneWithSize:self.size];
SKTransition *reveal = [SKTransition moveInWithDirection:SKTransitionDirectionUp duration:0.5];
reveal.pausesIncomingScene = NO;
[self.view presentScene:newScene transition:reveal];
I've tried this with didMoveToView as well:
#implementation NewScene
-(id)initWithSize:(CGSize)size {
if(self = [super initWithSize:size]) {
// Load a bunch of stuff like this:
SKSpriteNode *menuButton = [SKSpriteNode spriteNodeWithImageNamed:#"mainMenu"];
menuButton.position = CGPointMake(self.frame.size.width/2,self.frame.size.height/4);
menuButton.name = #"menuButton";
[self addChild:menuButton];
[menuButton setScale:0.8];
}
}
How can I make sure that my Sprite Kit game runs smoothly?
EDIT:
It turns out the problem was that I kept putting the main thread to sleep to fade out music. I've made all my audio methods run in the background and it works fine now.
A good approach is to load all resources asyncronously before SKScene's initialization. Apple uses this approach in Adventure game:
NewScene.h:
typedef void (^AGAssetLoadCompletionHandler)(void);
#interface GameScene : SKScene<SKPhysicsContactDelegate, UIGestureRecognizerDelegate>
+ (void)loadSceneAssetsWithCompletionHandler:(AGAssetLoadCompletionHandler)handler;
#end
NewScene.m:
#implementation NewScene
-(id)initWithSize:(CGSize)size {
if(self = [super initWithSize:size]) {
// Load a bunch of stuff like this:
SKSpriteNode *menuButton = [SKSpriteNode spriteNodeWithTexture:[self menuButtonTexture];
menuButton.position = CGPointMake(self.frame.size.width/2,self.frame.size.height/4);
menuButton.name = #"menuButton";
[self addChild:menuButton];
[menuButton setScale:0.8];
}
}
#pragma mark - Shared Assets
+ (void)loadSceneAssetsWithCompletionHandler:(AGAssetLoadCompletionHandler)handler {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// Load the shared assets in the background.
[self loadSceneAssets];
if (!handler) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
// Call the completion handler back on the main queue.
handler();
});
});
}
+ (void)loadSceneAssets {
sBackgroundTexture = [SKTexture textureWithImageNamed:#"background"];
sMenuButtonTexture = [SKTexture textureWithImageNamed:#"mainMenu"];
// etc.
}
static SKTexture *sBackgroundTexture = nil;
- (SKTexture *)backgroundTexture {
return sBackgroundTexture;
}
static SKTexture *sMenuButtonTexture = nil;
- (SKTexture *)menuButtonTexture {
return sMenuButtonTexture;
}
#end
Then just present NewScene from your UIViewController:
if (!self.skView.scene) {
CGSize viewSize = self.view.bounds.size;
// Here you can present some loading scene or splash screen
[NewScene loadSceneAssetsWithCompletionHandler:^{
NewScene *scene = [[NewScene alloc] initWithSize:viewSize];
[self.skView presentScene:scene transition:[SKTransition crossFadeWithDuration:1.f]];
}];
}

SpriteKit Pause and Resume SKView

I want to Pause and Unpause a Scene in SpriteKit, with 2 Buttons on the same position.
While the Scene is running, I want to show the 'Pause' Button.
While the Scene is paused, I want to hide the 'Pause' Button and show the 'Play' Button.
In SpriteKit you can use self.scene.view.paused which is defined in SpriteKit.
My Code:
#implementation MyScene {
SKSpriteNode *PauseButton;
SKSpriteNode *PlayButton;
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
[self Pause];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
UITouch * touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode * Node = [self nodeAtPoint:location];
if([Node.name isEqualToString:#"PauseButton"]){
self.scene.view.paused = YES;
[PauseButton removeFromParent];
[self Resume];
}
if([Node.name isEqualToString:#"PlayButton"]){
self.scene.view.paused = NO;
[PlayButton removeFromParent];
[self Pause];
}
}
-(void)Pause{
PauseButton = [SKSpriteNode spriteNodeWithImageNamed:#"Pause.png"];
PauseButton.position = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 1.04);
PauseButton.zPosition = 3;
PauseButton.size = CGSizeMake(40, 40);
PauseButton.name = #"PauseButton";
[self addChild:PauseButton];
}
-(void)Resume{
PlayButton = [SKSpriteNode spriteNodeWithImageNamed:#"Play.png"];
PlayButton.position = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 1.04);
PlayButton.zPosition = 3;
PlayButton.size = CGSizeMake(60, 60);
PlayButton.name = #"PlayButton";
[self addChild:PlayButton];
}
It pauses the Scene, but the there is still the Pause Button, and if I touch the Pause Button again, the Scene resumes. So now only the Images won't change. How can I fix this?
You can't update the button (or anything else in the scene) while the SKView is paused. In your touchesBegan method, you are pausing the view before updating the button (changing the order won't work). You will need to return to the run loop so your button is updated before pausing the game. Here's one way to do that:
This calls a method to pause the view after a short delay. Add this after your [self Resume] statement in touchesBegan, and delete self.scene.view.paused = YES.
[self performSelector:#selector(pauseGame) withObject:nil afterDelay:1/60.0];
This method pauses the SKView. Add this to your MyScene.m
- (void) pauseGame
{
self.scene.view.paused = YES;
}

Change image when touched

I need an image to change itself when it's touched.
At the moment the image that changes is the next image that spawns and not self.
#implementation MyScene2
{
Marsugo *marsugo;
SKAction *actionMoveDown;
SKAction *actionMoveEnded;
SKTexture *rescued;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size]) {
// Initializes Background
self.currentBackground = [Background generateNewBackground];
[self addChild:self.currentBackground];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches) {
NSArray *nodes = [self nodesAtPoint:[touch locationInNode:self]];
for (SKNode *node in nodes) {
if ([node.name isEqualToString:playerObject]) {
rescued = [SKTexture textureWithImageNamed:#"rescued"];
marsugo.texture = rescued;
// this is changing the image of the next marsugo that spawns instead of self.
}
}
}
}
-(void)addMarsugos
{
// the marsugo is being initialized inside this method, that might be the issue i believe
// Create sprite
marsugo = [[Marsugo alloc]init];
marsugo.xScale = 0.3;
marsugo.yScale = 0.3;
marsugo.zPosition = 75;
// Bounds + Spawn Positions
int minX = marsugo.size.width;
int maxX = self.frame.size.width - marsugo.size.width;
int rangeX = maxX - minX;
int actualX = (arc4random() % rangeX) + minX;
marsugo.position = CGPointMake(actualX, self.frame.size.height + 50);
[self addChild:marsugo];
// Spawn Timer
int minDuration = 1;
int maxDuration = 10;
int rangeDuration = maxDuration - minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;
// Movement Actions
actionMoveDown = [SKAction moveTo:CGPointMake(actualX, CGRectGetMinY(self.frame)) duration:actualDuration];
actionMoveEnded = [SKAction removeFromParent];
[marsugo runAction:[SKAction sequence:#[actionMoveDown, actionMoveEnded]]];
NSLog(#"Marsugo X: %f - Speed: %i", marsugo.position.x, actualDuration);
}
#end
Like i said previously, i need the self sprite to change texture and not the "next spawning sprite".
Any help fixing this would be appreciated, thank you.
You don't want to use touchesBegan - you're better off using a tap gesture recognizer. Below I have _testView, which is an instance variable I create and add to the view in viewDidLoad. I then created a tap gesture recognizer that calls a function when the view is tapped, and that function changes the color of the view - but in your case you can call your function that changes the image:
- (void)viewDidLoad {
[super viewDidLoad];
// create the test view and add it as a subview
_testView = [[UIView alloc] initWithFrame:CGRectMake(20, 100, 100, 100)];
_testView.backgroundColor = [UIColor redColor];
[self.view addSubview:_testView];
// create the tap gesture recognizer and add it to the test view
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(changeColor)];
[_testView addGestureRecognizer:tapGestureRecognizer];
}
- (void)changeColor {
// here I'm changing the color, but you can do whatever you need once the tap is recognized
_testView.backgroundColor = [UIColor blueColor];
}
This results in this at first:
Then when I tap the view:
You can override -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event in your Marsugo class and change the texture there. That way, you won't have to check if the touch is inside your node because that method won't be called if it's not.

SKScene.backgroundColor not working. Always default color

Hi I'm setting my SKScene's backgroundColor property in its didMoveToView function like this :
self.backgroundColor = [SKColor colorWithRed:0.2 green:0.2 blue:1.0 alpha:1.0];
But the background colour is always the default grey. I have another simple SpriteKit test project and setting the background colour the same way works.
I've removed all nodes from my scene to rule out the possibility that the background is being overlayed.
Do you know what might be stopping my background colour appearing?
Thanks.
I just tested this and it works as expected for me.
Here's my initial scene, HelloScene.m
#import "HelloScene.h"
#import "NewScene.h"
#interface HelloScene ()
#property BOOL contentCreated;
#end
#implementation HelloScene
- (void)didMoveToView:(SKView *)view
{
if (!self.contentCreated) {
[self createSceneContents];
self.contentCreated = YES;
}
}
- (void)createSceneContents
{
self.backgroundColor = [SKColor colorWithRed:0.333 green:0.780 blue:0.961 alpha:1];
self.scaleMode = SKSceneScaleModeAspectFit;
[self addChild:[self newHelloNode]];
}
- (SKLabelNode *)newHelloNode
{
SKLabelNode *helloNode = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
helloNode.name = #"helloNode";
helloNode.text = #"Hello !";
helloNode.fontSize = 42;
helloNode.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
return helloNode;
}
#if TARGET_OS_IPHONE
#pragma mark - Event Handling - iOS
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self runHelloNodeAction];
}
#else
#pragma mark - Event Handling - OS X
- (void)mouseDown:(NSEvent *)event
{
[self runHelloNodeAction];
}
#endif
- (void)runHelloNodeAction
{
SKNode *helloNode = [self childNodeWithName:#"helloNode"];
if (helloNode != nil) {
helloNode.name = nil;
SKAction *moveUpAndZoom = [SKAction group:#[[SKAction moveByX:0.0 y:100.0 duration:0.5],
[SKAction scaleTo:1.5 duration:0.5]]];
SKAction *fadeAway = [SKAction fadeOutWithDuration:0.25];
SKAction *remove = [SKAction removeFromParent];
SKAction *moveSequence = [SKAction sequence:#[moveUpAndZoom, fadeAway, remove]];
[helloNode runAction:moveSequence completion:^{
SKScene *newScene = [[NewScene alloc] initWithSize:self.size];
SKTransition *doors = [SKTransition doorsOpenVerticalWithDuration:0.5];
[self.view presentScene:newScene transition:doors];
}];
}
}
#end
Here's my second scene, NewScene.m
#import "NewScene.h"
#interface NewScene ()
#property BOOL contentCreated;
#end
#implementation NewScene
- (void)didMoveToView:(SKView *)view
{
if (!self.contentCreated) {
[self createSceneContents];
self.contentCreated = YES;
}
}
- (void)createSceneContents
{
self.backgroundColor = [SKColor purpleColor];
self.scaleMode = SKSceneScaleModeAspectFit;
}
#end

Resources