Most of the tutorials that I've read only explain Cocos2D examples in the HelloWorld class, but as I've started to build a simple game I need to know how to send an event to different classes, and for them to respond whenever it happens.
I have GameSceneLayer, a CCLayer class which loads in my different CCLayers of Sprites:
#implementation GameSceneLayer
+ (CCScene *)scene {
CCScene *scene = [CCScene node]; // Create a container scene instance
GameSceneLayer *gameLayer = [GameSceneLayer node]; // Create an instance of the current layer class
[scene addChild:gameLayer]; // Add new layer to container scene
return scene; // Return ready-made scene and layer in one
}
-(id)init
{
self = [super init];
if (self != nil)
{
Background *background = [Background node];
[self addChild:background z:0];
Player *player = [player node];
[self addChild:player z:1];
MainMenu *mainMenu = [MainMenu node];
[self addChild:mainMenu z:2];
}
return self;
}
#end
However, when my MainMenu CCLayer START sprite is touched I would like it to spawn in the PLAYER sprite from the Player CCLayer.
I'm guessing that I need a GlobalVariables.h with something like:
#define gameStart #"0"
So when the START sprite is pressed it changes gameStart to 1, and somewhere in the PLAYER sprite there is
if (gameStart == 1)
{
[self addChild:PLAYER];
}
However I'm not sure how to set up the code so that the PLAYER sprite is always looking for that information.
You have objects (instances of classes). You want one object to communicate with another. In Objective-C, that's called sending messages. In other languages it's simply calling a method.
You don't need global variables. Instead, the receiving object MainMenu needs to send a message to (call a method on) the Player object. How do you get two objects to know each other? You could put them in a stinky, loud, overcrowded discotheque and hope for the best.
Or you could simply let them talk to each other, but alas, they shouldn't. Since both are siblings of GameSceneLayer, they shouldn't hold references to each other themselves (danger of creating retain cycles unless you're using weak references).
But both have the same parent. So what does a good parent do when two siblings won't talk to each other? It relays the message!
In MainMenu, send a message to the parent GameSceneLayer:
[(GameSceneLayer*)self.parent gameWillStart];
The GameSceneLayer implements that selector, and forwards the message to any other object that should be informed about starting the game:
-(void) gameWillStart
{
[player gameWillStart];
[ingameUI gameWillStart];
// etc.
}
And Player also implements said selector:
-(void) gameWillStart
{
[self addChild:PLAYER];
}
Just one other thing: in GameSceneLayer, make player and all other objects ivars (instance variables) so that GameSceneLayer has these references readily available. The alternative would be to tag (less often used) objects and then use getChildByTag:
PS: adding PLAYER as child looks dubious to me. If you have already created whatever node PLAYER is, you should add it right away and if necessary, set it to be invisible and/or paused while the game hasn't started yet.
You can use a singleton GameState Manager, which keeps information on the game state.
Here is a little snippet:
+(GameManager*) sharedGameManager {
if (!_sharedGameManager) {
_sharedGameManager = [[self alloc] init];
}
return _sharedGameManager;
}
+(id) alloc {
NSAssert(_sharedGameManager == nil, #"Cannot create second instance of singleton Game Manager");
return [super alloc];
}
- (id)init
{
self = [super init];
if (self) {
}
return self;
}
in that game manager you can have an enum with the game states, and set a property for it.
//Header
typedef enum {
kGameStarted,
kGameEnded
}GameState
#interface GameManager : CCNode {
}
#property (nonatomic) GameSate gameState;
Then back in your implementation file, you synthesize that GameState property, and create your own setter
//Implementation
#synthesize gameState = _gameState;
//Create your own setter, so you can notify all listeners
-(void) setGameState:(GameState) newGameState {
if (newGameState != _gameState) {
_gameState = newGameState;
//Notify listeners via NSNotification
[[NSNotificationCenter defaultCenter] postNotificationName:#"gameState" object:nil];
}
}
in the classes where you want to get the messages you just have to subscribe to the "gameState" notification like so:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(onGameStateChange:)
name:#"gameState"
object:nil];
-(void) onGameStateChange{
if ([GameManager sharedManager].gameState == kGameStarted){
//start game
}
}
Related
I have a custom class "LinkWithNumber" with three sprites
and on the GameLayer in update I am trying to test
CGRectContainsRect for collisions but I am having trouble trying to access the sprite in the class file (I don't have much experience so most likely I am messing up :P)
I have tried the following:
LinkWithNumber.h
#interface LinkWithNumber : SKSpriteNode <SKPhysicsContactDelegate>
{
SKSpriteNode *collide;
}
LinkWithNumber.m
#synthesize collide;
//add collision object to the class
collide = [[SKSpriteNode alloc]initWithColor:[SKColor blueColor]
...blah blah as normal
[self addChild:collide];
collide.name = #"collide";
GameLayer.h
#class LinkWithNumber;
#interface GameScene : SKScene <SKPhysicsContactDelegate>
{
LinkWithNumber* twoSpritesWithParticlesBridge;
}
#property (nonatomic, strong)LinkWithNumber* twoSpritesWithParticlesBridge;
GameLayer.m
#synthesize twoSpritesWithParticlesBridge;
-(void)addStaticLinkedSpriteWithParticles
{
twoSpritesWithParticlesBridge =
[[LinkWithNumber alloc]initWithlinkSpriteA:#"RoseMine06"
spriteB:#"RoseMine06"
andPlistAnimation:#"need to create animations"
distbetween:300
hasParticles:YES
ParticlesNamed:#"Fire"];
[self addChild:self->twoSpritesWithParticlesBridge];
twoSpritesWithParticlesBridge.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: twoSpritesWithParticlesBridge.frame.size];
}
-(void)update:(CFTimeInterval)currentTime {
LinkWithNumber *currentSprite =
(LinkWithNumber*)[self childNodeWithName:#"collide"];
//NSLog(#"currentSprite Name #%#", currentSprite); //gets nil
if (CGRectContainsRect(myShip02.frame,currentSprite.frame)) {
NSLog(#"Hit barrier can pass");
}
}
Any help would be appreciated :)
How to locate your class object... The Solution, with thanks to 0x141E!
//name it on setup inside your customCLass
//eg yourObject.name = #"collide";
//Now in Gamelayer locate your object by recursive search
//it will look for any object named #"//collide"
//without the slashes it will only look on the game layer
//but since we need to dig a bit further we need them!
LinkWithNumber *currentSprite =
(LinkWithNumber*)[self childNodeWithName:#"//collide"];
NSLog(#"LinkWithNumber is %#",NSStringFromClass([currentSprite class]));
//do something with your object
if (currentSprite.position.y >0 ) {
NSLog(#"currentSprite Position %f",currentSprite.position.y);
}
extras
Sknode Class ref for other search functions
How to enumerate all nodes
I see two issues...
You are searching only the root node (in this case the scene) for the node named "collide", but that node is a child of LinkWithNumber node not the scene. To recursively search the entire node tree, use #"//collide"
You are casting the result of the search to a LinkWithNumber pointer, but collide is an SKSpriteNode not a LinkWithNumber.
Thanks for your help in advance!
I'm new to OOP, so this problem may be really basic, but I've searched for hours and still cannot find a good solution.
I'm using Cocos2d and Box2d in the project. In my GameLayer.mm file, I have a label to show current score. And there's is a custom sprite derived from CCSprite.
Now, I wanna increment current score from my custom sprite class when the property of sprite "isDead" is changed to true. As follows:
- (void) setIsDead
{
isDead = 1;
// then increment score
}
My question is how I can increment score from this subclass? I cannot access the instance or instance method of GameLayer.mm from this subclass. I tried to change the function of incrementing score from instance method to class method, and make score as a global instance, but I got duplicate error later.
Thanks for any advice!
Here is another approach I like: delegates.
First, go to your custom CCSprite header and create a new protocol. Basically, add this:
#protocol MyCustomDelegate
-(void)spriteGotKilled:(CCSprite*)sprite;
#end
Next, you need to modify your custom CCSprite to store its delegate. Your interface would look like this:
#interface MySprite {
id delegate;
}
#property (retain,nonatomic) id delegate;
Now go to GameLayer.h and make it implement the protocol:
#interface GameLayer : CCLayer <MyCustomDelegate>
Next implement the protocol method in your layer:
-(void)spriteGotKilled:(CCSprite*)sprite {
NSLog(#"%# got killed!",sprite);
}
And, finally, go to your setIsDead method:
-(void)setIsDead {
isDead = 1;
// Check if we have a delegate set:
if ([self delegate] != nil) {
// Check if our delegate responds to the method we want to call:
if ([[self delegate]respondsToSelector:#selector(spriteGotKilled:)]) {
// Call the method:
[[self delegate]spriteGotKilled:self];
}
}
}
When you create your sprite, you must set the layer as its delegate. Something like this:
MySprite *sprite = [[MySprite alloc]init];
[sprite setDelegate:self];
Now whenever your sprite dies, spriteGotKilled will be called in your layer.
You could use The Observer Design Pattern here in which an observer listens to an event and performs an action accordingly.
So in your GameLayer.mm add the observer in the init function:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveIsDeadNotification:)
name:#"SpriteIsDeadNotification"
object:nil];
and add a function:
- (void) receiveIsDeadNotification:(NSNotification *) notification
{
if ([[notification name] isEqualToString:#"SpriteIsDeadNotification"])
//update your label here
}
and in your custom sprite ,add the following line in the setIsDead method
-(void) setIsDead{
isDead =1;
[[NSNotificationCenter defaultCenter]
postNotificationName:#"SpriteIsDeadNotification"
object:self];
}
also remember to remove the observer in the dealloc of GameLayer.mm
[[NSNotificationCenter defaultCenter] removeObserver:self];
This pattern will reduce coupling in your code as instances of one class are not trying to access the methods of another.
I've implemented 3 classes.
-Scene.m & .h
Scene.m: (has HudLayer & BackgroundLayer properties on the header file)
-(id)init{
self = [super init];
if(self != nil){
//Level1Layer
_level1GameplayLayer = [Level1Layer node];
[self addChild:_level1GameplayLayer z:0];
//Hud Layer
_hudLayer = [HUDLayer node];
[self addChild:_hudLayer z:1];
}
return self;
}
-Which holds these 2 layers
BackgroundLayer.m & .h && HUDLayer.m & .h
Everytime I click the screen I get a Log notice like this "Touched Screen"(called on backgroundlayer.m) which is followed by a function which is implemented on HUDLayer.m & .h
I call it like this:
[_hud getAmmo:self.ammoLeft magsLeft:self.magsLeft];
_hud is stated on the Scene.m like this:(and its imported on backgroundlayer.m)
HUDLayer *hudLayer = [HUDLayer node];
[self addChild:hudLayer z:1];
background is z:0.
Also BackgroundLayer.m has the property under #interface:
(I realized this is nil because its not initialized, How do I initialize this???)
#property (strong) HUDLayer *hud;
Between the call of getAmmo: I make 3 CLOGS, one BEFORE "We're about to getAmmo:"
one INSIDE the function on HUDLayer.m that calls "Inside getAmmo:"
and one AFTER "We gotTheAmmo:"
BackgroundLayer.m:
CCLOG(#"We're about to getAmmo");
_hud getAmmo:self.ammoLeft magsLeft:self.magsLeft];
CCLOG(#"We got the ammo:%d, mags:%d",self.ammoLeft,self.magsLeft);
HUDLayer.m:
-(void)getAmmo:(int)ammo magsLeft:(int)magsLeft
{
CCLOG(#"We did this");
hudMagsLeft=magsLeft;
hudAmmoLeft = ammo;
CCLOG(#"HUD MAGS: %d, AMMO:%d", hudMagsLeft,hudAmmoLeft);
}
Im only getting the one before and the one after, there's no warnings on the way the function is being called but for some reason it isn't being called. There's no if statements or anything..what is it Im doing wrong???
So to make the question more simple, how do I access properties/functions from other classes?
Thank you for your time, have a good one.
On the scene.h I added this function:
-(id)initWithHUD:(HUDLayer *)hud;
Then on the scene.m I made this:
_backgroundLayer = [[[BackgroundLayer alloc] initWithHUD:_hudLayer] autorelease];
on Backgroundlayer.m I changed regular init with
-(id)initWithHUD:(HUDLayer *)hud{
:D
I'm trying to learn Cocos2D from Learning Cocos2D, A hands-on guide by Rod Strougo & Ray Wenderlich, but it uses Cocos 1, not 2 which is out now. I imagine the book is still relevant later on, but in the first chapter I'm running into an issue telling the director to run a scene, because it seems like that whole process is now different in Cocos2D 2.
I'd rather not have to buy a different book, so I was hoping that changing the way to run a scene is fairly simple.
This is what the book says to do:
find the -applicationDidFinishLaunching method and comment out:
[[CCDirector sharedDirector]runWithScene: [HelloWorld scene]];
and add:
[[CCDirector sharedDirector]runWithScene:[GameScene node]];
I can't find anything that looks like that in AppDelegate, instead it looks like this has something to do with the new way:
[director_ pushScene: [IntroLayer scene]];
My attempts to adapt what the tutorial says to the new way has so far failed, but maybe it is an easy fix.
Incase it is the GameScene which is outdated:
GameScene.h
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#import "BackgroundLayer.h"
#import "GameplayLayer.h"
#import "CCScene.h"
#interface GameScene : CCScene
{
}
#end
GameScene.m
#implementation GameScene
-(id)init
{
self = [super init];
if (self != nil)
{
BackgroundLayer *backgroundLayer = [BackgroundLayer node];
[self addChild:backgroundLayer z:0];
GameplayLayer *gameplayLayer = [GameplayLayer node];
[self addChild:gameplayLayer z:5];
}
return self;
}
#end
The issue you are having is with the class method +scene. That is used when creating the scene within a layer instead of initializing a scene and having that instance create its own child layers. You will understand the differences later in the book when you get more into the scene -> layer relationship.
Comment out [director_ pushScene: [IntroLayer scene]]; in -applicationDidFinishLaunching and replace it with the following:
[director_ pushScene:[GameScene node]];
That should work just fine for your needs. It will create an instance of GameScene (subclass of CCScene) with your two CCLayer subclass instances as children, those being the backgroundLayer and gameplayLayer you instantiate in the GameScene -init method.
If you are curious as to why calling [GameScene scene] was not working for you, that is because you never declared such a method in your interface. It is a little confusing, but basically you would instead create a Game Layer subclass of CCLayer and in your .h file, declare this class method:
+ (CCScene *)scene;
In your implementation .m file, you would define that method as such:
// Class initializer method
+ (CCScene *)scene {
CCScene *scene = [CCScene node]; // Create a container scene instance
GameLayer *gameLayer = [GameLayer node]; // Create an instance of the current layer class
[scene addChild:gameLayer]; // Add new layer to container scene
return scene; // Return ready-made scene and layer in one
}
Then when you call [GameLayer scene] that +scene method creates the scene for that layer and adds it as a child. It is quicker, but can be more troublesome with multiple layers in my experience.
Have you tried this:
[[CCDirector sharedDirector] pushScene:[IntroLayer scene]];
In my game, which is using cocos2d, there is going to be many different types of enemies, which all look different, and move all in different ways. Also, there is going to be a couple of different gamemodes, which both use the same enemies. As there will be different gamemodes, I decided to make each of my enemies have their own CCSprite class. In those there will be the way that the sprites move, the animation, etc. When one of these sprite is needed in my game, they will be spawned in to the scene. The only thing is, how do I do this? How do I call for one of the sprites to be create on the screen when they are using a class of their own?
If you want to tell me another way than having these sprites having their own classes, that is fine, but keep in mind that I will be having a couple of different gamemodes. If I do the code for the sprites in the CCLayer class of that gamemode, well I will have to write the code twice, which will take time.
Thanks.
You can just subclass CCSprite and override the default initializer initWithTexture:rect:
example taken from here
#implementation MySprite
-(id) initWithTexture:(CCTexture2D*)texture rect:(CGRect)rect
{
if( (self=[super initWithTexture:texture rect:rect]))
{
// initialize your ivars here
//ivar1 = xxx;
//ivar2 = yyy;
//ivar3 = zzz;
}
return self;
}
#end
// And to create an instance of MySprite you simply do:
MySprite *sprite = [MySprite spriteWithFile...];
// or any of the supported CCSprite methods.
you can have a super class say EnemySprite that looks like this
#interface EnemySprite : CCSprite
- (void)addToLayer:(CCLayer *)layer;
- (void)removeFromLayer:(CCLayer *)layer;
#end
than create a subclass for each type of enemy for example:
#inteface BigEnemySprite : EnemySprite
#end
#implementation BigEnemySprite
- (void)addToLayer:(CCLayer *)layer {
[layer addChild:self];
// animation code for your big enemy
}
- (void)removeFromLayer:(CCLayer *)layer {
[layer removeChild:self];
// animation code
}
#end
than you can use them like
EnemySprite *enemy = [BigEnemySprite spriteFromFile:file];
[enemy addToLayer:self];