How to reference the SKSpriteNode that's running an SKAction runblock? - objective-c-blocks

I have a method that creates a random amount of SKSpriteNodes that are supposed to fall from the roof, sit on the ground a little while, then fade out. They need an SKPhysicsBody so they'll fall and bounce properly, but when I create a lot, it's using a lot of CPU, so I was trying to remove their physicsbody when they'd been sitting on the ground a little while.
I can't figure out how to use a runBlock to just say "do (blah) to the object calling this runBlock", is there a way?
for (int i=0; i < howManyDollars; i++) {
SKSpriteNode *bills = [SKSpriteNode spriteNodeWithImageNamed:#"bills.png"];
int startX = arc4random() % (int)self.size.width/3;
int startY = self.size.height;
bills.position = CGPointMake(startX, startY);
bills.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(bills.size.width/2, bills.size.height/2)];
bills.physicsBody.affectedByGravity = YES;
[self addChild:bills];
SKAction *wait1 = [SKAction waitForDuration:1.5];
SKAction *wait2 = [SKAction waitForDuration:2];
SKAction *removeDynamics = [SKAction runBlock:^(void) {
// this is the spot I am confused at.
//bills.physicsBody = nil;
}];
SKAction *fade = [SKAction fadeOutWithDuration:15];
SKAction *remove = [SKAction removeFromParent];
[bills runAction:[SKAction sequence:#[wait1, removeDynamics, wait2, fade, remove]]];
}

You have it already:
SKAction *removeDynamics = [SKAction runBlock:^(void) {
bills.physicsBody = nil;
NSLog(#"bills is: %# (%p)", bills, bills);
}];
The cool thing about blocks is that they retain (copy) the objects in local scope. So if you create a block like the above and dereference the bills object, then for each block it will use the corresponding bills object that runs the action.
I've added the log statement so you can see for yourself that each block when executed is referencing a different object even though the variable name is the same and even though one would think based on the traditional sequential programming model that each bills object would have long been gone or referencing only the last object created.

Related

Is there a clever way to set a total duration for a sequence of SKActions?

I think the answer is no, but here’s what I’m doing and I’ll then show what I wish I could do. Overall, what I’m going for is a sequence of dampening rotations that total 24 seconds total.
#define START_ANGLE M_PI/4.0
#define SPRING_TENSION 0.9
#define START_DURATION 1.3 // Hack!
NSMutableArray *animationA = [NSMutableArray array];
double a = START_ANGLE;
double d = START_DURATION;
while (a * a > 0.01) {
// Bounce to the other side.
SKAction *a1 = [SKAction rotateToAngle:a duration:d];
a1.timingMode = SKActionTimingEaseIn;
[animationA addObject:a1];
// Return to rest
SKAction *a2 = [SKAction rotateToAngle:0 duration:d];
a2.timingMode = SKActionTimingEaseOut;
[animationA addObject:a2];
// Reduce the height of the bounce by the spring's tension
a *= -SPRING_TENSION;
d *= SPRING_TENSION;
}
SKAction *seqA = [SKAction sequence:animationA];
[mySprite runAction:seqA];
What I want is for the total sequence duration to be 24 seconds and for the intervening actions to be inferred from that. Since the API doesn’t allow that, in order to arrive at this solution I had to precompute how many iterations of the loop there’d be and track the angle’s attentuation in order to arrive at START_DURATION.
It’s hacky and inflexible -- bad enough that I had to unroll the loop to figure out each component duration, but what if I want to change the SPRING_TENSION later but maintain the total duration? I’d have to unroll the loop again and recompute START_DURATION.
I wish I could do something more flexible like this:
#define START_ANGLE M_PI/4.0
#define SPRING_TENSION 0.9
#define TOTAL_DURATION 24.0
NSMutableArray *animationA = [NSMutableArray array];
double a = START_ANGLE;
while (a * a > 0.01) {
// Bounce to the other side.
SKAction *a1 = [SKAction rotateToAngle:a]; // Duration will be computed from total duration
a1.timingMode = SKActionTimingEaseIn;
[animationA addObject:a1];
// Return to rest
SKAction *a2 = [SKAction rotateToAngle:0]; // Duration will be computed from total duration
a2.timingMode = SKActionTimingEaseOut;
[animationA addObject:a2];
// Reduce the height of the bounce by the spring's tension
a *= -SPRING_TENSION;
d *= SPRING_TENSION;
}
SKAction *seqA = [SKAction sequence:animationA duration:TOTAL_DURATION]; // This computes all undefined component durations
[mySprite runAction:seqA];
That way, I get what I want: I get an animation sequence of a chosen duration and let the framework figure out the intervening rotations. This is akin to how keyframes are inferred by CAKeyframeAnimation. Is there a clever way to do this with SpriteKit that I’ve overlooked?

Trigger audio files based on a method call or other variables

I am designing a game that changes scenes as the user is playing. The scenes change based on a NSTimer. I can ge everything to change accordingly in regards to graphics. Can even get background music to change.
Where I seem to be hitting a roadblock is changing the score sound and the sound for when the player collides with an object and dies.
As it it's right now I can cancel the sound for scene one but cannot get another one to play.
* on phone don't know how to hand code a code block on here*
If([_timer isValid]) {
//plays correct audio and does not continue when scene changes
}
Else if ([_timer == nil ]){
//suppose to play other sound but does nothing.
}
I would really prefer to call the different sounds based on if a method is called to change something along the lines of
Else if ([ _method1 == TRUE]) {
//play sound
}
Any advice in the right direction appreciated.
EDIT
- (void)didBeginContact:(SKPhysicsContact *)contact {
if( _moving.speed > 0 ) {
if( ( contact.bodyA.categoryBitMask & scoreCategory ) == scoreCategory || ( contact.bodyB.categoryBitMask & scoreCategory ) == scoreCategory ) {
// has contact with score entity
_score++;
_scoreLabelNode.text = [NSString stringWithFormat:#"%d", _score];
//Pick Up Egg
//Sound for Score
SKAction* teslaPoint = [SKAction playSoundFileNamed:#"Point1" waitForCompletion: YES];
[self runAction:teslaPoint withKey:#"teslaPoint"];
I have been trying to structure an IF statement based on the background image because that changes with the sounds.
EDIT
_scoreSound = [SKAudioNode node];
SKAudioNode* teslaPoint = [SKAudioNode nodeWithFileNamed:#"Point1"];
[_scoreSound addChild:teslaPoint];
EDIT
THIS IS ADDED IN initwithSize:
_scoreSound = [SKNode node];
[self addChild:_scoreSound];
Then its called in the contacts
EDIT
//Sound for Score
if([_timer isValid]){
SKAction* teslaPoint = [SKAction playSoundFileNamed:#"Point1" waitForCompletion:YES];
[self runAction:teslaPoint withKey:#"teslaPoint"];
}
if ([_bgImage isEqual: #"bgimage2.png"]){
SKAction* teslaPoint = [SKAction playSoundFileNamed:#"grassypoint" waitForCompletion:YES];
[self runAction:teslaPoint withKey:#"teslaPoint"];
}

SpriteKit - The child of my node does not appear to be changing its zPosition attribute

my issue is, I've got a "2.5D" game where characters that come into contact with one another need to dynamically change their zPosition. I'm using physics bodies to achieve this.
It works OK with the characters, however, the shield they are holding (a child object) is not following suit even though I'm logging a correct change of zPosition attribute.
if (((firstNode.categoryBitMask & dudeCategory) != 0) && ((secondNode.categoryBitMask & shieldGuyCategory) !=0))
{
if ((firstNode.node.position.y < secondNode.node.parent.position.y))
{
[secondNode.node.parent enumerateChildNodesWithName:#"shieldNode" usingBlock:^(SKNode *node, BOOL *stop){
NSLog(#"Ooh I Say! Shield zPos = %f", node.zPosition);
SKAction *zPos = [SKAction runBlock:^{
node.zPosition = node.zPosition -200;
NSLog(#"And now, %# zPos = %f", node.name, node.zPosition);
NSLog(#"But static NPC zPos = %f", firstNode.node.zPosition);
NSLog(#"And finally, Shield-holder Guy zPos = %f", secondNode.node.parent.zPosition);
}];
[node runAction:zPos];
}];
secondNode.node.parent.zPosition = secondNode.node.parent.zPosition -200;
}
}
Halp!
Instead of setting the zPosition using SKAction runBlock:, set it directly.
[secondNode.node.parent enumerateChildNodesWithName:#"shieldNode" usingBlock:^(SKNode *node, BOOL *stop){
node.zPosition -= 200;
}];
So, it turns out I was setting the zPosition for the shield in its method. Removing that restores normal behaviour when the parent changes zPosition, having it declared explicitly seems to override the nested behaviour.

efficient way to create animations

i was wondering if there is a more efficient way to create animations using pngs ..?
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"a1"];
SKTexture *a1 = [SKTexture textureWithImageNamed:#"a1"];
SKTexture *a2 = [SKTexture textureWithImageNamed:#"a2"];
SKTexture *a3 = [SKTexture textureWithImageNamed:#"a3"];
SKTexture *a4 = [SKTexture textureWithImageNamed:#"a4"];
SKTexture *a5 = [SKTexture textureWithImageNamed:#"a5"];
NSArray *animationFramesFarmer = #[a1, a2, a3, a4, a5];
SKAction *action = [SKAction animateWithTextures:animationFramesFarmer timePerFrame:0.2];
SKAction *endlessAction = [SKAction repeatActionForever:action];
[sprite runAction:endlessAction];
For some reason it seems most people posting here love the in app programmatic generation of their data.
Rather than efficient, I would look at flexible. For that, one of the best ways is to create your own "format" to represent the animation.
For example, here is a simple JSON that represents the what you are doing:
{
"frames" :
[
"a1",
"a2",
"a3",
"a4",
"a5"
],
"timePerFrame": 0.2
"iterations": -1
}
I've used -1 to represent "forever".
Your animation format loader then can be as "efficient" as you want. Atlases are the better way to go, but that really just means adjusting your format to accommodate that.
What are the benefits? Well, you can have an artist generate your the data for you. So their deliverable contains the image(s) and the data file. Even better if you have a tool chain to do it for you.
You also aren't coding specific "generator" sections just to create each animation type.
This is a simplistic case I've outlined above. I build games and I have an art toolset which then export the data out the formats I need and can tell you from experience, in the long run your life is way easier.
One last note. Seeking efficiency is fine, but make sure you need it. Too often I find people pre-optimize or needlessly optimize. Get it working, run it, see where you bottlenecks are (i.e profile). If you create that character only once, then it can be the most efficient code out there, but it won't matter for your overall frame rate/performance of the app.
You could create the frames in a loop like this
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"a1"];
NSMutableArray *animationFramesFarmer = [[NSMutableArray alloc] init];
for (int i = 1; i <= 5; i++)
{
NSString *name = [NSString stringWithFormat:#"a%i", i];
SKTexture *a = [SKTexture textureWithImageNamed:name];
[animationFramesFarmer addObject:a];
}
SKAction *action = [SKAction animateWithTextures:animationFramesFarmer timePerFrame:0.2];
SKAction *endlessAction = [SKAction repeatActionForever:action];
[sprite runAction:endlessAction];

how to increase the value of an NSNumber every 5 seconds?

i'm building a game for which i the gravity as:
self.physicsworld.gravity = CGVectorMake(gravityX , gravity-Y);
i want to change the forces acting on the sprite node with time.
since i'm a newbie, i'm not able to code a loop(s) so that the gravity changes over time thereby changing the difficulty of the game. if i want the float variable gravityX to increment by 0.5 every 5 seconds what should be the code implemented.
the main problem i'm facing with using the FOR LOOP here is in the CONDITION; I don't know how to make the computer understand actual time since the game started.
This can be done using the SKAction class. Add the following method to your SKScene subclass, and call it when you want gravity to start increasing. I've included both the Objective-C and the Swift version.
Objective-C:
- (void)startGravityIncrease {
SKAction *blockAction = [SKAction runBlock:^{
CGVector gravity = self.physicsWorld.gravity;
gravity.dx += 0.5;
self.physicsWorld.gravity = gravity;
}];
SKAction *waitAction = [SKAction waitForDuration:5];
SKAction *sequenceAction = [SKAction sequence:#[waitAction, blockAction]];
SKAction *repeatAction = [SKAction repeatActionForever:sequenceAction];
[self runAction:repeatAction];
}
Swift:
func startGravityIncrease() {
let blockAction = SKAction.runBlock { () -> Void in
self.physicsWorld.gravity.dx += 0.5
}
let waitAction = SKAction.waitForDuration(5)
let sequenceAction = SKAction.sequence([waitAction, blockAction])
let repeatAction = SKAction.repeatActionForever(sequenceAction)
self.runAction(repeatAction)
}