cocos2d sub-classing - objective-c

I know that cocos2d has scheduling callbacks to do nice things but when you need to use one CCAction (like CCMoveTo one) in order to move a sprite from position a to b, you do not have the ability to make small position arrangements to the sprite position for as long as the action is in effect.
The only possible way I found is by making a sub-class of CCMoveTo in order to check for obstacles and therefore provide some kind of movement to the left or right to a sprite that was moving from top to the bottom of the iPhone screen. The problem is that the sub-class does not have access to the parent class' instance variables (like the startPosition_ one) because they have not been declared as properties.
So I used the following snippet to overcome this situation but I wonder if I am doing something wrong...
- (void)myUpdate:(ccTime)time {
if(delegate && method_) {
NSNumber *num = (NSNumber *)[delegate performSelector:method_ withObject:ownTarget];
if(num) {
double xpos = [num doubleValue];
[num release];
CCMoveTo *parent = [super retain];
parent->startPosition_.x += xpos;
[parent release];
}
[super update:time];
}
Is it correct to retain/release the super-class? The "[super update:time];" at the bottom of the code will make the final positioning.

CCMoveTo *parent = [super retain];
Ouch! This statement makes absolutely no sense. It is the same as writing:
[self retain];
As for accessing the super class' instance variables: unless they're declared #private you can access them. I just checked: they're not #private. You should be able to write in your subclass:
startPosition_.x += xpos;
If that doesn't work make sure your class is really a subclass of CCMoveTo, and not some other class.
Finally, I'd like to say that actions are very limited when it comes to implementing gameplay. You're probably much better off to simply animate your game objects by modifying their position property every frame, based on a velocity vector. You have much more freedom over the position and position updates, and none of the side effects of actions such as a one-frame delay every time you run a new action.
-(void) update:(ccTime)delta
{
// modify velocity based on whatever you need, ie gravity, or just heading in one direction
// then update the node's position by adding the current velocity to move it:
self.position = CGPointMake(self.position.x + velocity.x, self.position.y + velocity.y);
}

Related

Accessing a property of a node

I currently have a SpriteKit game with the objective of shooting down enemies. I've implemented collision detection for it, and it works just fine. But I need to implement health for enemies. Enemies are constantly generated and keep moving, so you never know what node that should bebeSo I tried to declare my custom class node in didBeginContact method, then assigning it to bodyA, then changing it's health value, but this seems useless since I just create a new node (same shows the NSLog). I tried typecasting the declaration, but still with no luck. Did some research on this topic, but didn't find anything that suits me. Currently I can't provide source code for what I did, but I hope what I have requested is possible to explain. Please push me in the right direction.
Every SKSpriteNode has a userData NSMutableDictionary which can be used to store data (objects).
You first have initialize the dictionary like this:
myNode.userData = [NSMutableDictionary dictionary];
Then you can assign data to it like this:
float myHealth = 100.0;
NSString *myX = [NSString stringWithFormat:#"%f",myHealth];
[myNode.userData setValue:myX forKey:#"health"];
To read data you do this:
float myHealth = [[myNode.userData objectForKey:#"health"] floatValue];
I used float in my example but you can use whatever you want. Just remember that you cannot store primitives like float, int, long, etc... directly. Those need to be converted to NSNumber, NSString and so on.
That being said, Stephen J is right with his suggestion. You should subclass SKSpriteNode for your enemies and have health as a class property. Subclassing is much easier to work with in the long run and gives you greater flexibility compared to using the userData.
To illustrate some Object oriented concepts Stephen J and sangony are referring to, I have added some code for you.
Subclassing SKNode will define a new object class which inherits all functionality from SKNode. The main advantage here is that you can implement custom properties (such as health) and custom logic (such as lowering that health).
#interface EnemyNode : SKSpriteNode
- (void)getHit;
- (BOOL)isDead;
#property (nonatomic) CGFloat health;
#end
#implementation EnemyNode
- (instancetype)initWithColor:(UIColor *)color size:(CGSize)size {
self = [super initWithColor:color size:size];
if (self) {
self.health = 100.f;
}
}
- (void)getHit {
self.health -= 25.f;
}
- (BOOL)isDead {
return self.health <= 0;
}
#end
In your scene, you would use it as such:
EnemyNode *newEnemy = [[EnemyNode alloc] initWithColor:[UIColor blueColor] size:CGSizeMake(50,50)];
[self addChild:newEnemy];
...
[newEnemy getHit];
if ([newEnemy isDead]) {
[newEnemy removeFromParent];
}
For further illustration, you could take a look at my answer to a similar question.

Using stuff from other files in Cocos2d?

I am making a game (obviously) and I noticed that my HelloWorldLayer.m file is getting EXTREMELY cramped. I KNOW that there is a way to run methods from other .m files, I just don't know how. For example, I want to have a Character.h and Character.m file. Can I make it so in the HelloWorldLayer init layer, it just uses everything from the Character files instead of having to declare everything in the HelloWorldLayer? I hope my question makes sense, and any help is appreciated. Thanks!
Here is Character.m:
#implementation Character
#synthesize health,velocity;
-(void)dealloc {
[super dealloc];
}
-(id)initWithTexture:(CCTexture2D *)texture rect:(CGRect)rect
{
if((self = [super initWithTexture:texture rect:rect]))
{
[self scheduleUpdate];
}
return self;
}
-(void)update:(ccTime)dt {
[self setPosition:ccp(self.position.x,self.position.y)];
self = [CCSprite spriteWithFile:#"nukeboyGreen.gif"];
}
#end
And here's HelloWorldLayer.m (I skimped it down and took out the parts that aren't necessary):
self = [super init];
if( (self=[super initWithColor:ccc4(255,255,255,255)] )) {
CGSize winSize = [[CCDirector sharedDirector] winSize];
character = [Character spriteWithFile:#"nukeboyGreeen.gif"];
character.position = ccp(winSize.width/2,winSize.height/2);
character.scale = 0.15;
[self addChild:character];
Note that I have a Character declared in HelloWorldLayer.h
This is where object oriented programming comes to the rescue. OOP encourages you to encapsulate variables and functions that is pertinent to an object in that object itself. In your case, you should put the methods that are specific to Character in the Character class, and only get your HelloWorld to trigger those methods.
Examples:
#interface Character : CCSprite {
...
}
- (void)didCollideWith:(Object *)object;
- (void)moveTo:(CGPoint)nextPoint;
- (void)shootArrow:(ckDirection)direction;
- (BOOL)isAlive;
- (int)numberOfLivesRemaining;
...
#end
Then in HelloWorldLayer:
[character moveTo:ccp(100, 200)];
[character shootArrow:kDirectionUp];
if (![character isAlive]) {
[self showGameOver];
}
Not only that your HelloWorldLayer is less cluttered, you can easily understand what your code does by simply looking at the reasonably named methods.
EDIT:
To answer your question as in the comment about how to designate the sprite image in Character class:
#implementation Character
- (id)init {
self = [super initWithFile:#"sprite_character.png"];
if (self) {
// further customization
}
return self;
}
#end
EDIT (after code was added to the question):
First let me point out a few mistake (sorry for the lack of softer word):
You rarely need your sprite to call the [self scheduleUpdate] or [self schedule:SEL]. Normally people implement the update (or tick) method at the CCLayer or CCScene level, where the purpose is to check all the actors (sprites, menus, nested layers etc) for collision/interaction and update their attributes. If you just want to animate movement of a sprite to a specific position, just call runAction method from CCLayer (in the init, update, ccTouchBegan or somewhere). You can read cocos2d-iphone tutorial on Actions by clicking here. So, move the update method and the scheduleUpdate call into your HelloWorldLayer, and then you no longer need to override initWithTexture.
I'm seeing you instantiating a CCSprite in the update method. My above point on the inappropriateness of update method in CCSprite notwithstanding, there is something more important you need to understand when you implement a method: that is you need to decide how and how often your method is going to be used/called. Since the update method is going to be called once per frame (that is 60 times per second), it is simply wrong to unconditionally instantiate an object in that method. You are making the iPhone to allocate (and deallocate) the object with no apparent reason, wasting the processor time/power the device has. You might want to ask where should you instantiate the CCSprite. The answer is in the init method because that method is only called once per object instance. Again, all you need to know is whether a method is going to be called once or multiple times, and decide whether a piece of code should be in there or somewhere else.
In your code for HelloWorldLayer, did you realize that you are calling the super init* methods twice. You don't need to call [super init] since [super initWithColor:ccc4( ... )] is going to call specific init method internally. Although it is not entirely wrong to do so, you are going to break the 'assumption' that init is going to be called once per instance, so you might end up breaking some object integrity unintentionally (and believe me it's going to be hard to debug later)
And finally, care to enlighten me what's the real purpose of the line [self setPosition:ccp(self.position.x,self.position.y)];. You basically set the position of the self object to its current position, so that's like saying "hey you, move your position to your current position" and he'll be like "huh?" :P

Collision detection using mulitple subviews within a moving view

I am working on a game in OBJ C that has a ball view and a stage view. The stage view has 4 subviews. All views are UIImageViews. I have a method for collision detection that is working. I would like to expand it to more than 4 subviews without simply creating more lines of code. Looking at the code below, is there a way to simplify this into loops instead. Thanks!
// convert each square to be relevant to ball's superview in order to collision detect
CGRect square_01Frame = [ball.superview convertRect:square_01.frame fromView:square_01.superview];
CGRect square_02Frame = [ball.superview convertRect:square_02.frame fromView:square_02.superview];
CGRect square_03Frame = [ball.superview convertRect:square_03.frame fromView:square_03.superview];
CGRect square_04Frame = [ball.superview convertRect:square_04.frame fromView:square_04.superview];
// convert CGRects to NSStrings for storage in square_frames array
NSString *square_01FrameString = NSStringFromCGRect(square_01Frame);
NSString *square_02FrameString = NSStringFromCGRect(square_02Frame);
NSString *square_03FrameString = NSStringFromCGRect(square_03Frame);
NSString *square_04FrameString = NSStringFromCGRect(square_04Frame);
// load array of NSStrings
[square_frames replaceObjectAtIndex:0 withObject:square_01FrameString];
[square_frames replaceObjectAtIndex:1 withObject:square_02FrameString];
[square_frames replaceObjectAtIndex:2 withObject:square_03FrameString];
[square_frames replaceObjectAtIndex:3 withObject:square_04FrameString];
// create a for loop
for (int i=0; i<4; i++) { // 4 squares
// create test frame
CGRect test_frame = CGRectFromString([square_frames objectAtIndex:i]);
if (CGRectIntersectsRect(test_frame,ball.frame)) { // collision detection
// do something
}
}
Well, I would do a number of things.
First, I would create a ball "model", just an NSObject subclass to represent the Ball. Probably, that would have a property "location" or something, which is the CGRect.
Then, your current view could have an array of ball objects on the screen, and just loop through them.
Overall, though, I don't think using UIView's rects is the best way to manage collision detection. I think you'd be better off defining that in some other way, and then simply updating the UI accordingly.
Generally, it's not a good idea to rely on your UI implementation for game design. It makes it hard to change (as you note in your question).

Is there somethone wrong with my Object Scope?

This is a program I'm writing (myself as opposed to copying someone else's and thus not learning) as part of the ObjectiveC and Cocoa learning curve. I want to draw simple shapes on a NSView (limiting it to ovals and rectangles for now). The idea is that I record each NSBezierPath to an NSMutableArray so I can also investigate/implement saving/loading, undo/redo. I have a canvas, can draw on it as well as 2 buttons that I use to select the tool. To handle the path I created another object that can hold a NSBezierPath, color values and size value for each object drawn. This is what I want to store in the array. I use mouseDown/Dragged/Up to get coordinates for the drawing path. However, this is where things go wonky. I can instantiate the object that is supposed to hold the path/color/etc. info but, when I try to change an instance variable, the app crashes with no useful message in the debugger. I'll try to keep my code snippets short but tell me if I need to include more. The code has also degenerated a little from me trying so many things to make it work.
Project: Cocoa document based app
I have the following .m/.h files
MyDocument:NSDocument - generated by XCode
DrawnObject:NSObject - deals with the drawn object i.e. path, color, type (oval/rect) and size
Canvas:NSView - well, shows the drawing, deals with the mouse and buttons
Canvas is also responsible for maintaining a NSMutableArray of DrawnObject objects.
DrawnObject.h looks like this:
#import <Foundation/Foundation.h>
//The drawn object must know what tool it was created with etc as this needs to be used for generating the drawing
#interface DrawnObject : NSObject {
NSBezierPath * aPath;
NSNumber * toolType;//0 for oval, 1 for rectangular etc....
float toolSize;
struct myCol{
float rd;
float grn;
float blu;
float alp;
} toolColor;
}
-(void)setAPath:(NSBezierPath *) path;
-(NSBezierPath *)aPath;
#property (readwrite,assign) NSNumber * toolType;
-(float)toolSize;
-(void)setToolSize:(float) size;
-(struct myCol *)toolColor;
-(void)setCurrentColor:(float)ref:(float)green:(float)blue:(float)alpha;
#end
Canvas.h looks like this
#import
#import "drawnObject.h"
#interface Canvas : NSView {
NSMutableArray * myDrawing;
NSPoint downPoint;
NSPoint currentPoint;
NSBezierPath * viewPath;//to show the path as the user drags the mouse
NSNumber * currentToolType;
BOOL mouseUpFlag;//trying a diff way to make it work
BOOL mouseDrag;
}
-(IBAction)useOval:(id)sender;
-(IBAction)useRect:(id)sender;
-(IBAction)showTool:(id)sender;
-(NSRect)currentRect;
-(NSBezierPath *)createPath:(NSRect) aRect;
-(void)setCurrentToolType:(NSNumber *) t;
-(NSNumber *)currentToolType;
#end
In the Canvas.m file there are several functions to deal with the mouse and NSView/XCode also dropped in -(id)initWithFrame:(NSRect)frame and -(void)drawRect:(NSRect)rect Originally I use mouseUp to try to insert the new DrawnObject into the array but that caused a crash. So, now I use two BOOL flags to see when the mouse was released (clunky but I'm trying....)in drawRect to insert into the array. I've included the method below and indicated where it causes the app to fail:
- (void)drawRect:(NSRect)rect { //This is called automatically
// Drawing code here.
//NSLog(#"Within drawRect tool type is %d", [self currentTool]);
NSRect bounds = [self bounds];
NSRect aRect = [self currentRect];
viewPath = [self createPath:aRect];
//the createPath method uses the tool type to switch between oval and rect bezier curves
if(mouseUpFlag==YES && mouseDrag==YES){
mouseDrag=NO;
//Create a new drawnObject here
DrawnObject * anObject = [[DrawnObject alloc]init];//- WORKS FINE UP TO HERE
NSLog(#"CREATED NEW drawnObject");
[anObject setAPath:viewPath]; //- INSTANT APP DEATH!!!!
NSLog(#"Set a path in drawnObject");
[anObject setToolType:[[NSNumber alloc]initWithInt:5]];
NSLog(#"Set toolType in DrawnObject");
[anObject setToolType:currentToolType];
[myDrawing addObject:anObject];
NSLog(#"Added Object");
}
[[NSColor colorWithCalibratedRed:0.0 green:0.9 blue:0.0 alpha:0.5]set];
[NSBezierPath fillRect:bounds];
[[NSColor lightGrayColor]set];
[viewPath stroke]; //This is so the user can see where the drawing is being done
//Now, draw the paths in the array
[[NSColor blueColor]set];
for(DrawnObject * indexedObject in myDrawing){
[[indexedObject aPath] stroke];//This will do the actual drawing of ALL objects
}
}
I guess this has something to do with object scope or something but I just can not figure it out. As I said, as I've tried things the code has sort of undergone an metamorphosis, sadly not for the better. Like those BOOLS etc.
HELP! Any clever people out there, point me in the right direction please!
ADDED THIS ON:
-(NSBezierPath *)createPath:(NSRect) aRect
{
NSBezierPath * tempPath;
//I need to know what tool
switch(0){ //temporary - this would use the toolType as a selector
case 0:
tempPath = [NSBezierPath bezierPathWithOvalInRect:aRect];
break;
case 1:
tempPath = [NSBezierPath bezierPathWithRect:aRect];
break;
default:
tempPath = [NSBezierPath bezierPathWithOvalInRect:aRect];
break;
}
return tempPath;
}
You said your init method was:
-(void)init {
[super init];
//set default color = black
toolColor.rd=1.0;
toolColor.grn=1.0;
toolColor.blu=1.0;
toolColor.alp=1.0;
//set default size
toolSize=0.8;
//set default toolType
toolType=0;
//oval
NSLog(#"Init %#",self);
}
This is definitely wrong; read up on how to create an init method in the Obj-C guide or by reading sample code. Here's what it should look like:
-(id)init {
if (self = [super init]) {
//set default color = black
toolColor.rd=1.0;
toolColor.grn=1.0;
toolColor.blu=1.0;
toolColor.alp=1.0;
//set default size
toolSize=0.8;
//set default toolType
toolType=0;
//oval
NSLog(#"Init %#",self);
}
return self;
}
By not returning anything from -init, you were preventing the object's creation. Good luck! :-)
Edit: Ashley beat me to it...
What do you mean by “crash”?
Does anything appear in the Debugger Console (⇧⌘R)?
Does a stack trace appear in the Debugger window?
If there's a stack trace, where in your code does it crash?
It just hangs. In the debugger I see:
[Session started at 2008-11-28 14:40:34 +1000.]
2008-11-28 14:40:36.157 CH18Challenge_try2[1893:10b] Mouse Down at (80.000000,285.000000)
2008-11-28 14:40:36.333 CH18Challenge_try2[1893:10b] Mouse Up at (166.000000,217.000000)
2008-11-28 14:40:36.348 CH18Challenge_try2[1893:10b] Init
2008-11-28 14:40:36.349 CH18Challenge_try2[1893:10b] CREATED NEW drawnObject
[Session started at 2008-11-28 14:40:36 +1000.]
Loading program into debugger…
GNU gdb 6.3.50-20050815 (Apple version gdb-962) (Sat Jul 26 08:14:40 UTC 2008)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-apple-darwin".Program loaded.
sharedlibrary apply-load-rules all
Attaching to program: `/Users/johan_kritzinger/Documents/Cocoa/CH18Challenge_try2/build/Debug/CH18Challenge_try2.app/Contents/MacOS/CH18Challenge_try2', process 1893.
(gdb)
Then I have to force quit to stop it.
We need to see the implementation of setAPath from DrawnObject.m. Also, for the "stack trace" look on the upper left of the debugger--it should list a stack of functions showing where in your code the crash is. Make sure you're running in Debug mode, not Release.
On the command line you can type print-object and you can
set a breakpoint in that line and step through it from there. It seems setAPath is somehow broken
Regards
Friedrich
What you have is not a crash. A crash is when a signal is raised (like EXC_BAD_ACCESS) or an uncaught exception.
What you have seems to be an infinite loop.
You need to use the pause button in the Debugger and see exactly where. I would guess that you have an infinite loop in your setAPath: method. You need to work out why this function is looping indefinitely.

event scope

Given
#interface Canvas:NSView {
NSNumber * currentToolType;
...
}
declared in my .h file
and in the .m file
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
currentToolType=[[NSNumber alloc]initWithInt:1];
}
return self;
}
and further down
-(void)mouseUp:(NSEvent *)event
{
NSLog(#"tool value in event: %d",[currentToolType intValue]);
//rest of code
}
-(NSBezzierPath *)drawPath:(NSRect)aRect
{
NSLog(#"tool value in draw: %d",[currentToolType intValue]);
//rest of drawPath method code that uses the value of currentToolType in a switch statment
}
-(IBAction)selectToolOne:(id)sender
{
[currentToolType release];
[currentToolType = [[NSNumber alloc]initWithInt:0];
}
-(IBAction)selectToolTwo:(id)sender
{
[currentToolType release];
[currentToolType = [[NSNumber alloc]initWithInt:1];
}
The action methods are the only place where currentToolType is changed. But, for some reason, it seems to be a different instance of currentToolType in the mouseUp. I did not write (or synthesize) accessors for the var as it is used only by itself. I noticed that initWithFrame is called twice - I'm assuming it's for the parent window and the NSView?
What am I missing?THANKS!
This is an XCode generated Document based app using COCOA and Obj-C. I'm new at both.
You mention that initWithFrame: is called twice. Your initWithFrame: should only be called once (unless you happen to have two Canvas views).
Is it possible you have the Canvas view in your nib/xib file and are also creating another in code (with alloc/initWithFrame:)?
In which case you have two Canvas objects. You probably have one hooked up to your controls and the other one is in the window (and thus responding to the mouseUp: and it is giving you the same value every time).
If you have the Canvas view setup in IB, you can fix this problem by removing your code that is creating the second one.
You've probably run in to a special case: NSNumber could have cached instances to represent commonly-used numbers.
Two observations, though:
You're wasting a whole lot of memory using NSNumber when you could be simply using NSIntegers or maybe an old-fashioned enumerated type, completely avoiding the object overhead.
You never actually showed your code for when you look at the instances of NSNumber; without it, there's not really enough information here to answer your question.