Passing integer between scenes, iphone game sprite-kit - ios

I have a scene where the user chooses level in a game, then i want to switch to a game scene with the level the user chose. I want to create the game scene with the right level but to do this i need to pass an integer which is the level-number to the game scene so i know which level to load.
My question is, how do i accomplish this? I have looked at the userdata property of nodes but could not figure out how to use it to accomplish what i want. Any help would be deeply appreciated.

When you call [self.view presentScene:scene transition:transition] inside of your level selection scene, first set up your new scene with the level number. SO:
//GameScene.h
//This is the public interface for your game scene
#import SpriteKit;
#interface GameScene : SKScene
#property (nonatomic, assing)NSInteger levelNumber;
#end
And then
//Level Selection Scen
//Select.m
#import "GameScene.h"
... Rest of your code
User selects level and you prepare for transition and call
- (void)transitionToLevel:(NSInteger)level{
GameScene *destinationScene = [[GameScene alloc] initWithSize:self.size];
destinationScene.levelNumber = level;
SKTransition *transition = //Set up transition here
[self.view presentScene:destinationScene transition:transition];
}
And in your game scene's implementation
//GameScene.m
#import "GameScene.h"
#implementation GameScene
-(id)initWithSize:(CGSize){
if (self.levelNumber == 1){
[self playLevelOne]; //Where this is where you play your game
}
else if (self.levelNumber == 2){
[self playLevelTwo];
}
}
- (void)playLevelOne{
//Level Implementation
}
- (void)playLevelTwo{
//Level Implementation
}
#end
You would do this for each level you plan to support

// First Scene
NSUserDefaults.standardUserDefaults().setInteger(12345, forKey:"SCORE")
// Second Scene
if let myScore: AnyObject = NSUserDefaults.standardUserDefaults().objectForKey("SCORE") {
println(myScore)
finalScore = myScore as IntegerLiteralType
}

Related

Re-Assigning SKScene delegate protocol

