SpriteKit detect if node is removed from parent - ios

Is there a method that gets called every time a node is deleted? or added?
Along the terms of touchesBegan a method that gets called every time a touch on the screen happens.
or touchesMoved a method that gets called when a node gets touched and moved.
So is there such a method that gets called when a node gets removeFromParent?
I am currently using the update method to detect if a node is still present like:
SKNode *n = [self childNodeWithName: #"n"];
if (!n)
{
// no n
if (life != 0)
{
[self addChild:_n);
life--;
}
}
Now when ever that node is missing I remove 1 life, and then add the node again.
The problem is the update method is running too fast that 2 lives are removed every time the node is removed. I believe this is because adding the node back is kinda slower that the update method. So it takes 2 loops on the update method before it detects that the node is present again.

There is no such method. But you can create one.
Create a subclass of (for example) SKSpriteNode and override all "remove" methods (or just those you are using). Within that method send a message to whichever object needs to receive the removal event, or send a notification and register other objects to receive that notification. Don't forget to call the super implementation of the overridden method.

if you run this lines inside update
SKNode *n = [self childNodeWithName: #"n"];
if (!n)
that statement will run forever until you stop the game or your node is not equal to nil
to correct that behaviour you need to create a BOOL value
BOOL _shouldProceed;
change the name if you want
when you create your node set BOOL value to true or yes
- (void)createNode {
_shouldProceed = YES;
// Your implementation for the node
}
and put this inside update
SKNode *n = [self childNodeWithName:#"n"];
if (!n && _shouldProceed) {
_shouldProceed = NO;
if (life != 0) {
[self createNode];
life--;
}
}
if your node is equal to nil and BOOL value is equal to true/yes
you need to set BOOL value to false/NO
with this approach this statement only will happens once
when you create a new node with
[self createNode];
the BOOL value is set to true/YES
and that should solve your problems
Good Luck!!

Related

How would you get the update method to be called in a class other than MyScene in Objective-C's Sprite-Kit project?

So I'm in the process of making a Objective-C Sprite-Kit based game. I'm fairly new to Objective-C so I seem to be stuck on something involving the update method.
Traditionally in the default Sprite-Kit project it generates for you create a View which contains a scene which contains all the nodes that you want to manipulate. It also comes with a pre-generated, empty update method which since then I have replaced with my own (which works great). But I do not want my update method to based in the MyScene class and this is where my problem lies. If I leave the update method in the MyScene class I can see using NSLog that is in fact continuously being called like it should be, but if I move it say to the ViewControler class it won't be called at all.
My question is how would I go about getting it to call the update method in a class other than MyScene..? You may be asking why I would want to do this so I'll explain below but its not fully necessary to answer the question, however if you do have any tips on my program please let me know!
Traditional Sprite-Kit Project
ViewController --> Creates a MyScene (which is just inheriting from SKScene)
MyScene --> Creates all the nodes, and has the update method to hand game logic/graphics.
My Ideal Structure for my Sprite Game
ViewController --> Creates a ForegroundScene and BackgroundScene(still inherits from SKScene)
ForegroundScene --> creates all the nodes/characters for the game
BackgroundScene --> Creates the Sky, clouds, general landscape stuff for the game.
GameController --> contains the update method which calls displayView and updateGame.
Edit: To clear up any confusion, ForgegroundScene and BackgroundScene are essentialy the same thing as the MyScene class which was originally generated, but now there are two classes re-named, one containing stuff for the background, the other for the foreground.
Update Game is still located in the GameController class and it just does things like collision test, updates position of nodes, deletes characters that were killed.
DisplayView is located in the ViewController and its name is self-explanatory it displays the view of the game which is composed of nodes from the BackgroundScene and the ForegroundScene.
Anyway, this is my idea set up for a game, not sure how pratical it is. I'm very open to suggestions.
Here is my intended update method incase you are interested in seeing it but like I said I test it with NSLog and it ticks accordingly but in any other class its never called.
-(void)update:(CFTimeInterval)currentTime
{
const int TICKS_PER_SECOND = 35;
const int SKIP_TICKS = 1000 / TICKS_PER_SECOND; //these ivars would be moved to top of intended class
const int MAX_FRAMESKIP = 10;
NSDate *referenceDate = [NSDate date];
double timePassed_ms = [referenceDate timeIntervalSinceNow] * -1000.0;
NSUInteger next_game_tick = timePassed_ms;
int loops;
bool game_is_running = true;
while( game_is_running ) {
loops = 0;
while([referenceDate timeIntervalSinceNow] * -1000.0 > next_game_tick && loops < MAX_FRAMESKIP) {
[self updateGame] //calls GameController to do logic updates/collision test, etc.
NSLog(#"update game");
next_game_tick += SKIP_TICKS;
loops++;
}
[self displayGame]; //calls viewcontroler to display game
}
}
You could make the class that you want to notify every time you call -update: an instance variable of your scene, then just forward the message to that class. For example, create an instance variable in your scene of the class you want to notify.
#interface MyScene ()
#property (nonatomic) ClassToNotify *notify;
#end
Then in your -update: method, just notify the instance variable.
- (void)update:(CFTimeInterval)currentTime
{
// pass the message on
[self.notify notifyThatUpdateWasJustCalledWithTime:currentTime];
}
In this case, -notifyThatUpdateWasJustCalledWithTime: would be a method of your ClassToNotify class.

Accessing an instance variable from another class for cocos2d project?

In my cocos2d game, I have a "powerup" of sorts where, once attained, the character becomes blue, the platform becomes blue, and so does the background. I'm not sure if I am implementing this in the best way so if you have other suggestions please do share!:)
I created a class Blue that has an instance variable called blueCalled
I imported this class to both my "Platform" class as well as my "Player" class.
The powerup and player colliding code is inside my Platform class. This is the code for when the powerup is hit:
// try remove powerup
- (void) tryRemovePowerup
{
NSMutableArray * currentPowerUpArray = [self getcurrentPowersArr];
if(currentPowerUpArray)
{
int playerY = ((CCLayer*)(self.player)).position.y;
for(int x=0; x<[currentPowerUpArray count];x++)
{
CCSprite *powerup = [currentPowerUpArray objectAtIndex:x];
if(abs(powerup.position.x+powerup.parent.position.x-[Player initX])<50)
{
if(abs(powerup.position.y+blueberry.parent.position.y-playerY)<30 && powerup.visible && powerup.visible)
{
[power.parent powerup cleanup:YES];
CGSize winSize = [[CCDirector sharedDirector] winSize];
[[SimpleAudioEngine sharedEngine] playEffect:#"yay.wav" pitch:1 pan:0 gain:1];
// THIS IS WHERE ALL OF EFFECTS SHOULD HAPPEN
NSLog(#"powerup hit");
}
}
}
}
[self hitTestOB];
}
The place of // THIS IS WHERE ALL OF EFFECTS SHOULD HAPPEN is where I want to make everything blue. Right now, I plan to do this by setting the instance variable in the Blue class to YES.
In the methods that take care of the 1. frames of the main player, 2. background, and 3. platform frames, I have an if statement checking if the instance variable of the Blue class is YES or NO and it acts accordingly.
However, I don't know how to use the instance variable from the Blue class in my platform and player class, also, I'm not 100% confident this is the best way to implement a powerup.
Thank you for any help you can provide me :)
Go iOS!
As a general rule, if you want to access an iVar of another object, you should make a property to access it. This is not an exception to that rule, but I'll tell you how to do it anyways.
Objective C objects are inherently C structures. This means that the -> operator works on them. In this case, you would want to use blue->blueCalled, where blue is the instance of your Blue class.
Visibility is the key to this working (or failing). You can only use this if the iVar is declared within the #interface in the header file and not following and #private or #protected declaration.

Calling a BOOL method and dealing with the result

I am trying to work out the correct way to approach some methodology.
Workflow
When a game is created, I would like to first search to see if a game already exists with this user. If there is a game I will not create one and show a message to the user.
At present I have two methods:
+(void)createNewGameAgainst:(PFUser *)user2 withCompletion:(void (^)(BOOL success))completionHandler
+(BOOL)checkIfGameAlreadyExistsAgainst:(PFUser *)opponentUser
The createNewGame... method is called first. Then within this I make a call to [self checkIfGameAlreadyExistsAgainst:user2];.
How do I check the result of the second method, from within the first? So how do I determine what the BOOL value is of the call to the method checkIfGameAlreadyExistsAgainst?
Is this the correct way to approach this or is there a better/cleaner way possibly?
The return value of a function can be used like a variable:
BOOL gameExists = [self checkIfGameAlreadyExistsAgainst:user2]; // assign result to a new variable
if(gameExists == YES) // compare result to YES
{
}
You can skip creating a new variable and just compare the result
if ([self checkIfGameAlreadyExistsAgainst:user2] == YES) // compare result directly
{
}
And when the type is BOOL, you can omit the comparison and just do this:
if ([self checkIfGameAlreadyExistsAgainst:user2])
{
}

How to continuously update MKAnnotation coordinates on the MKMap

I have a GPSTestViewController class that has a MKMapView with added MKAnnotations (stored in a class called Bases). I'm trying to continuously update the coordinates for the MKAnnotations (with the updateBaseInformation method in Bases) so the bases is moving on the map. The update is invoked from the GPSTestViewController method locationUpdate (since it's called every second):
- (void)locationUpdate:(CLLocation *)location {
NSLog(#"locationUpdate");
self.cachedLocation = location;
[self centerTo:cachedLocation.coordinate];
//Trying to update the coordinates every second
[bases updateBaseInformation]; <--Program received signal: “EXC_BAD_ACCESS”
return;
}
But then I get the following message:
[bases updateBaseInformation]; Program received signal: “EXC_BAD_ACCESS”
Bases.m contains the following code (and even when some code is commented it crashes):
- (void)updateBaseInformation {
NSLog(#"Updating base information");
for(MyAnnotation *a in bases)
{
//CLLocationCoordinate2D c;
if([a.type compare:#"friend"] == 0)
{
//c.latitude = a.coordinate.latitude+0.001;
//c.longitude = a.coordinate.longitude+0.001;
//a.coordinate = c;
}
else if([a.type compare:#"enemy"] == 0)
{
//[a setCoordinate:CLLocationCoordinate2DMake(a.coordinate.latitude+0.002, a.coordinate.longitude+0.0012)];
}
}
}
My guess is that I'm accessing the objects that are already accessed somewhere else and that causes the EXC_BAD_ACCESS. I have spent many hours on searching, but without results. Why do I get this error and how should I do in order to make the Annotations move around on the map?
I have uploaded the complete project to (link removed).
Solution
The problem is now solved. The problem was that the array that holds the Annotations was autoreleased. So I changed the allocation from
bases = [NSMutableArray array];
to
bases = [[NSMutableArray array] retain];
Thanks in advance.
My guess is that I'm accessing the objects that are already accessed
somewhere else
You are probably accessing an object that no longer exists in memory - You need to check you are not deallocating an object which you are subsequently accessing. You should turn on Zombies for your scheme which will allow you to see when you are accessing a deallocated object by effectively keeping an object's memory under watch after the object has been deallocated. You can switch them on here: Scheme/edit scheme/enable zombie objects.

Implementing a life/score system in cocos2d

I am trying to implement a live/score system using cocos2d and box2d. my bodies are box2d bodies which collide as I want them to but the problem is that I have been trying to implement a score system where on each collision a life is removed or reduced from the lives left and after a certain number of collisions(say 3) the game is supposed to stop. On this occasion it doesn't but from the CCLOG I find out that it actually prints out the message I put in to display when the game manager is called but a weird thing is that it calls it forever(see the debugging window below)also it removes the object, "man" from the scene completely on starting the application.
I have a gameManager(singleton) class where in the .h file I have this declared
#interface GameManager : NSObject {
int lives;
}
-(void)removeLives : (int)val;
and in the gameManager.m file I have this method
-(void)removeLives : (int)val
{
lives -=val;
CCLOG(#"YOU HAVE LOST A LIFE MAN");
}
In my main gameLayer.mm file in the update method I have this code
if(kStateColliding){
if (lives > 0) {
man.visible = TRUE;
} else if (lives <= 0) {
[man stopAllActions];
man.visible = FALSE;
[[GameManager sharedGameManager] removeLives:1];
}
}
and the lives is initialised in gameManager init method thus
-(id)init { // 8
self = [super init];
if (self != nil) {
// Game Manager initialized
CCLOG(#"Game Manager Singleton, init");
lives = 3;
}
This is a screen shot of the debug console
Also note that I have a "Man" class which is Box2d class.
Am I doing the correct thing? Please can anyone help me out with how to go about implementing this system and where and how to make the appropriate calls.
Your call to [[GameManager sharedGameManager] removeLives:1] is within the else if (lives <= 0) block. It should only be called when lives > 0.
You will want to do something after the man has no lives left to prevent the if (kStateColliding) block from being called. Also, you will probably want to reset the mans position away from the object it is colliding with to prevent a single "collision" triggering multiple calls to removeLives.
Where are you declaring lives in gameLayer.mm? Make sure it is being set to the value stored in the singleton because with you current code, I dont see how it could possibly by set to 0 or below unless it is being initialized to 0 or you are changing its value elsewhere.
I would ask cocos2d questions like this in the cocos2d forums: http://www.cocos2d-iphone.org/forum/
There you will get answers more quickly, often within minutes.

Resources