Accessing Objects in other Layers (cocos2d) - cocos2d-iphone

I was playing around with a Joystick moving a Sprite around in one layer. Now, according to best practice, the Joystick and the Sprite have to be in different Layers of the same scene. I have managed to separate these, yet I am now completely stuck having absolutely no clue whatsoever how to pass joystick commands from one layer to another? What is the recommended way to do this?
Scene
Game Play Layer
Sprite
Game Control Layer
Joystick
When the joystick is manipulated I need to pass this info to the GamePlayLayer to control the sprite.

Well, I got a great Cocos2d book, written by Rod Strougo and Ray Wenderlich, by the name "Learning Cocos2d". In their book, they implement a game, which has a joystick implemented and all, using your initial setup. The GamePlayLayer contains both the joyStick and the hero sprite. (See book page 40).
I don't believe they would use bad practices in their book, given they are very talented!
...
With that being said, I have possible solutions, if you wish to implement them on separate layers:
GameScene
|
|__>GameLayer
|
|__>ControlLayer
That's your basic setup. But, intuitively, what is the purpose of the control layer? Control the game layer's content! So, I would suggest you hold a (weak) reference to the GameLayer within the ControlLayer. That way, using a simple:
#property (nonatomic, assign) CCSprite* hero;
you now have access to the hero from the ControlLayer!
Extra (if needed):
//GameScene init:
- (id)init {
....
gameLayer = [GameLayer node];
controlLayer = [ControlLayer node];
[controlLayer setGameLayerRef:gameLayer];
...
}
// Control layer:
#property (nonatomic, assign) GameLayer* gameLayerRef;
Even though I just suggested that way, I don't use it in my games :)
What I normally do is:
Make the GameScene class a "Semi-Singleton". (I learned this method from "Learn iPhone and iPad Game Dev" By Itterheim (aka gaming horror, Kobold2d publisher ... etc).
Then, inside the control layer, I would call the GameScene object:
[[GameScene sharedScene] doSomethingToTheGameLayer];
Yeah, the gameScene has simplistic methods that just relies what the control need to update in the game layer.
Edit:
Implementing the Semi-singleton pattern, as described by Itterheim in his book.
But, what is semi-singleton?
It has the singleton pattern's property: you can access the object instance from anywhere using a static call.
[GameScene sharedScene];
However, singleton objects are usually retained, after being created for the first time, till the end of the application's life. In the Semi-singleton pattern, this is not the case.
Once you create the instance, you cannot create another instance before destroying the old one, BUT once you are done with the instance, you destroy it (dealloc). Creating another one when necessary.
Recap:
Semi-Singleton: Create many object from it, but only one at any given time. Only recreate after destroying the old.
Implementation:
Of course, as you do with any singleton class, you first declare a static variable of the same type of the class:
//In GameScene.m
static GameScene* instance = nil;
#implementation
//Here we have the static method to access the instance:
+(GameScene*)sharedScene {
//you must have created one before trying to access it "Globally".
///Typically, created where you transition the GameScene using CCDirector's replaceScene.
NSAssert(instance != nil, #"GameScene not instantiated!!");
return instance;
}
-(id)init {
if((self = [super init])) {
//Make sure you don't have another instance of the class created
//NOTE: Possible race condition that I wouldn't worry about, since it should never happen.
NSAssert(instance == nil, #"Another instance is already created!!");
instance = self;
...
}
return self;
}
//Don't forget!! Dealloc!!
- (void)dealloc {
//the only instance we had is dealloc'd! We are back to zero. Can create a new one later! :D
instance = nil;
[super dealloc];
}
Edit2:
So, the timeline:
CCScene* scene = [GameScene node];
[[CCDirector sharedDirector] replaceScene:scene];
...
[[GameScene sharedScene] doSomething];
...
[[CCDirector sharedDirector] replaceScene:otherScene];
//After the new scene replaces GameScene, dealloc will be called, implicitly. Making instance = nil;
instance = nil;
[super dealloc];
...
//Loop again
CCScene* scene = [GameScene node];
[[CCDirector sharedDirector] replaceScene:scene];
...

Related

Making any given instance of a CCNode reactive to touch

So in my project I am using Cocos2D with CocosBuilder. I have assigned a few of my characters to be subclasses of CCNode with child CCSprites, etc.
I want these CCNodes to be reactive to touch - for example, if I touch any of them, they'd play a context sensitive animation. I only want to know how to make the node reactive to touch (or perhaps, having the layer reactive to touch which detects whether you've touched a sprite or not), the animation part is fine.
Any ideas? that would be lovely.
Sam
Turns out that this is fairly easy. In the header file of your class, you must define the class as implementing the protocol , like so:
#interface Foo : CCNode <CCTouchOneByOneDelegate>
{
}
and you must implement onEnter and onExit like this:
- (void)onEnter
{
[[[CCDirector sharedDirector] touchDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
[super onEnter];
}
- (void)onExit
{
[[[CCDirector sharedDirector] touchDispatcher] removeDelegate:self];
[super onExit];
}
and you must implement ccTouchBegan (if you're using the OneByOneDispatcher)

Cocos2D 2.0 - Can one CCSpriteBatchNode be used for multiple classes?

I don't know if this is possible, but I would like to create one big texture atlas and use it on all classes of the application.
Can one CCSpriteBatchNode be used for multiple classes?
Suppose I create this on the main class
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"atlasGeral.plist"];
self.batchNodeGeneral = [CCSpriteBatchNode batchNodeWithFile:#"atlasGeral.png"];
[self addChild:self.batchNodeGeneral];
and I have another class creating CCLayers on the main class, that is initialized, before using CCSpriteBatchNode, like this:
-(id) init
{
if( (self=[super init])) {
self.bg = [CCSprite spriteWithFile: #"cCircularBG.png"];
[self addChild:self.bg];
self.dr = [CCSprite spriteWithFile: #"cCircularDR.png"];
[self addChild:self.dr];
}
return self; // self is a CCLayer
}
can this be optimized using the self.batchNodeGeneral from the main class? My idea is to replace these two sprites and others with something like [CCSprite spriteWithSpriteFrameName:...
thanks
I'm not entirely sure I follow, but I'm pretty sure the answer is yes.
CCSpriteBatchNode doesn't have anything to do with classes, it has to do with assets. The important restriction on the use of batch nodes is that every sprite in the batch needs to reference the same texture atlas. So it is perfectly fine to have one batch node for your entire application and have every gameplay class add its own sprites to that batch. This can turn into a practical issue if your texture atlas becomes larger than the maximum texture size on your target hardware (see iOS device specs for details), but if you still want to have global batches and lots of assets it's not too hard to create a batch pool indexed by texture ID, create one batch node per atlas, and whenever you create a new sprite add it to the appropriate batch.
Honestly, I feel like the whole batch node thing is a terrible kludge on Cocos2D's part that could be made almost completely transparent to developers while still retaining its efficiency, but maybe this opinion is not entirely fair since I haven't dug around in the rendering code to understand their motivations. I guess it would mess with expectations of how depth sorting works, etc., but still I don't understand why batching objects for render is made the programmer's responsibility, it should be done by the engine.
Edit to add possible solution:
-(id) initWithMainClass:(MainClass*)mc
{
if( (self=[super init])) {
self.bg = [CCSprite spriteWithSpriteFrameName: #"cCircularBG.png"];
[mc.batchNodeGeneral addChild:self.bg];
self.dr = [CCSprite spriteWithSpriteFrameName: #"cCircularDR.png"];
[mc.batchNodeGeneral addChild:self.dr];
}
return self; // self is a CCLayer
}
`
So when you initialize one of the other classes, you pass the main class instance as a parameter so you can access its fields. Or make the main/manager class a singleton or find another architecture suitable to your needs.

Game object with composition and CCSpriteBatchNode

I'm currently developing a game with cocos2d and box2d on iPhone.
I read a lot of articles concerning game code organization, game objects structure etc.
I first started developing my game objects by inheriting from a base class itself inheriting from CCSprite.
I had a CCSpriteBatchNode to draw all the game items, which the player can interact with, in one draw call. This was easy because my Item class indirectly inherit from CCSprite so I could easily add my items to the CCSpriteBatchNode. And on top of that I could rely on cocos2d to retain my objects.
After I read the articles, I understood the need to refactor my code with a more composition oriented style rather than the inheritance style.
So I went with a GameObject base class inherited from NSObject and having properties such as one or more CCSprite, one b2Body etc.
The problem I'm facing now is that I can't directly add my GameObject to the CCSpriteBatchNode anymore. I first thought I could easily fix the problem by adding the sprite property of the GameObject to the CCSpriteBatchNode. It's ok but who retains the object owning the CCSprite ? How can I easily access the original object from the CCSprite (are userData/Object ok) ?
Should I create an array retaining my items ?
I'd like to know how you would use a CCSpriteBatchNode with such a game object structure ?
There is already a thread about that which is unanswered and I'd really like to hear about the subject. Not a straight answer but some elements to go further.
Thanks.
Personally I don't recommend using NSObject as the base class for cocos2d classes anymore. Simply because you lose some cocos2d convenience features such as scheduling and you can easily shoot yourself in the foot, memory-management wise.
What you want is a setup where the scene has one or more sprite batch nodes. You can consider them layers for your sprites. Your actual game objects (consider them to be MVC controllers) deriving from CCNode can be added anywhere, typically directly to the scene.
scene
+ batch node 1
+ sprite 1
+ sprite 2
+ sprite n
+ batch node 2
+ sprite 1
+ sprite 2
+ sprite n
+ game node 1
+ game node 2
+ game node 3
+ game node n
The thing to keep in mind is that each game node has one or more sprites as instance variables, but they're not childs of the node. So for example the game node 1 class might look like this:
game node 1 class
CCSprite* sprite1; // instance variable
CCSprite* sprite2; // instance variable
Now when you init game node 1 and its sprites, you add the sprites to the appropriate sprite batch node. Typically you will want to access your scene like a singleton to get access to its sprite batch properties.
sprite1 = [CCSprite spriteWithSpriteFrameName:#"doodle.png"];
[[scene sharedScene].batchNode1 addChild:sprite1];
sprite2 = [CCSprite spriteWithSpriteFrameName:#"splash.png"];
[[scene sharedScene].batchNode2 addChild:sprite2];
Note that you do not need to retain the sprites as long as they are children of the sprite batch node. The addChild method retains it for you. Actually, it just adds it into an array which does the retaining. Also, if you're still thinking about "retain", by all means start using ARC.
You do not need to figure out how to access the sprite in the sprite batch. It's available as an instance variable of your game node class, and if you wish, you can also make it publicly available to other classes as a property.
The game node class obviously runs all the object's game logic, including positioning and otherwise modifying the sprite's properties. The only thing you need to take care of in the game node class is that you remove the sprites from the batch node, otherwise it may be leaking. To do so, override the cleanup method:
-(void) cleanup
{
[sprite1 removeFromParentAndCleanup:YES];
sprite1 = nil;
[sprite2 removeFromParentAndCleanup:YES];
sprite2 = nil;
[super cleanup];
}
It's important to set the variables to nil because there may be code running after cleanup, including the code in the dealloc method. You could also remove the sprites in dealloc but depending on your setup, dealloc may not even get called because of the references to the sprite the game node class is still holding. Therefore it is generally safer to use the cleanup method if the sprites are not children (or grandchildren) of the game node class itself.

cocos2d remove scene / scene transitions

I started my app using the base code found here in the menus tutorial. In this manner, all of my 'screens' (there are only 5 of them) are implemented as extensions of the CCLayer class, and I have a shared + Scene Manager which works by adding my layer classes as children to a new scene and then using the director to run or replace the currently playing scene:
+(void) goMenu{
// \/---------- Issue right here, next line:
CCLayer *layer = [MenuLayer node];
[SceneManager go: layer];
}
+(void) go: (CCLayer *) layer{
CCDirector *director = [CCDirector sharedDirector];
CCScene *newScene = [SceneManager wrap:layer];
if ([director runningScene]) {
[director replaceScene: newScene];
}else {
[director runWithScene:newScene];
}
}
+(CCScene *) wrap: (CCLayer *) layer{
CCScene *newScene = [CCScene node];
[newScene addChild: layer];
return newScene;
}
The problem I am having is as follows. Let's say I have 2 layers -- 'MenuLayer' and 'GameLayer'. I start off with MenuLayer and later on use [SceneManager go:[GameLayer node]] to transition over to GameLayer. From GameLayer, if I goMenu the app terminates with an 'NSInternalInconsistencyException', reason: 'child already added. It can't be added again' where indicated. I assumed that this is happening because I am trying to add some child sprites in the layer's init code and I am re-adding them again.
My first attempt at debugging was to call [self removeAllChildrenWithCleanup:YES]; within the onExit code of all my layers. That didn't solve the issue. I added in some debugging logs and found out the last line that executes before the script blows up is the indicated one [myLayerClass node];. The init code inside the layer class does not get executed at all -- so it can't be one of the sprites or other children being added that is causing the issue. Again -- remember that everything works the first time I try to open any scene. It's moving back to an opened scene that is currently problematic.
So I tried a different approach -- the approach used in Cocos2D Hello world. I modified all my Layer Classes by adding a singleton +(id) scene method defined as follows:
+(id) scene
{
CCScene *scene = [CCScene node];
myLayerClass *layer = [myLayerClass node];
[scene addChild: layer];
return scene;
}
Added in a [super dealloc] in the dealloc, and encapsulated all my init code within:
-(id) init
{
if( (self=[super init] )) {
// All init code here....
}
}
Of course, all of the goLayerName singleton methods in the scene manager had their contents replaced to match this -- for example:
+(void) goMenu {
//CCLayer *layer = [MenuLayer node];
//[SceneManager go: layerMenu];
CCDirector *director = [CCDirector sharedDirector];
if ([director runningScene])
[director replaceScene:[MenuLayer scene]];
else
[director runWithScene:[MenuLayer scene]];
}
I figured this was the way Cocos2D's hello world worked, it must be good. Also, since each of my scenes would be created once and once alone (and thus every layer would be created once and once alone). No effect whatsoever! The first time I visit any scene it runs as expected. Whenever I try to navigate back to any existing scene, I get the error I mentioned above.
I tried poking around SO and found this, but I am unsure how to properly implement it; also I'm not entirely sure that would even solve the issue -- since the symptoms are different (he just gets a pink screen, whereas my app terminates).
Any assistance would be appreciated.
For anyone who was interested in this question, I was able to resolve the issue.
In summary, any child of a child you add, will not be released with releaseAllChildrenWithClenup. That function will only release the immediate children, so you are responsible to release any nested children yourself.
In my game, I had some characters and their eyes were nested in the head sprites (so that I could just move / rotate the heads). The eyes had their own animation loop but I wanted them to transform with the head. All that worked, but since I had a line of code in init which basically [headSprite addChild:eyeSprite];, I had to also add [headSprite removeChild:eyeSprite]; in the onExit code to dissociate it, followed by releaseAllChildrenWithClenup to remove all of the n=1 level children as well.
Once that was handled, everything worked as advertised.

Creating a custom cocos2d/box2d object

I'm trying to create a custom cocos2d and box2d object comprised of multiple bodies and need I little help with the structure.
I want the object to be composed of two bodies and one sprite. This is the header file I have created:
#interface NewBlock : CCNode {
CCSprite *sprite;
b2Body *body1;
b2Body *body2;
b2World *world;
}
So I'm inheriting from CCNode which I assume is the correct thing to do. Here is my implementation method:
-(id)initWithWorld:(b2World*)theWorld atLocation:(CGPoint)location {
if (self = [super init]) {
sprite = [CCSprite spriteWithFile:#"level_2.png"];
...box2d stuff...
[self addChild:sprite];
}
return self;
}
And in the scene I want the object, i would create it like:
NewBlock *block = [[NewBlock alloc] initWithWorld:world atLocation:ccp(100,100)];
[self addChild:block];
I know I could inherit from CCSprite but I might want to add more sprites to the object at some point so a more general example would be useful to me.
I have tried an example with the code above and get the following message in the console:
sharedlibrary apply-load-rules all
Current language: auto; currently c++
Is my structure for creating a custom cococs2d/box2d object correct? Am I missing anything?
The design is good. Personally I favor subclassing CCNode too, for the exact same reason: you never know when your CCSprite needs to contain multiple CCSprite. Thus the CCNode class becomes the controller, and CCSprite and other classes are treated as the View. The b2Body would be the model in this case.
Your error is unrelated to how you designed the class. Does the code still work, or does it crash? Maybe it's one of the many messages that can be ignored.
The usual caveats apply when adding Box2D code. All your classes should use the .mm extension, even those which may not be using Box2D code directly.