Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trial
James Brown
2,095 PointsHow to set rules for and manipulate multiple child nodes (from SKSpriteNodes) individually.
I have just completed the Sprite Kit tutorial, what a fantastic tutorial, I learnt so much from working through it, and as it moved on I understood more and more about the structure of a project.
The tutorial has taught me almost everything I need to know to create a game I've been wanting to make for years. Except for one thing. My game has numerous child nodes generated from a single SKSpriteNode. I generate 25 at the start of the game and that's it, I set them to a random position and a vector. But I want to be able to give properties to each child node, for example a counter, and rules to follow if something happens to the particular child node for instance when child node is hit de-iterate counter, or after 5 seconds + random time, change vector. These instructions would be identical for all nodes but of course the counter and the what-if situations will be different for each child node.
What I want to know is how and where-in-the-code can I set these sorts of properties for the child nodes?
There was something similar to the counter I'm after in the tutorial, the "isDamaged" property which enabled two BOOL states. What I'm wanting is an NSInteger (between +3 and -10). Can I create a counter using the same form as the "isDamaged" property? (I didn't fully understand this in the tutorial so it's difficult to translate it).
Thank you for reading my question, any guidance would be much appreciated :)
5 Answers
Stone Preston
42,016 Pointsyou would just create a custom class for your child nodes. in the class is where you would add the properties you want, the counter for instance. in the initializer you would set this to an initial value, then you could add a decrement method that decrements the counter and a method to change its position. then using the collision detection methods you learned in the tutorial you would call that child nodes decrement method whenever it gets hit.
if you give your child node all the same name you can loop through them using the enumerateChildNodesWithName method in your gameplay scene and call their changePosition method every 5 seconds from within the update method
James Brown
2,095 PointsThanks Stone!
By "custom class for your child nodes" do you mean an SKSpriteNode that has been created as a separate file (i.e. New File/ObjectiveC Class/SKSpriteNode...)? If so, that's great, I have that already. And just to clarify, is the "initializer" the init method in the gamePlayScene or is it within the custom SKSpriteNode class somewhere?
Thank you so much for "enumerateChildNodesWithName" and "changePosition" Those are exactly the terms I've been looking for but couldn't find :)
Stone Preston
42,016 Pointsyes I mean a custom class that inherits from SKSpriteNode. the initializer would be in the custom subclass. you would have to implement it yourself. you may already have a convenience constructor implemented, so if so you would just use that
James Brown
2,095 PointsHi Stone,
Just one last question, how do I call the enumerateChildNodes method in my update method? I think I have the method correct, I'm just not sure how to call it (what language to use).
(void) update:(NSTimeInterval)currentTime { [???] }
(void) enumerateChildNodesWithName:(NSString )Neut usingBlock:(void (^)(SKNode *, BOOL *))block { BOMNeutNode *neut = (BOMNeutNode) [self childNodeWithName:@"Neut"]; [self runAction:[SKAction waitForDuration:2.0] completion:^{ [neut setNeutDirection]; } }
Stone Preston
42,016 Pointsyou dont implement the enumerateChildNodesWithName method yourself. its a method of SKNode (your SKScene inherits from SKNode). so you would call that inside your update method which gets called every second:
- (void) update:(NSTimeInterval)currentTime {
//assuming you named your nodes @"Neut"
[self enumerateChildNodesWithName:@"Neut" usingBlock:^(SKNode *node, BOOL *stop) {
[self runAction:[SKAction waitForDuration:2.0] completion:^{
[node setNeutDirection];
}
}];
}
if you wanted to do this every 5 seconds you could increment a timer inside the update method and check to see when it reaches 5
James Brown
2,095 PointsThanks again Stone,
I know I said that was my last question... but I'm really close to getting over this hurdle, and I know how to do everything after this. I'm just finding it difficult to reference the child nodes within the enumerateChildNodes method.
So, I am giving the BOMNeutNodes a moodValue when I create the child nodes, I have created a property for this in the Node itself and the node's header file so that it comes up in green in the GamePlayScene in Xcode.
-
(void) createNeutAtPosition:(CGPoint)position {
for (int i=0; i < 15; i++) {
float y = [BOMUtil randomWithMin:20 max:self.frame.size.height-20]; float x = [BOMUtil randomWithMin:20 max:self.frame.size.width-20]; BOMNeutNode *neut = [BOMNeutNode neutAtPosition:CGPointMake(x, y)]; neut.moodValue = 0; [self addChild:neut];
}
for (int i=0; i < 5; i++) {
float y = [BOMUtil randomWithMin:20 max:self.frame.size.height-20]; float x = [BOMUtil randomWithMin:20 max:self.frame.size.width-20]; BOMNeutNode *neut = [BOMNeutNode neutAtPosition:CGPointMake(x, y)]; neut.moodValue = 1; [self addChild:neut];
}
for (int i=0; i < 5; i++) {
float y = [BOMUtil randomWithMin:20 max:self.frame.size.height-20]; float x = [BOMUtil randomWithMin:20 max:self.frame.size.width-20]; BOMNeutNode *neut = [BOMNeutNode neutAtPosition:CGPointMake(x, y)]; neut.moodValue = -1; [self addChild:neut];
}
}
So I'm creating 15 "Neuts" with a value of 0, 5 with a value of -1 and 5 with a value of +1. Then in my GamePlayScene update method I'm having a problem because the "moodValue" is a property of BOMNeutNode rather than SKNode (and the *node is an SKNode), so I can't use the syntax node.moodValue. If I declare a BOMNeutNode *neut within the enumerateChildNodes method it loses the value passed to the child nodes when I initialised them and I just get a value of 0 for everything. I've checked this with an NSLog. The BOMNeutNode declaration below is the only one I've found that runs successfully, but it still loses the value. If instead of "self" I use "BOMNeutNode" or "neutChild" I then can't use the "childNodeWithName" term.
-
(void) update:(NSTimeInterval)currentTime {
[self enumerateChildNodesWithName:@"Neut" usingBlock:^(SKNode *node, BOOL *stop) {
BOMNeutNode *neutChild = (BOMNeutNode*)[self childNodeWithName:@"Neut"]; if (neutChild.moodValue == 0) { NSLog(@"moodValue = 0"); node = [BOMNeutNode spriteNodeWithImageNamed:@"Neut_Neutral"]; } else if (neutChild.moodValue >= 1) { NSLog(@"moodValue = 1"); node = [BOMNeutNode spriteNodeWithImageNamed:@"Neut_Happy"]; } else if (neutChild.moodValue <= -1) { NSLog(@"moodValue = -1"); node = [BOMNeutNode spriteNodeWithImageNamed:@"Neut_Angry"]; }
}]; }
Thank you so much for your help so far. I really think this hurdle is the only one I need to get over and then the rest I'm very confident with.
Stone Preston
42,016 Pointswhen you call the enumerateChildNodesWithName method, just cast the node in the block argument to the type you need. since your BOMNeutNode inherits from SKNode, it should work just fine.
[self enumerateChildNodesWithName:@"Neut" usingBlock:^(SKNode *node, BOOL *stop) {
//cast the node defined in the block declaration above from SKNode to BOMNeutNude
BOMNeutNode *neutChild = (BOMNeutNode*)node;
if (neutChild.moodValue == 0) {
NSLog(@"moodValue = 0");
node = [BOMNeutNode spriteNodeWithImageNamed:@"Neut_Neutral"];
} else if (neutChild.moodValue >= 1) {
NSLog(@"moodValue = 1");
node = [BOMNeutNode spriteNodeWithImageNamed:@"Neut_Happy"];
} else if (neutChild.moodValue <= -1) {
NSLog(@"moodValue = -1");
node = [BOMNeutNode spriteNodeWithImageNamed:@"Neut_Angry"];
}
}];
}
James Brown
2,095 PointsThank you, that worked, you are a saint!
Stone Preston
42,016 Pointshaha no problem