something wrong with class initing? - iphone

I have a class "Hero", subclassed from CCSpriteBatchNode (cocos2d):
#interface Hero : CCSpriteBatchNode {...}
I'm initing it like this:
Hero *hero;
...
hero = [[Hero alloc] initWithWorld:world];
Here is init method of Hero:
-(id) initWithWorld:(b2World*)world
{
if (self = [super init])
{
...
}
return self;
}
Till this moment all works fine: I can add hero as a child to my layer, and it will be displayed correctly. But then, I'm trying to get some data from hero. I have this method in Hero class:
-(b2Body*) getBody; //in Hero.h
-(b2Body*) getBody //in Hero.mm
{
return selfBody;
}
and my application crashes just after calling [hero getBody]; with error:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CCSpriteBatchNode getBody]:
unrecognized selector sent to instance 0x42bde0'
so, he tries to find method getBody in CCSpriteBatchNode class. offcorse, he cant find it there. But WHY he tries to find it there? Why not in Hero class?
P.S. all my classes are *.mm. I need it to support box2d.
EDIT
The ony time I use hero between initing and calling getBody is adding it to CCLayer:
[self addChild:hero z:0 tag:kTagParentNode]; //self - CCLayer subclass
getBody is called from tick method:
[self schedule:#selector(tick:)];
...
-(void)tick:(float)dt
{
CGPoint scaledVelocity = ccpMult(leftJoystick.velocity, 480.0f);
if (scaledVelocity.y > CGPointZero.y)
{
[objectControls jump:[hero getBody]];
}
if (scaledVelocity.x > CGPointZero.x)
{
}
}

While it's hard to tell what's causing the issue without seeing much code, I advise you to rethink your architecture.
A CCSpriteBatchNode is an object that's used by the cocos2d engine to render batched sprites. Is your Hero really such an object? I don't think so. The Hero should be a class on it's own and have a has one relation to a CCSpriteBatchNode and a b2Body.
Why? There are several reasons for this. First and foremost it's going to decouple your Hero implementation from the interface imposed by cocos2d. If the creators of cocos2d decide to change some implementation details, your class might no longer work if it inherits from a CCNode. Also think about encapsulation: Does a visual component really have to know about player logic?
If you split up your architecture you're going to make your code better readable, easier to maintain and also easier to port to another platform if that's ever going to happen. For a similar post, see this question on gamedev.SE.

Related

How can I check if a SKSpriteNode that has been touched also of some other type that inherits from SKSpriteNode?

I need to check which of my custom - class SKSpriteNode inherited objects is touched. I have some elements in my game like:
#interface Hero : SKSpriteNode
and a non playing character element:
#interface StaticLevelElement : SKSpriteNode
and I need to check which one has been touched and to invoke the proper method accordingly (either the method form the Hero or from the StaticLevelElement class).
Which method could I use to differentiate between the specific types of classes?
You can do something like this :
if ([node isKindOfClass:[StaticLevelElement class]]){
NSLog(#"Touched node is StaticLevelElement");
}else if([node isKindOfClass:[Hero class]]){
NSLog(#"Touched node is Hero");
}
From the docs about isKindOfClass: method:
Returns a Boolean value that indicates whether the receiver is an
instance of given class or an instance of any class that inherits from
that class.
Look at this part:
...or an instance of any class that inherits from that class
You should be careful, because if you subclass StaticLevelElement like this:
#interface SubclassOfStaticLevelElement : StaticLevelElement
#end
And inside touchesBegan do this:
if ([node isKindOfClass:[StaticLevelElement class]]){
NSLog(#"Touched node is StaticLevelElement");
}
The method will return true if you touch both, instances of StaticLevelElement and instances of SubclassOfStaticLevelElement. This is because SubclassOfStaticLevelElement inherits from StaticLevelElement.
So I guess, this is not the optimal method for you. In SpriteKit, in situations like yours, people often use the method below (by setting the SKNode's name property).
Inside your Hero's initializer (or you can name your nodes upon creation inside your scene):
self.name = #"hero";
Then in your touchesBegan:
if ([node.name isEqualToString:#"hero"]){
NSLog(#"Touched node is hero");
}
You could just override the touchesBegan method inside of your SKSpriteNode sub classes, and execute their functions accordingly. This leaves the least amount of change for error, because this method will only be called when the system has determined that the particular node has been touched.
If you need to call some kind of method from the main scene, you could then use NSNotificationCenter to send a message to the scene (This will also work if you need to communicate with many different objects,) you could call the scene methods by having the hero have a property that stores the instance of the scene, or you could have a property called isTouched, which you set to true (Yes) when you are in touchesBegan and false (No) when you are in touchesEnded and touchesCancelled, along with some kind of identification property to eliminate the need to do extensive searching, and pretty much do a call like
if(node.isTouched && node.identifier == "StaticLevelElement")
{
//do stuff pertaining to StaticLevelElement
}

"'CCLayer' may not respond to ... "

Playing with cocos2d and this seems to come up in some instances of message passing. I have to admit, I'm not well versed in Objective-C yet as I'm reading through the book as I write some code.
For people familiar with cocos2d, you have a CCLayer class. In my case, I have a GameLayer which has some children layers, like so:
#interface GameLayer : CCLayer {
CCSprite *_background;
CCPanZoomController *_controller;
CCLayer *_truckLayer;
}
I then have a special initWithLayers method to add my child layers within this layer:
-(id)initWithLayers:(TruckLayer *)truck
{
if( (self=[super init])) {
self.truckLayer = truck;
.....
}
Now if I ever send a message to self.truckLayer I get the following warning in Xcode:
'CCLayer' may not respond to 'getNextAvailableSpot'
An example might be doing something like so:
CGPoint nextSpot = [self.truckLayer getNextAvailbleSpot];
All this code compiles and runs successfully, so I'm curious as to what I am missing here? I don't want my code literred with these if I'm doing something wrong here.
It's because your declare *_truckLayer as CCLayer, but on your init you assign (TruckLayer *)truck to your CCLayer* _truckLayer. So CCLayer doesn't know about getNextAvailableSpot method, so the compiler gives you warning.
The code runs fine because _truckLayer is a pointing to correct TruckLayer, which knows about getNextAvailableSpot.
Simply change your declaration and the warning will be gone.
#interface GameLayer : CCLayer
{
CCSprite *_background;
CCPanZoomController *_controller;
CCLayer *_truckLayer;
}
to
#class TruckLayer;
#interface GameLayer : CCLayer
{
CCSprite *_background;
CCPanZoomController *_controller;
TruckLayer *_truckLayer;
}
Just as the warning says: the compiler can't guarantee that CCLayer has a method to handle the getNextAvailableSpot message. Unless you've somehow added in such a method dynamically, or have some other (very good) reason to believe that this method is present, this is probably a problem.

CCSprite is not displaying on the iPhone

I am trying to make a sprite display on the screen in Cocos2d. But, I don't want to use a CCSprite directly. I have a class Unit which will have some additional properties that I will need later on in my game. The class declaration of Unit is as follows:
#interface Unit : CCSprite {
CCSprite *sprite;
}
-(void)init;
#property(nonatomic, retain) NSNumber *type;
#property(nonatomic, retain) CCSprite *sprite;
#end
And my init method for it looks like this:
-(void)init {
self.sprite = [CCSprite spriteWithFile:#"BasicUnit.png"];
self.sprite.position = ccp(50, 100);
}
Now what I need to do is apply it to the screen. So, I have another class called Playscene which is the scene where I want to display sprites and things. Here is what the init method (the method that should draw the sprites) looks like in Playscene:
-(id) init {
if( (self=[super init] )) {
self.isTouchEnabled = YES;
[army init];
[self addChild:army.sprite];
}
return self;
}
But, when I run this I get a ton of error data including: "terminate called after throwing an instance of 'NSException'" and probably of more importance: "Assertion failure in -[PlayScene addChild:]". I don't know how I can solve this. Any help would be appreciated.
Based on your snippets it is very difficult to know what goes wrong. That said I assume that army in your last snipped is of type Unit. But because that is inside the init() method it could be that it is nil because it is not created here like in army = [[Unit] alloc] initXXX];.
That said I am not sure what you want to accomplish with subclassing CCSprite in your Unit class because you are referencing CCSpirit and so there is not need to subclass it.
Finally your Assertion is probably because your army.spirit is either NIL or it is already added and the assertion inside Coco2d throws the exception (I am assuming that the last snippet is from a subclass of CCNode).
My suggestions:
Don't extend CCSpirit in Unit (not needed as far as I can see)
Don't have a method - (void) init but overwrite - (id) init
Make sure army is properly instantiated using [[Unit alloc] init] (see point above)
Using alloc you need to make sure that if it is assigned to a retaining property that you also release it to offset the alloc.
Hope that helps.

Accessing a class' instance variable (NSMutable Array) from another class

new to Obj C and programming in general - learned a lot from this site and really appreciate everyone's contributions.
My scenario is as follows (programming an iPhone game which explains the funny names)
In my main gameLoop (which is in my view controller) if a certain condition is met I create an enemy - the cherry bomb
if (bounceCounterGlobal % 2 == 0 && bounceCounterGlobal > 1 && cherryBombSwitch == 0){
[self addCherryBomb];
}
The addCherryBomb method is as follows:
-(void) addCherryBomb{
CherryBomb *myCherryBomb = [[CherryBomb alloc] init];
[cherryBombArray insertObject:myCherryBomb atIndex:0];
[myCherryBomb release];
[[cherryBombArray objectAtIndex:0] initializeCherryBomb];
[self.view addSubview:[[cherryBombArray objectAtIndex:0] cherryBombView]];
cherryBombSwitch = 1;
}
The CherryBomb header file is short:
#import <Foundation/Foundation.h>
#import "SimpleGameViewController.h"
#interface CherryBomb : NSObject {
UIImageView *cherryBombView;
NSTimer *cherryBombDetonateTimer;
NSTimer *cherryBombMoveTimer;
}
#property (nonatomic, retain) UIView *cherryBombView;
-(void) initializeCherryBomb;
-(void) detonateCherryBomb;
-(void) moveCherryBomb;
#end
What I would like to do is when the cherry bomb detonates (which is determined within the cherryBomb object), I would like the object to remove itself from the cherryBombArray which is an ivar of the view controller.
I tried calling a view controller class method to do this - but I am unable to access ivars of the view controller (because it is a class method). I do not know how to communicate back to the view controller class to tell it to remove the exploded object.
#implementation CherryBomb
...
-(void) detonateCherryBomb{
NSLog(#"KABOOM!");
cherryBombDetonateTimer = nil;
[cherryBombMoveTimer invalidate];
[cherryBombView removeFromSuperview];
//I would like to remove this object from the view controller's cherryBombArray
}
#end
Your help is greatly appreciated. Thank you in advance!
I recommend you to create some "Environment" object that will handle all the gaming logic.
The cherryBomb shouldn't deal with its explosion. The cherryBomb can store many information (size of the explosion, type of explosion, and so on), but the effects of the cherryBomb on the others "things" (characters, bombs, whatever) shouldn't be calculated by the cherryBomb itself.
I'm not very used to game programming, but this aspect of architecture/design is common: each object/class has its responsibilities.
The cherryBomb represents a bomb, no more (and not the "graphic" aspect either).
The Environnement represents a "world" at current instant, and modelizes actions/interactions between the elements of the world.
There is a lot to say about the best way to design a game...
Anyway, to give your question an answer, you can still use "events". The bomb can send a message to your controller telling him: "I've exploded, remove me".
In the bomb:
[[NSNotificationCenter defaultCenter] postNotificationName:#"kaBOOM"
object:self];
In the controller:
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(methodToCallWhenKaBOOM)
name:#"kaBOOM"
object:nil];
And
- (void)methodToCallWhenKaBOOM:(NSNotification *)note
{
// do stuffs
}
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/Reference/Reference.html
There are a number of ways to do this, and you may want to think about the exact division of labour here, in terms of controllers and models. Things can get pretty spaghettified if you have too much calling back and forth.
However, without getting into all of that, the basic thing you need to do to allow one object to access another object is to give the first a reference to the second.
In this case, you're actually creating the CherryBomb in the view controller, so it's easy to just pass it a reference at that point. Give your CherryBomb class another ivar like this:
SimpleGameViewController* cherryBombViewController;
Modify CherryBomb so that either the init method or your initializeCherryBomb one (these probably should just be a single method, btw) takes such a pointer and assigns it to the ivar:
- (void) initializeCherryBomb:(SimpleGameViewController*)vc
{
// ... whatever other stuff you do in here, plus something like:
cherryBombViewController = vc;
}
When you call this, pass it self as the vc parameter. Then later on, when your bomb detonates it can invoke some method you add on the controller to remove itself:
[cherryBombViewController handleDetonationOfCherryBomb:self];
Note that you absolutely should not access the controller's array directly -- that's an implementation detail your bomb should have no knowledge of. You can get away with being a little sloppy in your control structures in simple cases, but never screw with your encapsulation.
Read up on the MVC design pattern. If you find ivars which you need to share among views, they should probably go in a higher level Model object (the M of MVC), instead of having some view's peaking into other view's ivars. A pointer to this Model object can then be passed down to all the view objects that need to access it.
I think this is a good application of key value observing. You need a property of the cherry bomb which represents its state e.g.
#property (assign) BOOL isExploded;
Any object that is interested in whether the cherry bomb has exploded registers itself for KVO on the isExploded property. For example the view controller might do:
[cherryBomb addObserver: self
forKeyPath: #"isExploded"
options: ....
context: ....];
and in -observeValueForKeyPath:ofObject:change:context: for the view controller remove the cherry bomb from the array.
Your detonate method does the following as well as everything else it is currently doing:
[self setExploded: YES];

NSMutableArray and memory dealloc

Im making an app for the iphone using cocos2d and i am trying to figure out the best approach for removing items from a NSmutableArray and from the layer at the same time.
What i mean by this is that the objects within the array inherit from ccNode and contain a ccsprite which i have added as a child to the cclayer. The below code is in a cclayer that has the nsmutablearray called bonusicons.
-(void) AddNewBonusIcon: (int) colour :(int) pos{
BonusIcon *newbonus;
CGSize winSize = [[CCDirector sharedDirector] winSize];
int maxX = winSize.width;
int maxY = winSize.height;
int posX, posY;
newbonus = [[BonusIcon alloc] init];
[newbonus setBonusColour: colour];
int bonusOffset = 0;
posX = anchorX;
posY = anchorY;
bonusOffset = [bonusIcons count]*([newbonus.bonus_sprite boundingBox].size.width/2 + 12);
newbonus.bonus_sprite.position = ccp(posX+bonusOffset,posY);
[newbonus.bonus_sprite setTag:pos];
[self addChild:newbonus.bonus_sprite];
[bonusIcons addObject:newbonus ];
[newbonus release];
}
This appears to do what i want for adding the objects sprite to screen and adding the objects to the nsmutablearray. Now of course this is probably not the correct way to do it so shout at me if not!
next i try to delete the objects from the array and from the screen. I can delete them from the array with no problems i just do the following
for (int i = INITIAL_BONUSES-1; i>=0; i--) {
[bonusIcons removeObjectAtIndex:i];
}
this of course leaves the sprites on screen. so how do i approach what i am trying to do so that i can remove both the sprites from screen and the objects from array that the sprite is associated with. I can remove the sprites from the screen by using the tags and typing
[self removeChildByTag:i cleanup:YES]; but then i get errors when trying to remove items from the array . i assume because i have deleted a part of the object already and the dealloc of the ccnode can no longer find the sprite to release?
so any pointers/tips etc of how i should be doing this would be much appreciated. I have read a bunch of stuff on memory management which i believe is my current issue but i just dont seem to be getting it right.
thanks all
edit: ok since posting this i have removed the sprite dealloc from the ccnode itself and added it to the cclayer above it. This has stopped the crashing so i guess i was right with the problem i was having. I of course do not think the way i solved it is the most ideal way but it will do until i find a better way.
You don't have it in the code you posted, but your question seems to strongly imply that you are calling dealloc. The only place you should ever call dealloc is [super dealloc] at the end of a class's dealloc method. Calling it on anything but super or in any other place is wrong and will lead to errors about prematurely deallocated objects (because, well, that's what it does).
If this is what you're doing, I strongly suggest you read Apple's memory management guide. It lays out how memory management works in Cocoa very simply yet thoroughly.