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?
Related
I'd like to add a timer to a simple SpriteKit game. Using the code suggested in an existing answer ("Spritekit - Creating a timer") causes 5 errors (s. comments below). Maybe there are other ideas, too!
Thanks a lot!
This is my .m file:
#implementation GameScene {
BOOL updateLabel;
SKLabelNode *timerLabel;
}
int timer;
- (id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.backgroundColor = [SKColor whiteColor];
timer = 0;
updateLabel = false;
timerLabel = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
timerLabel.text = #"0";
timerLabel.fontSize = 48;
timerLabel.fontColor = [SKColor greenColor];
timerLabel.position = CGPointMake(700,50);
[self addChild: timerLabel];
}
return self;
}
-(void) didMoveToView:(SKView *)view {
}
The following code gives me the trouble:
id wait = [SKAction waitForDuration:1.0];//Redefinition of 'wait' as different kind of symbol
id run = [SKAction runBlock:^{//Initializer element is not a compile-time constant
timer ++;
updateLabel = true;
}];
[node runAction:[SKAction repeatActionForever:[SKAction sequence:#[wait, run]]];//1. Expected identifier or '('; 2. Use of undeclared identifier 'node'; 3. Collection element of type 'pid_t (int *)' is not an Objective-C object
-(void)update:(CFTimeInterval)currentTime {
if(updateLabel == true){
timerLabel.text = [NSString stringWithFormat:#"%i",timer];
updateLabel = false;
}
}
#end
int timer = 0;
SKLabelNode timerLabel;
- (id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
SKAction *wait = [SKAction waitForDuration:1];
SKAction *performSelector = [SKAction performSelector:#selector(timerLabel) onTarget:self];
SKAction *removeChild = [SKAction runBlock:^{
[timerLabel removeFromParent];
}];
SKAction *increaseTimer = [SKAction runBlock:^{
timer++;
}];
SKAction *sequence = [SKAction sequence:#[removeChild, performSelector, wait, increaseTimer]];
SKAction *repeat = [SKAction repeatActionForever:sequence];
[self runAction:repeat];
}
return self;
}
-(void)timerLabel
{
timerLabel = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
timerLabel.text = [NSString stringWithFormat:#"%i", timer];
timerLabel.fontSize = 48;
timerLabel.fontColor = [SKColor greenColor];
timerLabel.position = CGPointMake(700,50);
[self addChild: timerLabel];
}
Try to call with perform selector. I hope this will work.I didn't test.
I am having trouble having my game begin paused so that the player has to press a button to start the game. I also have collision detection working, so when the two objects collide they just fall off of the screen. Here is my current code:
- (instancetype)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.physicsWorld.contactDelegate = self;
[self buildBackground];
[self startScrolling];
_firstPosition = CGPointMake(self.frame.size.width * 0.817f, self.frame.size.height * .40f);
_squirrelSprite = [SKSpriteNode spriteNodeWithImageNamed:#"squirrel"];
_squirrelSprite.position = _firstPosition;
_atFirstPosition = YES;
_squirrelSprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:10];
_squirrelSprite.physicsBody.categoryBitMask = squirrelHitCategory;
_squirrelSprite.physicsBody.contactTestBitMask = nutHitCategory;
_squirrelSprite.physicsBody.collisionBitMask = nutHitCategory;
_squirrelSprite.physicsBody.affectedByGravity = NO;
self.physicsWorld.contactDelegate = self;
[self addChild:_squirrelSprite];
// Declare SKAction that waits 2 seconds
SKAction *wait = [SKAction waitForDuration:3.0];
// Declare SKAction block to generate the sprites
SKAction *createSpriteBlock = [SKAction runBlock:^{
const BOOL isHeads = arc4random_uniform(100) < 50;
NSString* spriteName = isHeads ? #"lightnut.png" : #"darknut.png";
SKSpriteNode *nut = [SKSpriteNode spriteNodeWithImageNamed:spriteName];
BOOL heads = arc4random_uniform(100) < 50;
nut.position = (heads)? CGPointMake(257,600) : CGPointMake(50,600);
nut.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(200,160)];
nut.physicsBody.categoryBitMask = nutHitCategory;
nut.physicsBody.contactTestBitMask = squirrelHitCategory;
nut.physicsBody.collisionBitMask = squirrelHitCategory;
nut.physicsBody.affectedByGravity = NO;
self.physicsWorld.contactDelegate = self;
[self addChild: nut];
SKAction *moveNodeUp = [SKAction moveByX:0.0 y:-700.0 duration:1.3];
[nut runAction: moveNodeUp];
}];
// Combine the actions
SKAction *waitThenRunBlock = [SKAction sequence:#[wait,createSpriteBlock]];
// Lather, rinse, repeat
[self runAction:[SKAction repeatActionForever:waitThenRunBlock]];
}
return self;
}
My touchesBegan:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (_atFirstPosition)
{
SKAction *moveNodeLeft = [SKAction moveByX:-207.8 y:0.0 duration:0.85];
[_squirrelSprite runAction: moveNodeLeft withKey:#"moveleft"];
} else {
SKAction *moveNodeRight = [SKAction moveByX:207.8 y:0.0 duration:0.85];
[_squirrelSprite runAction: moveNodeRight withKey:#"moveright"];
}
_atFirstPosition = !_atFirstPosition;
_squirrelSprite.xScale *= -1.0;
}
Finally, my didBeginContact:
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody;
firstBody = contact.bodyA;
secondBody = contact.bodyB;
if(firstBody.categoryBitMask == squirrelHitCategory || secondBody.categoryBitMask == nutHitCategory)
{
}
}
I've been trying to figure this out for the last 2 weeks and I have read many tutorials and code to start the game with a simple start button and end screen, but nothing has worked. Any help is greatly appreciated!
I would use a navigation controller and have your start button on the root view and have that button push a viewcontroller that presents your game.
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!
I have my background set and it is scrolling horizontally, here is my code:
-(void)initalizingScrollingBackground
{
for (int i = 0; i < 2; i++)
{
SKSpriteNode *bg = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
bottomScrollerHeight = bg.size.height;
bg.position = CGPointMake(i * bg.size.width, 0);
bg.anchorPoint = CGPointZero;
bg.name = #"background";
[self addChild:bg];
}
}
and also this code:
- (void)moveBottomScroller
{
[self enumerateChildNodesWithName:#"background" usingBlock: ^(SKNode *node, BOOL *stop)
{
SKSpriteNode * bg = (SKSpriteNode *) node;
CGPoint bgVelocity = CGPointMake(-BG_VELOCITY, 0);
CGPoint amtToMove = CGPointMultiplyScalar(bgVelocity,_dt);
bg.position = CGPointAdd(bg.position, amtToMove);
//Checks if bg node is completely scrolled off the screen, if yes then put it at the end of the other node
if (bg.position.x <= -bg.size.width)
{
bg.position = CGPointMake(bg.position.x + bg.size.width*2,
bg.position.y);
}
[bg removeFromParent];
[self addChild:bg]; //Ordering is not possible. so this is a hack
}];
}
The second part makes the background scroll. Without it, the background is still.
Also, without the movebottomscroller, my sprite appears on top of the background. With the movebottomscroller, he appears behind the scrolling background. Is there any command to bring him to the front, above any other backgrounds?
Thank you!
Try the approach below, hope that works for you.
#interface GameScene()
#property (nonatomic) NSTimeInterval lastTimeSceneRefreshed;
#end
#implementation GameScene
- (instancetype)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
[self buildBackground];
[self startScrolling];
}
return self;
}
// This method will add 3 background nodes
- (void)buildBackground {
float centerX = CGRectGetMidX(self.frame);
SKSpriteNode *firstBackgroundNode = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
firstBackgroundNode.name = #"background";
firstBackgroundNode.position = CGPointMake(centerX,
firstBackgroundNode.size.height*firstBackgroundNode.anchorPoint.y);
[self addChild:firstBackgroundNode];
float previousYPosition = firstBackgroundNode.position.y;
for (int i = 0; i < 2; i++) {
SKSpriteNode *backgroundNode = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
backgroundNode.position = CGPointMake(centerX,
previousYPosition + backgroundNode.frame.size.height);
previousYPosition = backgroundNode.position.y;
backgroundNode.name = #"background";
[self addChild:backgroundNode];
}
}
- (void)update:(CFTimeInterval)currentTime {
// Updating background nodes
// We don't want to update backgrounds each frame (60 times per second)
// Once per second is enough. This will reduce CPU usage
if (currentTime - self.lastTimeSceneRefreshed > 1) {
[self backgroundNodesRepositioning];
self.lastTimeSceneRefreshed = currentTime;
}
}
- (void)backgroundNodesRepositioning {
[self enumerateChildNodesWithName:#"background" usingBlock: ^(SKNode *node, BOOL *stop)
{
SKSpriteNode *backgroundNode = (SKSpriteNode *)node;
if (backgroundNode.position.y + backgroundNode.size.height < 0) {
// The node is out of screen, move it up
backgroundNode.position = CGPointMake(backgroundNode.position.x, backgroundNode.position.y + backgroundNode.size.height * 3);
}
}];
}
- (void)startScrolling {
SKAction *moveAction = [SKAction moveByX:0 y:-200 duration:1];
[self enumerateChildNodesWithName:#"background" usingBlock: ^(SKNode *node, BOOL *stop)
{
[node runAction:[SKAction repeatActionForever:moveAction] withKey:#"movement"];
}];
}
I don't fully understand your approach here, but I can answer your question so I'll just go ahead and do that - I hope I haven't misunderstood.
SKNode has a method insertChild:atIndex: that would allow you to put the background nodes at the back, or put the sprite at the front.
SKNode Class Reference
I have a object in the scene which is determine by:
SKSpriteNode* object = [SKSpriteNode spriteNodeWithTexture:_objectTexture];
[object setScale:2];
object.position = CGPointMake(0, y);
object.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:object.size];
object.physicsBody.dynamic = NO;
then if my character hit it, it will stay there. Now, I want whenever the character hit the object, the object also move its upper half. How it moves depends on the index.
if index value in range 1, object change texture
if index value in range 2, object and character reflex by normal physical contact law
if index value in range 3, object broken into half
I tried to put
object.physicsBody.dynamic = YES;
then I saw it pulled down and disappear by gravity.
How do I set object dynamic and condition to satisfy the above need?
Thanks a lot for your help !
Added (as requested): My delegate method:
#interface MyScene () <SKPhysicsContactDelegate> {
SKSpriteNode* _character;
SKTexture* _object1;
SKTexture* _object2;
SKAction* _moveObjects;
SKNode* _moving;
SKNode* _objects;
BOOL _canRestart;
NSInteger _index;
SKTexture* characterTexture;
SKSpriteNode* _characterSprite;
}
later part (not sure if it is relevant)
-(id)initWithSize:(CGSize)size{
if (self = [super initWithSize:size]) {
/*Setup Scene here */
self.physicsWorld.gravity = CGVectorMake(0.0, -6.0);
self.physicsWorld.contactDelegate = self;
My objects are moved on scence by:
CGFloat distanceToMove = self.frame.size.width + 2 * _object1.size.width;
SKAction* moveObjects = [SKAction moveByX:-distanceToMove y:0 duration:0.01* distanceToMove];
SKAction* removeObjects = [SKAction removeFromParent];
_moveObjectsAndRemove = [SKAction sequence:#[moveObjects,removeObjects]];
Ps: add didBeginContact:
-(void)didBeginContact:(SKPhysicsContact *)contact{
if (_moving.speed > 0) {
//Score node
if ((contact.bodyA.categoryBitMask & scoreCategory) == scoreCategory || (contact.bodyB.categoryBitMask & scoreCategory) == scoreCategory)
{
_score++;
// Add a little visual
[_scoreLabelNode runAction:[SKAction sequence:#[[SKAction scaleTo:1.5 duration:0.1], [SKAction scaleTo:1.0 duration:0.1]]]];
}
else
//Collided with world
{
_moving.speed = 0;
_character.physicsBody.collisionBitMask = worldCategory;
[_character runAction:[SKAction rotateByAngle:M_PI * _character.position.y*0.01 duration:_character.position.y * 0.03] completion:^{_character.speed = 0; }];
....
}
}
You need to set
object.physicsBody.affectedByGravity = NO;