Bonbons falling continuously from random positions - ios

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.

Related

Change SCNScene's sceneNamed programmatically

I am trying to change a current sceneNamed with code but it seems my method has some problems. First, the new scene will be changed but I have to touch or rate the object to changing process happens.Second, it seems childNodeWithName doesn't change at all ! Here is my code :
- (void)load3DObjectName:(NSString*)name nodeName:(NSString*)nodeName zPhone:(CGFloat)positioniPhone zPad:(CGFloat)positioniPad{
SCNScene * scene = [SCNScene sceneNamed:name];
// retrieve the ship node
SCNNode *trex = [scene.rootNode childNodeWithName:nodeName recursively:YES];
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
trex.position = SCNVector3Make(0, 0, positioniPhone);
} else {
trex.position = SCNVector3Make(0, 0, positioniPad);
}
_the3DScence.scene = scene;
_the3DScence.autoenablesDefaultLighting = YES;
_the3DScence.allowsCameraControl = YES;
_the3DScence.backgroundColor = [UIColor colorWithRed:0.92 green:0.92 blue:0.92 alpha:1.00];
}
// Load default object :
- (void)viewDidLoad {
[self load3DObjectName:#"cube.dae" nodeName:#"cube1" zPhone:-40 zPad:-30];
}
//Trying to change the 3D object with button:
- (IBAction)nextObject:(id)sender {
[self load3DObjectName:#"redCube.dae" nodeName:#"cube2" zPhone:-40 zPad:-30];
}
- (IBAction)changeIt:(id)sender {
[self load3DObjectName:#"dayere.dae" nodeName:#"Sphere" zPhone:-40 zPad:-40];
}
Here is a source code :
https://www.dropbox.com/s/rrvbxmrb9wcrnoj/3D%20Objects%20Change.zip?dl=0
The code in the Dropbox version is not what I posted above. Here is the Dropbox version:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self load3DObjectName:#"cube.dae" nodeName:#"Cube" zPhone:-40 zPad:-40];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)load3DObjectName:(NSString*)name nodeName:(NSString*)nodeName zPhone:(CGFloat)positioniPhone zPad:(CGFloat)positioniPad{
SCNScene * scene = [SCNScene sceneNamed:name];
// retrieve the ship node
SCNNode *node = [scene.rootNode childNodeWithName:nodeName recursively:YES];
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
node.position = SCNVector3Make(0, 0, positioniPhone);
}
else {
node.position = SCNVector3Make(0, 0, positioniPad);
}
_the3DView.scene = scene;
_the3DView.autoenablesDefaultLighting = YES;
_the3DView.allowsCameraControl = YES;
_the3DView.backgroundColor = [UIColor colorWithRed:0.92 green:0.92 blue:0.92 alpha:1.00];
}
- (IBAction)changeIt:(id)sender {
[self load3DObjectName:#"dayere.dae" nodeName:#"Sphere" zPhone:-40 zPad:-40];
}
So I have take a look at your project. here is the issue: you don't have camera in you scenes. So I put camera for your each scenes manually at the same distance, and moved the nodes as desired. here is what it looks like now:
- (void)load3DObjectName:(NSString*)name nodeName:(NSString*)nodeName zPhone:(CGFloat)positioniPhone zPad:(CGFloat)positioniPad
{
SCNScene * scene = [SCNScene sceneNamed:name];
SCNNode *node = [scene.rootNode childNodeWithName:nodeName recursively:YES];
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone)
{
node.position = SCNVector3Make(0, 0, positioniPhone);
} else {
node.position = SCNVector3Make(0, 0, positioniPad);
}
SCNCamera *cam = [[SCNCamera alloc] init];
cam.xFov = 35;
cam.yFov = 35;
cam.zFar = 5000;
cam.zNear = 0;
SCNNode *camNode = [[SCNNode alloc] init];
camNode.camera = cam;
camNode.position = SCNVector3Make(0, 0, 400);
[scene.rootNode addChildNode:camNode];
_the3DView.pointOfView = camNode;
_the3DView.scene = scene;
_the3DView.autoenablesDefaultLighting = YES;
_the3DView.allowsCameraControl = YES;
_the3DView.backgroundColor = [UIColor colorWithRed:0.92 green:0.92 blue:0.92 alpha:1.00];
}
and I also changed the your object positions for testing purposes:
- (IBAction)changeIt:(id)sender
{
[self load3DObjectName:#"dayere.dae" nodeName:#"Sphere" zPhone:-340 zPad:-340];
}
and this one as well:
- (void)viewDidLoad
{
[super viewDidLoad];
[self load3DObjectName:#"cube.dae" nodeName:#"Cube" zPhone:-40 zPad:-40];
}
and last but not least, the screen shots:
It looks to me like your second round of code works fine. I notice that you're loading different scenes/nodes in the Dropbox sample than you are in the code you posted first. So that makes me think there's something in your DAE file that isn't what you expect it to be. Check for node names that are missing or misspelled. Add NSAssert calls after your childNodeWithName: and sceneNamed: calls, to make sure that you really did successfully load the scene and find the child node.
SCNScene * scene = [SCNScene sceneNamed:name];
NSAssert(scene, #"failed to load scene named %#", name);
// retrieve the ship node
SCNNode *node = [scene.rootNode childNodeWithName:nodeName recursively:YES];
NSAssert(node, #"couldn't fine child node named %#", nodeName);
If the NSAsserts fail, use your 3D editing tool to fix your Collada file.
Note also that when you replace the3DView.scene (aside: please don't bang instance variables directly, use accessor methods instead!), you're replacing the camera and lighting too. So you should expect a jerk in the camera positioning. Maybe you want to animate this? Or remove/replace nodes for the objects, without messing with camera and lighting?
When I run the code you posted to Dropbox, I see a screen full of red. When I zoom away, and rotate the camera, it looks like this:
After I tap the "Button" button, I see this:

SpriteKit Scene Pause

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.

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]];
}];
}

Sprite-Kit Change Image of Node when screen gets touched

There is a hero that is controlled by tapping on the screen. I want the hero too look a little different every time the screen gets touched.
What I did is setting up two images that are a little different. I want the image of the hero to be changed when there is a touch event.
Until now I set up an array to save the information in but it kinda won't work out:
NSMutableArray *heroFrames = [NSMutableArray array];
NSString* textureName = nil;
if (UITouchPhaseBegan) {
textureName = #"hero1.gif";
}
else {
textureName = #"hero2.gif";
}
SKTexture* texture = [SKTexture textureWithImageNamed:textureName];
[heroFrames addObject:texture];
[self setHeroFrames:HeroFrames];
self.hero = [SKSpriteNode spriteNodeWithTexture:[_heroFrames objectAtIndex:1]];
I get an exception when running this.. Any other idea how to achieve my problem?
Thanks guys!
Welcome to SO.
Try this code:
#import "MyScene.h"
#implementation MyScene
{
BOOL myBool;
SKSpriteNode *hero;
SKTexture *texture1;
SKTexture *texture2;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
myBool = false;
texture1 = [SKTexture textureWithImageNamed:#"hero1.gif"];
texture2 = [SKTexture textureWithImageNamed:#"hero2.gif"];
hero = [SKSpriteNode spriteNodeWithTexture:texture1];
hero.position = CGPointMake(200, 150);
[self addChild:hero];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if(myBool == true)
{
hero.texture = texture1;
myBool = false;
} else
{
hero.texture = texture2;
myBool = true;
}
}

Deallocate SKScene after transition to another SKScene in SpriteKit

I have a view controller that has three skscenes as children.
When I transition from one to another, the old skscene doesn't get deallocated.
I want it to get deallocated as if it was never there.
Example:
When I first load the app, only 1 skscene is visible (say it takes up 100mb memory), then I transition to another (100mb more), and then the third (300mb memory).
I would end up with 300mb memory and I want to have 100 at all times.
How can I achieve this?
My view controller:
//
// ViewController.m
// Paddle Jumper
//
// Created by Chance Daniel on 1/18/14.
// Copyright (c) 2014 Max Hudson. All rights reserved.
//
#import "Flurry.h"
#import "ViewController.h"
#import "startViewController.h"
#implementation ViewController{
BOOL sceneSetUp;
}
- (void)viewWillLayoutSubviews
{
if(!sceneSetUp){
[super viewWillLayoutSubviews];
// Configure the view.
SKView * skView = (SKView *)self.view;
//skView.showsFPS = YES;
//skView.showsNodeCount = YES;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if([[defaults objectForKey:#"firstTime"] intValue] != 1){
[defaults setObject:[NSNumber numberWithInt:1] forKey:#"firstTime"];
[defaults setObject:#"ggr" forKey:#"skinSelected"];
[defaults setObject:[NSNumber numberWithInt:2] forKey:#"ggrOwned"];
[defaults setObject:[NSNumber numberWithInt:5000] forKey:#"gona"];
[defaults setObject:[NSNumber numberWithInt:1500] forKey:#"points"];
[defaults setObject:[NSNumber numberWithInt:7] forKey:#"livesLeftValue"];
[defaults setObject:[NSNumber numberWithInt:3] forKey:#"shieldsLeftValue"];
[defaults setObject:[NSNumber numberWithInt:2] forKey:#"lvlTwoLeftValue"];
[defaults setObject:[NSNumber numberWithInt:0] forKey:#"lvlThreeLeftValue"];
}
if(![defaults objectForKey:#"tut_game1"]){
[defaults setObject:[NSNumber numberWithInt:1] forKey:#"tut_game1"];
[defaults setObject:[NSNumber numberWithInt:1] forKey:#"tut_store"];
[defaults setObject:[NSNumber numberWithInt:1] forKey:#"tut_daily"];
}
[defaults synchronize];
// Create and configure the scene.
SKScene * startScene = [StartViewController sceneWithSize:skView.bounds.size];
startScene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:startScene];
//[skView presentScene:scene];
sceneSetUp = YES;
}
}
-(void) switchScene{
}
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
return UIInterfaceOrientationMaskAllButUpsideDown;
} else {
return UIInterfaceOrientationMaskAll;
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#end
An SKScene that won't release: //
// SettingsSKScene.m
// Paddle Jumper
//
// Created by Max Hudson on 3/15/14.
// Copyright (c) 2014 Max Hudson. All rights reserved.
//
#import "SettingsSKScene.h"
#import "gameViewController.h"
#import "StoreScene.h"
#interface SettingsSKScene (){
SKSpriteNode *bg;
SKSpriteNode *masterOverlay;
SKSpriteNode *wbox;
SKSpriteNode *gamecenter;
SKSpriteNode *max;
SKSpriteNode *chance;
SKSpriteNode *bryce;
SKSpriteNode *home;
SKSpriteNode *play;
SKSpriteNode *chance_link;
SKSpriteNode *max_link;
SKSpriteNode *bryce_link;
SKSpriteNode *fbButton;
SKSpriteNode *fbToggleYes;
SKSpriteNode *fbToggleNo;
SKLabelNode *story1;
SKLabelNode *story2;
SKLabelNode *story3;
SKLabelNode *chance_name;
SKLabelNode *max_name;
SKLabelNode *bryce_name;
SKLabelNode *chance_role;
SKLabelNode *max_role;
SKLabelNode *bryce_role;
SKLabelNode *chance_handle;
SKLabelNode *max_handle;
SKLabelNode *bryce_handle;
SKTexture *bg_texture;
SKTexture *gamecenter_texture;
SKTexture *wbox_texture;
SKTexture *max_texture;
SKTexture *chance_texture;
SKTexture *bryce_texture;
SKTexture *home_texture;
SKTexture *play_texture;
SKTexture *fb_texture;
SKTexture *toggle_yes_texture;
SKTexture *toggle_no_texture;
}
#end
#implementation SettingsSKScene
-(id) initWithSize:(CGSize)size{
if(self = [super initWithSize:size]){
masterOverlay = [SKSpriteNode spriteNodeWithColor:[SKColor blackColor] size:self.frame.size];
masterOverlay.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
[self addChild:masterOverlay];
bg_texture = [SKTexture textureWithImageNamed:#"settings_bg"];
wbox_texture = [SKTexture textureWithImageNamed:#"white_rect_bg"];
gamecenter_texture = [SKTexture textureWithImageNamed:#"gc"];
max_texture = [SKTexture textureWithImageNamed:#"max"];
chance_texture = [SKTexture textureWithImageNamed:#"chance"];
bryce_texture = [SKTexture textureWithImageNamed:#"bryce"];
home_texture = [SKTexture textureWithImageNamed:#"home_light"];
play_texture = [SKTexture textureWithImageNamed:#"play_light"];
fb_texture = [SKTexture textureWithImageNamed:#"fb_light"];
toggle_yes_texture = [SKTexture textureWithImageNamed:#"toggle_yes"];
toggle_no_texture = [SKTexture textureWithImageNamed:#"toggle_no"];
NSArray *to_preload = #[bg_texture, wbox_texture, gamecenter_texture, max_texture, chance_texture, bryce_texture, home_texture, play_texture, fb_texture, toggle_yes_texture, toggle_no_texture];
[SKTexture preloadTextures:to_preload withCompletionHandler:^{
[self fadeRemove:masterOverlay];
[self initialize];
}];
}
return self;
}
-(void) fadeRemove: (SKNode *) node{
double duration = arc4random() % 10;
duration *= 0.05;
SKAction *fadeOut = [SKAction fadeOutWithDuration:0.1+duration];
SKAction *remove = [SKAction runBlock:^{
[node removeFromParent];
}];
[node runAction:[SKAction sequence:#[fadeOut, remove]]];
}
-(void) initialize{
bg = [SKSpriteNode spriteNodeWithTexture:bg_texture];
bg.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
bg.zPosition = 0;
wbox = [SKSpriteNode spriteNodeWithTexture:wbox_texture];
wbox.position = CGPointMake(self.frame.size.width/2, 70);
wbox.alpha = 0.8;
wbox.zPosition = 2;
gamecenter = [SKSpriteNode spriteNodeWithTexture:gamecenter_texture];
gamecenter.position = CGPointMake(self.frame.size.width/2 + 100, self.frame.size.height/2-2);
gamecenter.name = #"gc";
gamecenter.zPosition = 2;
fbButton = [SKSpriteNode spriteNodeWithTexture:fb_texture];
fbButton.size = CGSizeMake(36, 36);
fbButton.position = CGPointMake(self.frame.size.width/2 - 115, self.frame.size.height/2-2);
fbButton.name = #"fb";
fbButton.zPosition = 2;
fbToggleNo = [SKSpriteNode spriteNodeWithTexture:toggle_no_texture];
fbToggleNo.position = CGPointMake(self.frame.size.width/2 - 50, self.frame.size.height/2-2);
fbToggleNo.size = CGSizeMake(fbToggleNo.size.width/3, fbToggleNo.size.height/3);
fbToggleNo.name = #"fbno";
fbToggleNo.zPosition = 2;
fbToggleYes = [SKSpriteNode spriteNodeWithTexture:toggle_yes_texture];
fbToggleYes.position = CGPointMake(self.frame.size.width/2 - 70, self.frame.size.height/2-2);
fbToggleYes.name = #"fbyes";
fbToggleYes.zPosition = 2;
int hpBuffer = 40;
int hpZ = 2;
home = [SKSpriteNode spriteNodeWithTexture:home_texture];
home.position = CGPointMake(hpBuffer, self.frame.size.height - hpBuffer);
home.zPosition = hpZ;
home.name = #"home";
play = [SKSpriteNode spriteNodeWithTexture:play_texture];
play.position = CGPointMake(self.frame.size.width - hpBuffer, self.frame.size.height - hpBuffer);
play.zPosition = hpZ;
play.name = #"play";
[self addChild:bg];
[self addChild:wbox];
[self addChild:gamecenter];
[self addChild:home];
[self addChild:play];
[self addChild:fbButton];
[self addChild:fbToggleNo];
[self addCredits];
[self addStory];
}
-(void) addCredits{
/* images */
int cmbZ = wbox.zPosition + 1;
int cmbY = wbox.position.y;
int cmbXUnit = 132;
int cmbX = self.frame.size.width/2 - (3*cmbXUnit)/2 + 20;
int cmbWidth = 40;
chance = [SKSpriteNode spriteNodeWithTexture:chance_texture];
max = [SKSpriteNode spriteNodeWithTexture:max_texture];
bryce = [SKSpriteNode spriteNodeWithTexture:bryce_texture];
chance.zPosition = cmbZ;
max.zPosition = cmbZ;
bryce.zPosition = cmbZ;
chance.position = CGPointMake(cmbX+cmbXUnit*0, cmbY+3);
max.position = CGPointMake(cmbX+cmbXUnit*1 + 10, cmbY);
bryce.position = CGPointMake(cmbX+cmbXUnit*2 + 10, cmbY+5);
chance.size = CGSizeMake(cmbWidth, (chance.size.height/chance.size.width)*cmbWidth);
max.size = CGSizeMake(cmbWidth, (max.size.height/max.size.width)*cmbWidth);
bryce.size = CGSizeMake(cmbWidth, (bryce.size.height/bryce.size.width)*cmbWidth);
[self addChild:chance];
[self addChild:max];
[self addChild:bryce];
/* names */
int cmb_nameXUnit = 30;
int cmb_nameY = wbox.position.y - 10;
int cmb_nameFontSize = 18;
chance_name = [SKLabelNode labelNodeWithFontNamed:#"BebasNeue"];
max_name = [SKLabelNode labelNodeWithFontNamed:#"BebasNeue"];
bryce_name = [SKLabelNode labelNodeWithFontNamed:#"BebasNeue"];
chance_name.text = #"Chance Daniel";
max_name.text = #"Max Hudson";
bryce_name.text = #"Bryce Daniel";
chance_name.fontColor = [SKColor blackColor];
max_name.fontColor = [SKColor blackColor];
bryce_name.fontColor = [SKColor blackColor];
chance_name.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
max_name.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
bryce_name.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
chance_name.position = CGPointMake(chance.position.x + cmb_nameXUnit, cmb_nameY);
max_name.position = CGPointMake(max.position.x + cmb_nameXUnit, cmb_nameY);
bryce_name.position = CGPointMake(bryce.position.x + cmb_nameXUnit, cmb_nameY);
chance_name.fontSize = cmb_nameFontSize;
max_name.fontSize = cmb_nameFontSize;
bryce_name.fontSize = cmb_nameFontSize;
chance_name.zPosition = cmbZ;
max_name.zPosition = cmbZ;
bryce_name.zPosition = cmbZ;
[self addChild:chance_name];
[self addChild:max_name];
[self addChild:bryce_name];
/* roles */
int cmb_roleY = wbox.position.y - 25;
int cmb_roleFontSize = 11;
chance_role = [SKLabelNode labelNodeWithFontNamed:#"BebasNeue"];
max_role = [SKLabelNode labelNodeWithFontNamed:#"BebasNeue"];
bryce_role = [SKLabelNode labelNodeWithFontNamed:#"BebasNeue"];
chance_role.text = #"Programmer";
max_role.text = #"Programmer";
bryce_role.text = #"Graphic Designer";
chance_role.fontColor = [SKColor darkGrayColor];
max_role.fontColor = [SKColor darkGrayColor];
bryce_role.fontColor = [SKColor darkGrayColor];
chance_role.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
max_role.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
bryce_role.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
chance_role.position = CGPointMake(chance.position.x + cmb_nameXUnit + 19, cmb_roleY);
max_role.position = CGPointMake(max.position.x + cmb_nameXUnit + 12, cmb_roleY);
bryce_role.position = CGPointMake(bryce.position.x + cmb_nameXUnit + 6, cmb_roleY);
chance_role.fontSize = cmb_roleFontSize;
max_role.fontSize = cmb_roleFontSize;
bryce_role.fontSize = cmb_roleFontSize;
chance_role.zPosition = cmbZ;
max_role.zPosition = cmbZ;
bryce_role.zPosition = cmbZ;
[self addChild:chance_role];
[self addChild:max_role];
[self addChild:bryce_role];
/* twitter handles */
int cmb_handY = wbox.position.y - 40;
int cmb_handFontSize = 10;
chance_handle = [SKLabelNode labelNodeWithFontNamed:#"BebasNeue"];
max_handle = [SKLabelNode labelNodeWithFontNamed:#"BebasNeue"];
bryce_handle = [SKLabelNode labelNodeWithFontNamed:#"BebasNeue"];
chance_handle.text = #"#ChanceOfThat";
max_handle.text = #"#max_hud";
bryce_handle.text = #"#BryceOfLife";
chance_handle.fontColor = [SKColor darkGrayColor];
max_handle.fontColor = [SKColor darkGrayColor];
bryce_handle.fontColor = [SKColor darkGrayColor];
chance_handle.position = CGPointMake(chance.position.x, cmb_handY);
max_handle.position = CGPointMake(max.position.x, cmb_handY);
bryce_handle.position = CGPointMake(bryce.position.x, cmb_handY);
chance_handle.fontSize = cmb_handFontSize;
max_handle.fontSize = cmb_handFontSize;
bryce_handle.fontSize = cmb_handFontSize;
chance_handle.zPosition = cmbZ;
max_handle.zPosition = cmbZ;
bryce_handle.zPosition = cmbZ;
[self addChild:chance_handle];
[self addChild:max_handle];
[self addChild:bryce_handle];
/* touchzones */
CGSize cmdL_size = CGSizeMake(120, 70);
SKColor *cmdL_color = [SKColor clearColor];
int cmdLZ = 3;
int cmdLX = cmbX + 40;
chance_link = [SKSpriteNode spriteNodeWithColor:cmdL_color size:cmdL_size];
max_link = [SKSpriteNode spriteNodeWithColor:cmdL_color size:cmdL_size];
bryce_link = [SKSpriteNode spriteNodeWithColor:cmdL_color size:cmdL_size];
chance_link.position = CGPointMake(cmdLX+cmbXUnit*0, cmbY);
max_link.position = CGPointMake(cmdLX+cmbXUnit*1 + 10, cmbY);
bryce_link.position = CGPointMake(cmdLX+cmbXUnit*2 + 10, cmbY);
chance_link.zPosition = cmdLZ;
max_link.zPosition = cmdLZ;
bryce_link.zPosition = cmdLZ;
chance_link.name = #"c_handle";
max_link.name = #"m_handle";
bryce_link.name = #"b_handle";
[self addChild:chance_link];
[self addChild:max_link];
[self addChild:bryce_link];
}
-(void) addStory{
int stX = self.frame.size.width/2;
int stZ = 2;
SKColor *stColor = [SKColor whiteColor];
story1 = [SKLabelNode labelNodeWithFontNamed:#"BebasNeue"];
story2 = [SKLabelNode labelNodeWithFontNamed:#"BebasNeue"];
story3 = [SKLabelNode labelNodeWithFontNamed:#"BebasNeue"];
story1.text = #"Gon";
story2.text = #"Help Gon-Gon and his friends get";
story3.text = #"Back to the time they came from!";
story1.fontColor = stColor;
story2.fontColor = stColor;
story3.fontColor = stColor;
story1.zPosition = stZ;
story2.zPosition = stZ;
story3.zPosition = stZ;
story1.position = CGPointMake(stX, self.frame.size.height - 55);
story1.fontSize = 50;
story2.position = CGPointMake(stX, self.frame.size.height - 95);
story2.fontSize = 30;
story3.position = CGPointMake(stX, self.frame.size.height - 120);
story3.fontSize = 20;
[self addChild:story1];
[self addChild:story2];
[self addChild:story3];
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touchUI = [touches anyObject];
CGPoint touchPoint = [touchUI locationInNode:self];
SKNode *touchNode = [self nodeAtPoint:touchPoint];
if([touchNode.name isEqualToString:#"home"]){
StartViewController *svc = [[StartViewController alloc] initWithSize:self.size];
SKTransition *fade = [SKTransition fadeWithColor :[SKColor blackColor] duration:0.4];
[self.view presentScene:svc transition:fade];
}
if([touchNode.name isEqualToString:#"play"]){
gameViewController *gvc = [[gameViewController alloc] initWithSize:self.size];
SKTransition *fade = [SKTransition fadeWithColor :[SKColor blackColor] duration:0.4];
[self.view presentScene:gvc transition:fade];
}
if([touchNode.name isEqualToString:#"gc"]){
NSDictionary * dict = [[NSDictionary alloc]initWithObjectsAndKeys:[NSNumber numberWithBool:1], #"showGC", nil];
[[NSNotificationCenter defaultCenter]postNotificationName:#"kNotificationUpdateBannerView" object:self userInfo:dict];
}
if([touchNode.name isEqualToString:#"c_handle"]){
NSURL *urlApp = [NSURL URLWithString: [NSString stringWithFormat:#"%#", #"twitter:///user?screen_name=ChanceOfThat"]];
int worked = [[UIApplication sharedApplication] openURL:urlApp];
if(!worked){
NSURL *urlApp = [NSURL URLWithString: [NSString stringWithFormat:#"%#", #"https://twitter.com/#!/ChanceOfThat"]];
[[UIApplication sharedApplication] openURL:urlApp];
}
}
if([touchNode.name isEqualToString:#"m_handle"]){
NSURL *urlApp = [NSURL URLWithString: [NSString stringWithFormat:#"%#", #"twitter:///user?screen_name=max_hud"]];
int worked = [[UIApplication sharedApplication] openURL:urlApp];
if(!worked){
NSURL *urlApp = [NSURL URLWithString: [NSString stringWithFormat:#"%#", #"https://twitter.com/#!/max_hud"]];
[[UIApplication sharedApplication] openURL:urlApp];
}
}
if([touchNode.name isEqualToString:#"b_handle"]){
NSURL *urlApp = [NSURL URLWithString: [NSString stringWithFormat:#"%#", #"twitter:///user?screen_name=BryceOfLife"]];
int worked = [[UIApplication sharedApplication] openURL:urlApp];
if(!worked){
NSURL *urlApp = [NSURL URLWithString: [NSString stringWithFormat:#"%#", #"https://twitter.com/#!/BryceOfLife"]];
[[UIApplication sharedApplication] openURL:urlApp];
}
}
}
#end
A similar problem was faced by the person who asked this question.
When asked whether he was able to solve it, they said:
Yes, I did, there wasn't anything I could do about it from the scene
or Sprite Kit for that matter, I simply needed to remove the scene and
the view containing it completely from the parent view, cut all its
bonds to the other parts of the system, in order for the memory to be
deallocated as well.
You should use separate views for each of your scene and transition between these views. You can follow these steps to make it look natural:
1 - At the point you want to transition from one scene to the other, take a snapshot of the scene using the following code:
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, scale);
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Then, add this image as a subview of the SKView of the current scene, and remove the scene.
2 - Initialise the new scene on another view, and transition between the two views using UIView animation.
3 - Remove the first view from it's superview.
I am just starting out with SpriteKit in Swift and I had the same problem: My intro scene was playing some music that I wanted to stop after transitioning to the main menu scene, and I figured the deinit would be a good place to put the AVAudioPlayer.stop() call in, but deinit was never being called.
After looking around I learned that it may be because of some strong references to the scene, so in my GameViewController:UIViewController subclass I changed this code:
let intro = IntroScene(size: skView.bounds.size)
skView.presentScene(intro)
to
skView.presentScene(IntroScene(size: skView.bounds.size))
and in the intro scene, that I wanted to be deallocated, I changed
let mainMenu = MainMenuScene(size: self.size)
let crossFade = SKTransition.crossFadeWithDuration(1)
self.scene.view.presentScene(mainMenu, transition: crossFade)
to
self.scene.view.presentScene(MainMenuScene(size: self.size),
transition: SKTransition.crossFadeWithDuration(1))
and it worked! After the transition was complete the deinit method got called.
I assume that the outgoing scene was not being deinitialized because there were variables holding references to it.
December 2019/Swift 5
Update:
My layout:
I have a single view controller that contains 2 SKViews which each of them have their own unique SKScene that are presented at the same time. One SKView & its SKScene is the main overworld where the player character is rendered, controlled, NPC's rendered, camera tracking, the whole shebang etc., & the other SKView & its SKScene display the mini map of the overworld.
You can imagine there are also quite a number of SKSpriteNode's & lot of them ALWAYS have some kind of SKAction/animation running non-stop (swaying trees for instance). My SKScenes even contain their own arrays pointing at specific groups of SKSpriteNodes, such as, character nodes, building nodes, tree nodes. This is for quick access & convenience purposes.
Plus, I have a few singletons that either contain an array of SKTextures, or character models, etc.. They are kept around as an optimization for quick data access rather than reading from disc/accessing storage every time I need something.
There are even UIKit elements used for the game UI inside the view controller.
Many other objects, such as, my models that contain data on characters, buildings, the entire game session all have some kind of delegates pointing at someone. On top of all of this the codebase is massive.
After observing memory in the debug session I found out the sure-fire way to make sure nothing is retained is to absolutely make sure the following:
Memory handling:
Pass in nil for scene presentation & set that skview's property/pointer to nil as well
Remove all view controller primary view subviews
Absolutely set every delegate you have to nil !!!!!!!!!!!!!!
Remove all observers IF you happen to have any
Anywhere you have an array/dictionary or pointer to some kind of object YOU created, set it to nil/empty it
CRITICAL: put all of the above into a single function/method & make sure to call it right before changing view controllers!
*NOTE: IF you have 1 view controller for the entire app (congrats on squeezing everything - youz overlord squeezer), then do NOT nil everything & use caution. BUT the previous scene still needs to be set to nil during presentation.
So something like this if you're jumping between view controllers:
/// Remove all pointers to any data, nodes & views.
fileprivate func cleanUp() {
guard self.skView != nil else { return }
// Session was passing data updates to this view controller; time to nil it
self.gameSessionModel.delegate = nil
self.skView.presentScene(nil)
self.skViewMiniMap.presentScene(nil)
self.skView = nil
self.skViewMiniMap = nil
for subview in self.view.subviews {
subview.removeFromSuperview()
}
}
/// Take the user back to the main menu module/view controller.
fileprivate func handleMenuButton() {
// First, clean up everything
self.cleanUp()
// Then go to the other view controller
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "MainViewController")
self.show(view, sender: .none)
}
Call handleMenuButton() function or whatever function you use to present another view controller, but make sure the cleanUp() function is called within it!
*NOTE: Classes such as 'gameSessionModel' are entirely my own custom classes. Xcode will throw an error on such things (unless you magically have the same one...) so delete such things. They are only used as example code in case you have delegates pointing at the current view controller.
If you're only presenting different SKScene's with a single SKView, then your cleanUp() function can end up being "lighter" as such:
/// Remove all pointers to any data, nodes & views.
fileprivate func cleanUp() {
self.skView.presentScene(nil)
// Previous scene was set to nil, its deinit called. Now onto the new scene:
let gameScene = self.setupGameScene(id: "perhapsYouUseStringIDsToDisntiguishBetweenScenes?")
self.skView.presentScene(gameScene)
}
*NOTE: Do NOT forget to use the SKScene's deinit method to remove anything you won't need. It's a practice I use all the time for all my classes to untangle anything I might have missed.
There's nothing wrong with SKScene or SKView, as long as I can see. Make sure the scene instance is not strongly reference anywhere else, especially inside a block. Blocks are highly probable to be ignored.
More about weak reference inside a block: https://stackoverflow.com/a/17105368/571489
As far as I see, you do have a block strongly referencing the scene instance:
[SKTexture preloadTextures:to_preload withCompletionHandler:^{
[self fadeRemove:masterOverlay];
[self initialize];
}];
EDIT: My previous idea about an NSTimer was irrelevant
To make sure this is an issue isolated to this scene, override the dealloc methods of all scenes you might have (including this one) like this:
-(void)dealloc {
NSLog(#"Dealloc <scene name>");
}
Look at your other scene transitions, see if they deallocate properly. Find the differences between these scenes. This will help you see if it's an isolated issue or a bigger problem. Once you have the problem fixed be sure to comment out or remove the dealloc method as it is overriding the one that actually deallocates the memory. Hopefully this helps!

Resources