SpriteKit Animation Issue - ios

I am having an issue with the sprite animation in my game.
In the current code posted below, the sprite will show on the screen but does not do the walking animation; it is only a static frame of one of the images in the walk cycle.
The init function:
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize: size]) {
self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
// Create the game nodes
// Background
_backgroundNode = [self createBackgroundNode];
[self addChild: _backgroundNode];
// Foreground
_foregroundNode = [SKNode node];
[self addChild: _foregroundNode];
// Add the Player
_thePlayer = [self createPlayer];
[_foregroundNode addChild:_thePlayer];
[self walkingQueen];
}
return self;
}
Function creating the player / filling in the animation array:
-(SKSpriteNode *)createPlayer
{
SKSpriteNode *playerNode = [SKSpriteNode node];
//SKSpriteNode *_player = [SKSpriteNode node];
NSMutableArray *walkFrames = [NSMutableArray array];
SKTextureAtlas *queenAnimatedAtlas = [SKTextureAtlas atlasNamed: #"QueenSprites"];
SKTexture *walk1 = [queenAnimatedAtlas textureNamed:#"queen7"];
SKTexture *walk2 = [queenAnimatedAtlas textureNamed:#"queen8"];
SKTexture *walk3 = [queenAnimatedAtlas textureNamed:#"queen9"];
walkFrames[0] = walk1;
walkFrames[1] = walk2;
walkFrames[2] = walk3;
_queenWalkingFrames = walkFrames;
/*
int numImages = 9;
for (int i = 7; i <= numImages; i++) {
NSString *textureName = [NSString stringWithFormat:#"queen%d", i];
SKTexture *temp = [queenAnimatedAtlas textureNamed: textureName];
[walkFrames addObject: temp];
}
*/
//_queenWalkingFrames = walkFrames;
SKTexture *temp = _queenWalkingFrames[0];
SKSpriteNode *_player = [SKSpriteNode spriteNodeWithTexture:temp];
_player.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
//[self addChild:_player];
//[self walkingQueen];
[playerNode addChild:_player];
[self walkingQueen];
return playerNode;
}
And the function for starting the walk animation:
-(void)walkingQueen
{
for (int i = 0; i < _queenWalkingFrames.count; i++) {
if (_queenWalkingFrames[i] == NULL) {
NSLog(#"ERROR!");
}
}
[_thePlayer runAction:[SKAction repeatActionForever:
[SKAction animateWithTextures:_queenWalkingFrames
timePerFrame:0.1f
resize:NO
restore:YES]] withKey:#"walkingInPlaceQueen"];
//return;
}
I played around with the code a bit and the animation DOES work IF I ignore the concept of layers (background / foreground layers) and stick the creation of the player into the init function (basically taking what's inside the createPlayer() and sticking it inside initWithSize()).
I do need to have the layers for my game and everything seems like it should work; had no idea that moving a few lines of code into its own function block would create such an issue. The check inside the walkingQueen() function did not return any errors so I assume my array has been filled with the sprite images.

_thePlayer = [self createPlayer];
This means _thePlayer is nil until after the createPlayer method returns. Thus in the walkingQueen method _thePlayer is nil and the actions don't run.
Replace this:
SKSpriteNode *playerNode = [SKSpriteNode node];
with
_thePlayer = [SKSpriteNode node];
and use _thePlayer instead of playerNode. You can also skip returning and assigning it, it's an ivar after all.

Re-wrote the createPlayer() function. Correct implementation is much simpler and as follows:
-(SKSpriteNode *)createPlayer
{
NSMutableArray *walkFrames = [NSMutableArray array];
SKTextureAtlas *queenWalkingAtlas = [SKTextureAtlas atlasNamed:#"QueenSprites"];
SKTexture *walk1 = [queenWalkingAtlas textureNamed:#"queen7"];
SKTexture *walk2 = [queenWalkingAtlas textureNamed:#"queen8"];
SKTexture *walk3 = [queenWalkingAtlas textureNamed:#"queen9"];
walkFrames[0] = walk1;
walkFrames[1] = walk2;
walkFrames[2] = walk3;
_queenWalkingFrames = walkFrames;
SKTexture *temp = _queenWalkingFrames[0];
_thePlayer = [SKSpriteNode spriteNodeWithTexture:temp];
_thePlayer.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
return _thePlayer;
}
Basically doing as the post above suggested, get rid of the ivars, use the function ONLY to fill the array with the appropriate images. Then, in the init function, call createPlayer(), add it to the foreground layer, and then call walkingQueen().

Related

spritekit didBeginContact not called

didBeginContact doesnt get called for some reason. How can I fix this? Thanks! I set the category bitmasks and the skphysicsContactDelegate, yet it is still not registering contacts. I've been stuck at this for some time now.
#import "MyScene.h"
#import "FuelNode.h"
#import "SKSpriteNode+DebugDraw.h"
typedef NS_OPTIONS(uint32_t, CollisionCategory) {
CollisionCategoryPlayer = 1 << 0,
CollisionCategoryFuel = 1 << 1,
};
#interface MyScene() <SKPhysicsContactDelegate>
#end
#implementation MyScene
{
SKNode *_playerNode;
SKNode *_backgroundNode;
SKNode *_foreGround;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size]) {
self.physicsWorld.contactDelegate = self;
_backgroundNode = [self createBackground];
[self addChild:_backgroundNode];
_foreGround = [SKNode node];
[self addChild:_foreGround];
//add a fuelNode
FuelNode *fuel = [self createFuelAtPosition:CGPointMake(160, 440)];
[_foreGround addChild:fuel];
_playerNode = [self createPlayer];
[_foreGround addChild:_playerNode];
SKAction *actionMove = [SKAction moveToY:-100 duration:3.0];
[fuel runAction:actionMove];
NSLog(#"yea");
}
return self;
}
-(SKNode *)createPlayer
{
CGSize playerPhysicsBody;
//Create player
SKNode *playerNode = [SKNode node];
SKSpriteNode *player = [SKSpriteNode spriteNodeWithImageNamed:#"wship-3.png"];
player.position = CGPointMake(self.size.width/2, 120);
[playerNode addChild:player];
//Add physics
playerPhysicsBody = CGSizeMake(player.size.width/2, player.size.height/2);
playerNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:playerPhysicsBody];
playerNode.physicsBody.dynamic = NO;
//Setup collision settings
playerNode.physicsBody.usesPreciseCollisionDetection = YES;
playerNode.physicsBody.categoryBitMask = CollisionCategoryPlayer;
playerNode.physicsBody.collisionBitMask = 0;
playerNode.physicsBody.contactTestBitMask = CollisionCategoryFuel;
[player attachDebugRectWithSize:playerPhysicsBody];
return playerNode;
}
-(SKNode *)createBackground
{
//Create background
SKNode *bgNode = [SKNode node];
SKSpriteNode *bg = [SKSpriteNode spriteNodeWithImageNamed:#"purple"];
bg.anchorPoint = CGPointZero;
[bgNode addChild:bg];
return bgNode;
}
- (FuelNode *)createFuelAtPosition:(CGPoint)position
{
// 1
FuelNode *node = [FuelNode node];
[node setPosition:position];
[node setName:#"NODE_FUEL"];
// 2
SKSpriteNode *sprite;
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"fuelBlue"];
[node addChild:sprite];
// 3
CGSize contactSize = CGSizeMake(sprite.size.width/2, sprite.size.height/2);
node.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:contactSize];
// 4
node.physicsBody.dynamic = NO;
//Setup collision settings
node.physicsBody.categoryBitMask = CollisionCategoryFuel;
node.physicsBody.collisionBitMask = 0;
//node.physicsBody.contactTestBitMask = CollisionCategoryPlayer;
[sprite attachDebugRectWithSize:contactSize];
//SKAction *actionMove = [SKAction moveToY:-100 duration:3.0];
//[node runAction:actionMove];
return node;
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
BOOL fuelCollision = NO;
SKNode *other = (contact.bodyA.node != _playerNode) ? contact.bodyA.node : contact.bodyB.node;
NSLog(#"collision");
fuelCollision = [(GameObjectNode *)other collisionWithPlayer:_playerNode];
}
#end
In order to enable contact detection, you need to set
node.physicsBody.dynamic = YES;
Have a look at the documentation as well.
Fixed! In the create player method I have mistakenly set the position of the player sprite instead of setting the position of the player node!

SpriteKit change Image after swipe gesture

I am trying to code a game. I got an object, that can jump and slide.
I want to hold the animation of 'run' while jumping, but while sliding, I want to change the image. My problem: the image won't show off , if I just change the image to 'slide7'.
Nothing happens.
The slide animation should appear only for about 4 seconds, than go again into the run animation. Any suggestions?
My Code :
-(void)Mensch{
SKTexture * MenschTexture1 = [SKTexture textureWithImageNamed:#"Mensch1"];
MenschTexture1.filteringMode = SKTextureFilteringNearest;
SKTexture * MenschTexture2 = [SKTexture textureWithImageNamed:#"Mensch2"];
MenschTexture2.filteringMode = SKTextureFilteringNearest;
SKAction * Run = [SKAction repeatActionForever:[SKAction animateWithTextures:#[MenschTexture1, MenschTexture2] timePerFrame:0.4]];
Mensch = [SKSpriteNode spriteNodeWithTexture:MenschTexture1];
Mensch.size = CGSizeMake(45, 45);
Mensch.position = CGPointMake(self.frame.size.width / 5, Boden.position.y + 73);
Mensch.zPosition = 2;
Mensch.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:Mensch.size];
Mensch.physicsBody.dynamic = YES;
Mensch.physicsBody.allowsRotation = NO;
Mensch.physicsBody.usesPreciseCollisionDetection = YES;
Mensch.physicsBody.restitution = 0;
Mensch.physicsBody.velocity = CGVectorMake(0, 0);
[Mensch runAction:Run];
[self addChild:Mensch];
}
- (void)didMoveToView:(SKView *)view
{
UISwipeGestureRecognizer *recognizerDown = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(handleSwipeDown:)];
recognizerDown.direction = UISwipeGestureRecognizerDirectionDown;
[[self view] addGestureRecognizer:recognizerDown];
}
-(void)handleSwipeDown:(UISwipeGestureRecognizer *)sender
{
[Mensch removeAllActions];
Mensch = [SKSpriteNode spriteNodeWithImageNamed:#"slide7.png"];
NSLog(#"Slide");
}
You are replacing the Mensch object. More specifically, you are replacing the pointer you have to the object, but that doesn't stop it from being displayed in the scene.
You can either:
Replace the SKTexture inside the sprite, which should be displayed immediately. This means Mensch.texture = [SKTexture textureWithImageNamed:#"slide7.png"];
Replace the Sprite. This entails removing the current sprite ([sprite removeFromParent]) and adding the new one ([self addChild:newSprite]).
I think you want the first one, since you don't have to recreate the Mensch object.
Might I add that you are performing a lot of Mensch-specific logic in this object? It would be better (from an Object Oriented standpoint) to move this creation code to an SKSpriteNode subclass Mensch.
#interface Mensch : SKSpriteNode
- (void) displayRunAnimation;
- (void) displaySlideAnimation;
#end
#implementation : Mensch
- (instancetype) init {
self = [super init];
if (self) {
// your initialization code here, including physics body setup
[self displayRunAnimation];
}
return self;
}
- (void) displayRunAnimation {
SKTexture * MenschTexture1 = [SKTexture textureWithImageNamed:#"Mensch1"];
MenschTexture1.filteringMode = SKTextureFilteringNearest;
SKTexture * MenschTexture2 = [SKTexture textureWithImageNamed:#"Mensch2"];
MenschTexture2.filteringMode = SKTextureFilteringNearest;
SKAction * Run = [SKAction repeatActionForever:[SKAction animateWithTextures:#[MenschTexture1, MenschTexture2] timePerFrame:0.4]];
// if you plan to call this often, you want to cache this SKAction, since creating it over and over is a waste of resources
[self runAction:Run];
}
- (void) displaySlideAnimation {
[self removeAllActions];
self.texture = [SKTexture textureWithImageNamed:#"slide7.png"];
NSLog(#"Slide");
// re-start the runAnimation after a time interval
[self performSelector:#selector(displayRunAnimation) withObject:nil afterDelay:4.0];
}

SKScene transition, memory deallocation

Ok, i've read the iOS Games book and i've searched for my question in a number of sites and although I do find that a number of people had this problem, I haven't found a solution as such.
I am building a game where I transition a few times from a SKScene to another SKScene. What happens is that even when I transition from a simple SKScene, as the example bellow, to an empty SKScene the memory does not get deallocated. I've heard that I need to remove any strong references to my SKScene, but I do not believe that my code bellow has any:
#import "LaunchScene.h"
#import "EmptyScene.h"
#implementation LaunchScene
{
float _scaleForDevice;
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
_scaleForDevice = 0.5;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
_scaleForDevice = 0.208335;
}
SKSpriteNode *bg;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && IS_WIDESCREEN) {
bg = [SKSpriteNode spriteNodeWithImageNamed:#"launchBackground-568h"];
} else {
bg = [SKSpriteNode spriteNodeWithImageNamed:#"launchBackground"];
}
bg.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
bg.zPosition = 10;
bg.name = #"launchBackground";
[self addChild:bg];
for (int i = 0; i < 5; i++) {
SKSpriteNode *launch = [SKSpriteNode spriteNodeWithImageNamed:[NSString stringWithFormat:#"launch%d",i]];
launch.position = CGPointMake(self.size.width, 0);
launch.anchorPoint = CGPointMake(0.5, 0.5);
launch.zPosition = 1000+i;
launch.name = [NSString stringWithFormat:#"launch%d",i];
[launch setScale:_scaleForDevice];
[self addChild:launch];
if (i == 0 || i == 2) {
SKAction* rotate = [SKAction rotateByAngle:-RadiansToDegrees(360) duration:10000*(i+1)];
[launch runAction:rotate];
} else if (i == 1) {
SKAction* rotate = [SKAction rotateByAngle:RadiansToDegrees(360) duration:10000*(i+1)];
[launch runAction:rotate];
}
}
SKSpriteNode *mainMenuBackground;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && IS_WIDESCREEN) {
mainMenuBackground = [SKSpriteNode spriteNodeWithImageNamed:#"mainMenuBackground-568h"];
} else {
mainMenuBackground = [SKSpriteNode spriteNodeWithImageNamed:#"mainMenuBackground"];
}
mainMenuBackground.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
mainMenuBackground.zPosition = 5;
mainMenuBackground.name = #"mainMenuBackground";
[self addChild:mainMenuBackground];
[SKActionEffects fadeOutAndRemove:bg duration:2];
SKSpriteNode *mainMenuGround;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && IS_WIDESCREEN) {
mainMenuGround = [SKSpriteNode spriteNodeWithImageNamed:#"mainMenuGround-568h"];
} else {
mainMenuGround = [SKSpriteNode spriteNodeWithImageNamed:#"mainMenuGround"];
}
mainMenuGround.position = CGPointMake(self.size.width, 0);
mainMenuGround.zPosition = 500;
mainMenuGround.anchorPoint = CGPointMake(1, 1);
mainMenuGround.name = #"mainMenuGround";
[self addChild:mainMenuGround];
SKAction *waitMainMenuGround = [SKAction waitForDuration:1];
SKAction *moveMainMenuGround = [SKAction moveToY:(self.size.height)/3 duration:0.3];
moveMainMenuGround.timingMode = SKActionTimingEaseInEaseOut;
SKAction *shakeMainMenuGround = [SKAction runBlock:^{
[SKActionEffects shakeSprite:mainMenuGround toDirection:1];
}];
SKAction *shrinkLaunch = [SKAction runBlock:^{
for (int i = 0; i < 5; i++) {
SKSpriteNode *launch = (SKSpriteNode*)[self childNodeWithName:[NSString stringWithFormat:#"launch%d",i]];
[SKActionEffects disappearAndRemove:launch];
}
}];
SKAction *presentMainMenu = [SKAction runBlock:^{
//Create and configure the scene.
EmptyScene * scene = [[EmptyScene alloc] initWithSize:self.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[self.view presentScene:scene];
}];
SKAction *group = [SKAction sequence:#[waitMainMenuGround, moveMainMenuGround, shakeMainMenuGround, shrinkLaunch, waitMainMenuGround, presentMainMenu]];
[mainMenuGround runAction:group];
}
return self;
}
#end
I have created an EmptyScene that is just that. It doesn't have anything in its init.
#import "EmptyScene.h"
#implementation EmptyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
}
return self;
}
#end
When I run just the EmptyScene from the start my memory is around 25mb. When I run the LaunchScene first, the memory goes to 95mb, and when it transitions to the EmptyScene, where one would expect the memory to go down to 25mb, it remains at 95mb. Any idea why and what I can do to solve this?

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!

CCAnimation crashes with unrecognized selector setDisplayFrame

I don't know why but my animation does not work.
I am trying to add an animated unit now via sprite sheet but through a set of sprites.
And something goes wrong
Here is the code of init method:
#implementation Enemy
-(id) initAt:(CGPoint)pos
{
if([super init])
{
self.position = pos;
CCSprite *start_sprite = [CCSprite spriteWithFile:#"unit1_00000.png"];
start_sprite.position = ccp(0,0);
[self addChild:start_sprite z:2];
const int FRAMES_COUNT = 10;
NSMutableArray* frames = [[NSMutableArray alloc]initWithCapacity:FRAMES_COUNT];
for (int i = 0; i < FRAMES_COUNT; i++)
{
NSString* file = [NSString stringWithFormat:#"unit1_0000%i.png", i];
CCTexture2D* texture = [[CCTextureCache sharedTextureCache] addImage:file];
CGSize texSize = texture.contentSize;
CGRect texRect = CGRectMake(0, 0, texSize.width, texSize.height);
CCSpriteFrame* frame = [CCSpriteFrame frameWithTexture:texture rect:texRect];
[frames addObject:frame];
}
_default_animation = [CCAnimation animationWithSpriteFrames:frames delay:0.1f];
_current_anim_action = [CCAnimate actionWithAnimation:_default_animation];
CCRepeatForever* repeat = [CCRepeatForever actionWithAction:_current_anim_action];
[self runAction:repeat];
}
return self;
}
The error is the following: -[Enemy setDisplayFrame:]: unrecognized selector sent to instance 0x8929bd0
setDisplayFrame is a method of CCSprite class, so in order to run a CCAnimation on Enemy class (which internally calls setDisplayFrame), Enemy must extend CCSprite, not CCNode.

Resources