So I create a delegate protocol and corresponding property in my scene. In my view controller that holds all my scenes I set the scenes delegate to itself so I can call a fullscreen ad(from the view controller) in the menu which is the first game scene called. This works great I can call the method. But after i transition to the level scenes then back to the menu scene calling my method to show fullscreen ad does nothing. Im assuming because the delegate being set to itself has been lost. How can I set the delegate back to itself within the game scene? This is how I do it inside the View controller.M
myScene = [GameScene unarchiveFromFile:#"GameScene"];
((GameScene *)myScene).mySceneDelegate = self; // i need to be able to do this within the scene
myScene.scaleMode = SKSceneScaleModeResizeFill;
// Present the scene.
[skView presentScene:myScene];
How I call the fullScreen method thats in my VC from my menu scene...
[self.mySceneDelegate showFullScreen:self];
...so in my code when I transition from my LevelOne scene I try to reassign the delegate with the following code but it nothing happens
-(void)goBackToMenu{
GameScene *newscene = [GameScene sceneWithSize:self.size];
[self.view presentScene:newscene transition:[SKTransition doorsCloseHorizontalWithDuration:1]];
((GameScene *)newscene).mySceneDelegate = self;
}
I have also tried setting the property attribute to Strong instead of weak with no luck either
**********************ALL CODE **************************************
More code.. my Viewcontroller.h
#protocol TCAMySceneDelegate;
#interface GameViewController : UIViewController<TCAMySceneDelegate>
#property(nonatomic, weak) id<TCAMySceneDelegate> mySceneDelegate;
#end
my view controller.m
- (void)viewDidLoad
{
[super viewDidLoad];
SKView * skView = (SKView *)self.view;
myScene = [GameScene unarchiveFromFile:#"GameScene"];
((GameScene *)myScene).mySceneDelegate = self;
// Present the scene.
[skView presentScene:myScene];
}
-(void)showFullScreen:(GameScene *)gameScene{
NSLog(#"Show Full Screen");
[revmobFS loadWithSuccessHandler:^(RevMobFullscreen *fs) {
[fs showAd];
NSLog(#"Ad loaded");
} andLoadFailHandler:^(RevMobFullscreen *fs, NSError *error) {
NSLog(#"Ad error: %#",error);
} onClickHandler:^{
NSLog(#"Ad clicked");
} onCloseHandler:^{
NSLog(#"Ad closed");
}];
[RevMobAds startSessionWithAppID:#"547251235f2a043608a66f1a"
withSuccessHandler:^{
NSLog(#"Session started with block");
// Here, you should call the desired ad unit
revmobFS = [[RevMobAds session] fullscreen];
[revmobFS showAd];
// [RevMobAds session].testingMode = RevMobAdsTestingModeWithAds;
revmobFS.delegate = self;
[revmobFS loadAd];
} andFailHandler:^(NSError *error) {
NSLog(#"Session failed to start with block");
}];
}
my game scene.h file..
#protocol TCAMySceneDelegate;
#interface GameScene : SKScene
#property (nonatomic, weak) id<TCAMySceneDelegate> mySceneDelegate;
#end
#protocol TCAMySceneDelegate <NSObject>
-(void)showFullScreen:(GameScene *)gameScene;
#end
my game scene.m file..
-(void)didMoveToView:(SKView *)view {
//Show Ad
[self.mySceneDelegate showFullScreen:self];
}
now the above showFullScreen: is called but online when the gameScene (my game menu) is presented the first time .. after transitioning to other scenes in the games such as my levels then go back to my menu(gamescene) it never gets called. Hope this is more clear now
how i switch to another scene from my game scene.m
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
NSLog(#"Level Selected: %#", node.name);
int x = [node.name intValue];
switch (x) {
case 1:{
LevelOne *newscene = [LevelOne sceneWithSize:self.size];
[self.view presentScene:newscene transition:[SKTransition doorsOpenVerticalWithDuration:1]];
break;
}
case 2:{
LevelTwo *newscene = [LevelTwo sceneWithSize:self.size];
[self.view presentScene:newscene transition:[SKTransition doorsOpenVerticalWithDuration:1]];
}
break;
}
case 3:{
LevelThree *newscene = [LevelThree sceneWithSize:self.size];
[self.view presentScene:newscene transition:[SKTransition doorsOpenVerticalWithDuration:1]];
}
break;
}
//..........
default:
break;
}
}
LevelOne.h code...
#import <SpriteKit/SpriteKit.h>
#import "DrawCanvas.h"
#import "GameScene.h"
#interface LevelOne : SKScene<SKPhysicsContactDelegate>
#end
Personally, I found your description as well as the code you posted a bit lacking in details as well as being a bit confusing. When providing code you will probably get better/quicker help if you provide more detail. For example, which file does goBackToMenu belong to?
How/in what context is
[self.mySceneDelegate showFullScreen:self];
called? I would assume that it is from some trigger point in the GameScene. And what is the method signature of showFullScreen?
And since you don't indicate what type mySceneDelegate is, you're making the readers guess what you mean.
So trying to decipher what you are asking...
Let's assume you have some delegate which is called GameSceneDelegate. I am also going to assume you already know SKSceneDelegate exists and are either using it already but just haven't posted that code or don't need to use it.
Your GameViewController would have it's interface defined as:
#interface GameViewController : UIViewController<GameSceneDelegate>
And your GameScene would have a property defined like this:
#property (nonatomic, weak) id<GameSceneDelegate> mySceneDelegate;
Note you should be using weak here. Because if your GameViewController ever has a strong reference to your GameScene, then you've just created a retain cycle.
You have a few options of when to set mySceneDelegate. If I know I must have a delegate defined, what I often do is add it as an argument to the initializers. This way it is clear that it must have a delegate. So, given you are using class methods to generate instances, you'd do something like this:
#interface GameScene : SKScene
#property (nonatomic, weak) id<GameSceneDelegate> mySceneDelegate;
+ (instancetype)unarchiveFromFile:(NSString *)file mySceneDelegate:(id<GameSceneDelegate>)mySceneDelegate;
+ (instancetype)sceneWithSize:(CGSize)size mySceneDelegate:(id<GameSceneDelegate>)mySceneDelegate;
#end
And you those methods would look like:
+ (instancetype)unarchiveFromFile:(NSString *)file mySceneDelegate:(id<GameSceneDelegate>)mySceneDelegate
{
GameScene *scene = [super unarchiveFromFile:file];
scene.mySceneDelegate = mySceneDelegate;
return scene;
}
+ (instancetype)sceneWithSize:(CGSize)size mySceneDelegate:(id<GameSceneDelegate>)mySceneDelegate
{
GameScene *scene = [super sceneWithSize:size];
scene.mySceneDelegate = mySceneDelegate;
return scene;
}
Note to make this work, you'll need to define this in the GameViewController.h (or a separate file for the category if you wish)
#interface SKScene (Unarchive)
+ (instancetype)unarchiveFromFile:(NSString *)file;
#end
The alternative is to immediately set mySceneDelegate right after creating your instance of the scene.
Realistically, if you are doing this, it should work and your delegate's should stay in tact. FWIW, I am actually doing this in my game. So I know it works.
Okay, so this is where things are hazy in your question. You indicate the delegate is "lost". The only way the delegate can be "lost" would be if someone explicitly sets it to nil. Or the other possiblity I suppose is your GameScene is being dealloc'd and you don't realize it.
I'd setup two things to help debug it.
In GameScene add
- (void)dealloc
{
NSLog(#"I'm being dealloc'd");
}
- (void)setMySceneDelegate:(id<GameSceneDelegate>)mySceneDelegate
{
_mySceneDelegate = mySceneDelegate;
NSLog(#"Setting mySceneDelegate");
}
So now you can try and determine where it is "getting lost". If any of those print out to the console at the time it is lost, simply set a breakpoint in those methods to see who is calling it.
One other pieces of info that would also help are
What do you mean assigning a delegate to itself? I have no idea what
that means.
You mentioned you assume the delegate is lost. Why? Do you have
evidence or is this purely a guess?
If you can provide some answers and better detail, we can probably figure out what is going awry.
Okay, now looking at your code, I think your problem is here in GameScene.m:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
NSLog(#"Level Selected: %#", node.name);
int x = [node.name intValue];
switch (x) {
case 1:{
LevelOne *newscene = [LevelOne sceneWithSize:self.size];
newScene.mySceneDelegate = self.mySceneDelegate;
// Or, if you can make it work, newscene = [LevelOne sceneWithSize:size mySceneDelegate:self.mySceneDelegate;
[self.view presentScene:newscene transition:[SKTransition doorsOpenVerticalWithDuration:1]];
break;
}
case 2:{
LevelTwo *newscene = [LevelTwo sceneWithSize:self.size];
newScene.mySceneDelegate = self.mySceneDelegate;
// Or, if you can make it work, newscene = [LevelOne sceneWithSize:size mySceneDelegate:self.mySceneDelegate;
[self.view presentScene:newscene transition:[SKTransition doorsOpenVerticalWithDuration:1]];
}
break;
}
case 3:{
LevelThree *newscene = [LevelThree sceneWithSize:self.size];
newScene.mySceneDelegate = self.mySceneDelegate;
// Or, if you can make it work, newscene = [LevelOne sceneWithSize:size mySceneDelegate:self.mySceneDelegate;
[self.view presentScene:newscene transition:[SKTransition doorsOpenVerticalWithDuration:1]];
}
break;
}
//..........
default:
break;
}
}
What I am doing here is assigning the the new scene's mySceneDelegate to the existing one being used.
You can clean this code up to be more readable.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
NSLog(#"Level Selected: %#", node.name);
int x = [node.name intValue];
GameScene *newScene = nil;
switch (x) {
case 1:
newScene = [LevelOne sceneWithSize:self.size];
// Or, if you can make it work, newScene = [LevelOne sceneWithSize:size mySceneDelegate:self.mySceneDelegate;
break;
case 2:
newScene = [LevelTwo sceneWithSize:self.size];
// Or, if you can make it work, newScene = [LevelTwo sceneWithSize:size mySceneDelegate:self.mySceneDelegate;
break;
case 3:
newScene = [LevelThree sceneWithSize:self.size];
// Or, if you can make it work, newScene = [LevelThree sceneWithSize:size mySceneDelegate:self.mySceneDelegate;
break;
// other cases..........
default:
break;
}
if (newScene) {
newScene.mySceneDelegate = self.mySceneDelegate;
[self.view presentScene:newScene transition:[SKTransition doorsOpenVerticalWithDuration:1]];
}
}
For your levels, you would need to do the following:
#import <SpriteKit/SpriteKit.h>
#import "DrawCanvas.h"
#import "GameScene.h"
#interface LevelXYZ : GameScene<SKPhysicsContactDelegate>
#end
Where XYZ is the level (eg. One). By doing this, you inherit mySceneDelegate from GameScene. If for some reason you are insistent that the level scenes are not inheriting from GameScene, then add a base class which holds the delegate. So that would be something like:
#interface MyBaseScene : SKScene
#property(nonatomic, weak) id<TCAMySceneDelegate> mySceneDelegate;
#end
And then GameScene would be this:
#interface GameScene : MyBaseScene
And your level scenes would be
#interface LevelXYZ : MyBaseScene<SKPhysicsContactDelegate>
Note a couple of things I did.
Changed newscene to newScene. In iOS, you typically use came case.
you were doing it for mySceneDelegate. Consistency is key.
Originally declared newScene as nil and assigned as need. Then at the end of the switch, if a newScene exists, assign the delegate and
present. This way, you are only doing it in one place. It makes life
easier. For example, if you wanted to change the duration for each
one, then what? You have to change ever case. Note there is a
downfall to this. If you wanted on level to have a special duration,
then it doesn't quite work. But you can change it by making the
duration and transition type variables.
BTW, I would strongly advise against having your scene class contain the code that does the actual transition. I would either have it call a delegate to do it or find another way (like using a notification that a scene change is needed). For example, in my current game, I have my view controller be the controller of all scene changes. The advantage of this is you can also call scene transitions from other external forces (and not have your scene be the one that manages it).
Lastly, you can hopefully see that the only way the issue was discoverable was by presenting the right code. I will agree it is hard to know what code to present, because if there is too much code, then people may not read it. It's a fine line, but some food for thought.

Passing the object from SKScene to UIViewController

I'm trying to pass an object from an SKScene to the current UIViewController where the scene is being shown, it's like I created a label that will only be triggered once the object from the scene reached a specific location, I understand that I can just easily create a SKLabel and have it added to the scene once the object reaches the location I want it to, but I'd rather do it the ViewController style way since I'll be adding a lot of objects that will do the same thing as my app Progress, that reason step aside, I did actually tried adding an sk label to see if it will work that way, Yes I was able to see the SKLabel appear upon the object reaching let's say location.x = 50 and I set the node to be removed when the object reaches location.x = 270, But the problem is it's only doing it once, after the object being added again, it seems that the scene is not removing the node even though I'm pointing my object to hit 270..
By the way, since I mentioned 2 problems, here's the code that executes the said operation for the SKlabel node which is only happening once, I want it to execute the statement one time, everytime I hit that location
if (newLocation.x==270.00 )) {
[self addingTheLabel];
}
if (newLocation.x == 50.00) {
SKAction *removingTheNode = [SKAction removeFromParent];
[self.label runAction:removingTheNode];
}
Nevermind, was able to resolve the issue now..
For those people who might encounter this,Creating a protocol on your scene will fix the issue:
#protocol gameSceneDelegate <NSObject>
-(void)testProtocol;
-(void)testProtocol2;
#end;
#interface MyScene : SKScene
#property (weak,nonatomic) id<gameSceneDelegate> delegate;
Implement it on your scene's view controller:
#interface ViewController : UIViewController<gameSceneDelegate>
in ViewController.m you need to set first your scene as your delegate:
MyScene *scenePointer = (MyScene*) scene;
[scenePointer setDelegate:self];
and finally, implement the methods on your ViewController:
-(void)testProtocol{
NSString *sampleString2 = [[NSString alloc]initWithFormat:#"This will show when testProtocol is selected"];
self.sampleLabel.text = sampleString2;
}
-(void)testProtocol2{
NSString *sampleString3 = [[NSString alloc]initWithFormat:#"This will show when test 2 protocol is selected"];
self.sampleLabel.text = sampleString2;
}
Make an if statement inside your ViewDidLoad that if your scenePoint is the delegate do the following:
if([scenePointer delegate]){
[self testProtocol];
[self testProtocol2];
}
Now, Going to your Scene, since what I want is for the label to change whenever the SpriteNode hits a specific location, what I did is:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
for(UITouch *touch in touches){
CGPoint location = [touch locationInNode:self];
CGPoint newLocation = CGPointMake(location.x, 450);
if (newLocation.x == 270) {
//This will trigger the method everytime the spritenode hit's this location
if([_delegate respondsToSelector:#selector(testProtocol2)]){
[_delegate performSelector:#selector(testProtocol2)];
}
}
if(newLocation.x <= 220){
newLocation.x = 220;
//This will trigger the method everytime the spritenode hit's this location
if([_delegate respondsToSelector:#selector(testProtocol)]){
[_delegate performSelector:#selector(testProtocol)];
}
}
self.spriteNode.position = newLocation;
}
}
It will now change the label every time your SpriteNode hits the location you want it to.
I hope this will help other's who are in the same concept as this.
import your appdelegate and then use
ViewController *vc = (ViewController*)[AppDelegate*)[[UIApplication sharedApplication] delegate] viewController];
to parse the object to your presented UIViewController

Add a SKSpriteNode to the main scene from another class which is not a view

So I have my main view where I can do things such as this:
player = [Hero spriteNodeWithImageNamed:#"player.png"];
player.position = CGPointMake(512, 384);
[self addChild:player];
This is done in the GameScene, which is my main scene, its loaded in the view controller like so:
-(void) presentFirstScene
{ // create and present first scene
GameScene* myScene = [GameScene sceneWithSize:self.view.bounds.size];
[self.kkView presentScene:myScene];
}
Now If I have a different class called A (an instance of this class can be found in GameScene) how can I add SKSpriteNodes in A to the main GameScene.
What is easiest/most efficient way of doing this?
Thanks,
Lewis
You can create method in your class A which returns SKSpriteNodes, for example:
// in .m
-(SKSpriteNode*)getHero {
SKSpriteNode *hero = [SKSpriteNode spriteWithImageNamed:#"player.png"];
// Add more customisation here
return hero;
}
And in your GameScene you can call it like that:
SKSpriteNode *hero = [a getHero];
hero.position = CGPointMake(512, 384);
[self addChild: hero];
Remember to add class declaration to .h file:
-(SKSpriteNode*)getHero;
Hope this what you are after.
If A is generating nodes without GameScene explicitly requesting them, then you could use the delegate pattern. Have A call a method on its delegate when it has a new node to add.

Using different buttons to get to the same ViewController

I am creating a game on XCode that has a menu with buttons for setup conditions (i.e., "Play to 30", "Play to 20", etc.). I want these buttons to create a segue to the same ViewController that has my game, with the only difference being how many points must be achieved until the game is over. It is far too inefficient to have multiples of the same ViewController for each setting. Is there a way around this?
In your game view controller create a custom initializer in as so:
// add in GameViewController.m
#implementation GameViewController
-(id)initWithLimit:(int)limit {
self = [super initWithNibName:#"NibName" bundle:nil];
if (self) {
_limit = limit;
}
return self;
}
// add in GameViewController.h
#interface GameViewController : UIViewController
#property (nonatomic) int limit;
#end
implement the menu's button actions as so:
-(IBAction)play30 {
GameViewController *game = [[GameViewController alloc] initWithLimit:30];
// Handle game view here.
}
this answer assumes you create a new instance of GameViewController when the user taps the button.
if you don't want to instantiate a new ViewControllerSubclass each time the button is taped then you can create a GameViewController property in you menu view controller and use lazy instantiation for the game view controller:
- (GameViewController *)game {
if (!_game) _game = ...;
return _game;
}
-(IBAction)play20 {
// Assuming game is a property.
self.game.limit = 20;
// Perform setup that expects the limit property to be set.
[self.game setup];
// Handle game view here.
}
Hope this helped :)

(Cocos2D) Detect which CCScene is showing?

Is it possible to detect which CCScene is currently showing on the scene? I have 2 CCScenes in my game and I want a certain action to occur if one is showing.
Also quick related question, if I wanted to check if a CCMenu is not showing currently would I do something like
if (!menu) {
//Menu is not showing currently
}
I am a bit of a noob when it comes to Cocos2D so please forgive me :)
Thanks!
You can use the CCDirector to tell which scene is running.
[[CCDirector sharedDirector] runningScene];
As for whether the menu is showing. You would have to check with the parent of the menu. If the parent where your CCLayer, then you could check by
// assume menu is set up to have tag kMenuTag
CCMenu * menu = [self getChildByTag:kMenuTag];
If the menu is child of some other node, you can get the parent through a similar method and get a reference to the menu.
If the menu == nil, it is not showing.
UPDATE
In cocos2d, you are discouraged from keeping references to all of your sprites, instead you should be giving each node a unique tag and use that to reference it. To achieve your first goal, you can give your scene a tag in your 2 respective CCLayer classes.
You can set up your unique tags in an enum in a file called Tags.h, then import that into any classes that need access to your tags
Example Tags.h
enum {
kScene1Tag = 0,
kScene2Tag = 1,
kMenuTag = 2};
Then in your layer class
+(id) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
scene.tag = kScene1Tag;
// 'layer' is an autorelease object.
HelloWorld *layer = [HelloWorld node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
Now when you grab the current scene you can check against the tags
int currentSceneTag = [[CCDirector sharedDirector] runningScene].tag;
if (currentSceneTag == kScene1Tag) {
} else if (currentSceneTag == kScene2Tag) {
}
The tag property is from CCNode which is the base class of CCLayer, CCScene, CCSprite, CCMenu...
This how to find out which scene is running
if ([CCDirector sharedDirector].runningScene == yourScene1) {
// your scene 1 is showing
} else {
// your scene 2 is showing
}
and to find out if a node is child of the running scene
BOOL isShowing = NO;
CCNode *node = yourMenu;
while (node != nil) {
if (node == [CCDirector sharedDirector].runningScene) {
isShowing = YES;
break;
} else {
node = node.parent;
}
}
if (isShowing) {
// your menu is in the display hierarchy
}

Resources