Accessing a property of a node - objective-c

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.

Related

KVO: How to observe plain pointers?

We've been using KVO to track some changes in objects. Recently we changed code to c++ to put it into a multi-platform library.
This left us with some properties that are now pointers to c++ objects, but the same reasoning would hold for, say an void *, or any pointer to what's not a true Objective-C object.
So basically we have
#property (nonatomic) void *value;
There are several problems arising:
No change notifications are generated automatically.
This can be handled by implementing the setter manually, i.e.
-(void)setValue:(void *)value {
[self willChangeValueForKey:#"value"];
_value = value;
[self didChangeValueForKey:#"value"];
}
The class is not KVO-compliant for the key.
This seems natural, as it's not clear how to put the custom data into some object to pass along. This breaks observers that ask for old or new data in the change dictionary. Again, one can help oneself here and wrap things up manually:
-(id)valueForKey:(NSString *)key {
if ([key isEqualToString:#"value"])
return [NSValue valueWithPointer:_value];
return [super valueForKey:key];
}
So having sorted out some glitches, I wonder if there are more pitfalls to look out for. Or are there some best practices in that regard?

Cannot keep track of the value of my property in Objective-c

I have a problem with one property in one class. I am coding with iOS 6.1 if it makes any difference.
The class is UIViewController and the property is the declared in the header file like so:
// Keeps track of time in seconds
#property (nonatomic, strong) NSNumber *timeInSeconds;
In my implementation file I use the property during 3 occasions:
one is to add time with the method - (void)addTime
one is to subtract time with the method - (void)subtractTime
Those two methods use the property like so:
- (void)addTime
{
CGFloat timeFloat = [self.timeInSeconds floatValue];
// Here I set the value of the property timeInSeconds, but I can't access that value later on in the code!
self.timeInSeconds = [NSNumber numberWithFloat:timeFloat +5];
NSLog(#"Total Time:%#", self.timeInSeconds);
}
The two methods addTime and subtractTime do what they are supposed to do, and they keep a good track of the property timeInSeconds value as I add then subtract then add...
The problem is when I call in the same implementation file the third method which is:
- (void)updateLabelTime
{
self.label.attributedText = [[NSAttributedString alloc]initWithString:[self.timeInSeconds stringValue]];
[self.label setNeedsDisplay];
[NSTimer scheduledTimerWithTimeInterval:0.8 target:self selector:#selector(updateLabelTime) userInfo:nil repeats:YES];
}
I also tried to create a the NSAttributedString with stringWithFormat instead of initWithString but the problem persists which is that instead of returning the value of the property timeInSeconds which i previously set using addTime and subtractTime, it calls the getter which creates a new instance of timeInSeconds since in my getter I have lazy instantiation.
I tried to not write the getter/setter for the property (since I am using iOS 6.1) but it makes no difference.
If I just set the label to some random string, it would work. The problem is that if I know the value of timeInSeconds is 55, it would still create a new _timeInSeconds.
I tried my best with my English since I am French, please don't answer if the question was already asked by a beginner iOS developer and just redirect me. I couldn't find an answer though, thanks!
EDIT: Here is the custom getter
- (float)timeInSeconds
{
if (!_timeInSeconds) {
_timeInSeconds = 0;
}
return _timeInSeconds;
}
SECOND EDIT:
The stupid beginner mistake that I made was that addTime and subtractTime are actually implementing a protocol and they set the property which "lives" in another class which is why I could not access it! That other class that needs the protocol was creating a new instance of the class where addTime and subtractTime is written.
What needs to be done is to set the controller as the delegate for the protocol. I did this in the viewDidLoad method with something like:
self.view.delegate = self;
Thanks for all the help.
In your header file, declare this property:
#property (assign) float timeInSeconds;
In the implementation file:
#synthesize timeInSeconds = _timeInSeconds;
- (void)viewDidLoad
{
[super viewDidLoad];
_timeInSeconds = 0.0f;
}
- (void)addTime
{
_timeInSeconds += 5.0f;
}
This should initialize timeInSeconds to zero and then increment its value by 5 each time you call addTime. To use its value in the label:
- (void)updateLabelTime
{
self.label.text = [NSString stringWithFormat:#"%f", _timeInSeconds];
}
In your custom getter you are assigning a scalar value to an object property. In fact assigning zero to an object property is the equivalent of setting the object to nil.
What you need to do is this:
- (float)timeInSeconds
{
if (!_timeInSeconds) {
_timeInSeconds = [NSNumber numberWithFloat:0.0f];
// or alternatively with the latest version of objective c
// you can more simply use:
// _timeInSeconds = #(0.0f);
}
return _timeInSeconds;
}

cocos2d sub-classing

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);
}

How to use a C array as instance variable?

For example I want to store this in an ivar:
CGFloat color[4] = {red, green, blue, 1.0f};
so would I put this in my header?
CGFloat color[];
How would I assign values to that guy later? I mean I can't change it, right?
Instance variables are zeroed out on allocation so you can't use initialisers with them.
You need something like this:
// MyObject.h
#interface MyObject
{
CGFloat color[4];
}
#end
// MyObject.m
#implementation MyObject
-(id) init
{
self = [super init];
if (self != nil)
{
color[0] = red;
color[1] = green;
color[2] = blue;
color[3] = alpha;
}
return self;
}
You'd need to put the size in so that enough space is reserved.
CGFloat color[4];
Or use a pointer to the array, but that's more work and hardly superior for representing something as well-known as a color.
You are better off using a NSColor object if you can.
However, to your original question one of my first questions is where do you want to create this array. When you say put it in a header do you mean as a member of a class or as a global array, you certainly can do both however there are some serious gotchas with putting globals in headers. If you need that follow up and I can explain it better.
If it is in a class then you can just declare it like any other member field. If you say
CGFloat color[4];
then the space for the array is allocated in your object itself. You can also just use a
CGFloat *color;
or its moral equivalent to refer to an array that is stored outside of the object. You do need to manage that storage appropriately however.
This matters in the case you hinted at where you use a constant array object and cannot later change it. That can happen but is rare, since it cannot happen with the first approach, you don't see it in the wild very often.
There is a whole dissertation on the corner cases in here, I am sure it is not helping to go into it. Just use CGFloat color[4] in your object and it won't matter, by the time you see things they will be mutable and you can just use them the way you expect.

Constant NSDictionary/NSArray for class methods

I am trying to code a global lookup table of sorts.
I have game data that is stored in character/string format in a plist, but which needs to be in integer/id format when it is loaded.
For instance, in the level data file, a "p" means player. In the game code a player is represented as the integer 1. This let's me do some bitwise operations, etc. I am simplifying greatly here, but trying to get the point across. Also, there is a conversion to coordinates for the sprite on a sprite sheet.
Right now this string->integer, integer->string, integer->coordinate, etc. conversion is taking place in several places in code using a case statement. This stinks, of course, and I would rather do it with a dictionary lookup.
I created a class called levelInfo, and want to define the dictionary for this conversion, and then class methods to call when I need to do a conversion, or otherwise deal with level data.
NSString *levelObjects = #"empty,player,object,thing,doohickey";
int levelIDs[] = [0,1,2,4,8];
// etc etc
#implementation LevelInfo
+(int) crateIDfromChar: (char) crateChar {
int idx = [[crateTypes componentsSeparatedByString:#","] indexOfObject: crateChar];
return levelIDs[idx];
}
+(NSString *) crateStringFromID: (int) crateID {
return [[crateTypes componentsSeparatedByString:#","] objectAtIndex: crateID];
}
#end
Is there a better way to do this? It feels wrong to basically build these temporary arrays, or dictionaries, or whatever for each call to do this translation. And I don't know of a way to declare a constant NSArray or NSDictionary.
Please, tell me a better way....
If you want an array to be available to all the code in your class, just declare it outside the #implementation context, and then initialize it in your class's +initialize method.
NSArray *levelObjects;
#implementation LevelInfo
+ (void) initialize
{
if (!levelObjects)
levelObjects = [[NSArray alloc]
initWithObjects:#"empty",#"player",#"object",#"thing",#"doohickey",nil];
}
// now any other code in this file can use "levelObjects"
#end
Declare it static so it only needs to be created once.