Question

Code --> http://pastebin.com/W3DMYjXa

I am tinkering with SpriteKit and I cannot seem to get an emitter node to rotate with my spaceship the way I would like.

I watched the Apple Tech Talks Session where they described making a spaceship and using an emitter for the thrust exhaust, but they lock the ship to only face one direction.

In my experiment I am trying to allow the player sprite (spaceship) to travel in any direction, I have rotation and scrolling working for the player, but the particle emitter doesn't seem to rotate with the player.

my hierarchy looks something like this

Scene

-->World Node

-->Player Node

---->Player Sprite

---->Emitter Node

My theory is that If I rotate (Player Node) it should rotate both of its children, and it does rotate them, but the emitter continues to emit in the same direction.

I can change the emission angle manually, but it seems needlessly complicated.

here is what I am using to rotate

-(void)rotatePlayerToDirection:(TTDirection)direction {

    CGFloat radDir;
    CGFloat emiDir;
    scrollDirection = direction;

    switch (direction) {
        case TTUp:
            radDir = 0;
            emiDir = 3.142;
            break;
        case TTRight:
            radDir = 4.712;
            emiDir = 1.571;
            break;
        case TTDown:
            radDir = 3.142;
            emiDir = 0;
            break;
        case TTLeft:
            radDir = 1.571;
            emiDir = 4.712;
            break;

        default:
            break;
    }



    SKAction *rotatePlayer = [SKAction rotateToAngle:radDir duration:0.1 shortestUnitArc:YES];

    [playerNode runAction:rotatePlayer];

}

Am I missing something here?

Here is a video of this in action

http://youtu.be/NGZdlB9-X_o

Was it helpful?

Solution

I'm fairly certain there is a bug in spritekit to do with the targetNode and rotation. The other answer seems to suggest this is expected behavior, but that ignores that the docs explicitly give this situation as a motivation for the existence of targetNode.

Working with Other Node Types

What you really want is for the particles to be spawned, but thereafter be independent of the emitter node. When the emitter node is rotated, new particles get the new orientation, and old particles maintain their old orientation.

It then gives an example using targetEmitter on how to achieve this. However, the example code does not work.

It seems that setting targetNode breaks particle rotation.

UPDATE: I found a workaround.

Spritekit is failing to adjust the SKEmitterNode.emissionAngle property when SKEmitterNode.targetNode is set. You can work around this by setting it manually after actions have been processed.

Assuming that your SKEmitterNode has only one parent, you can do something like this in your scene.

- (void)didEvaluateActions
{
    // A spaceship with a thrust emitter as child node.
    SKEmitterNode *thrust = (SKEmitterNode *)[self.spaceship childNodeWithName:@"thrust"];
    thrust.emissionAngle = self.spaceship.zRotation + STARTING_ANGLE;
}

Note that if there are multiple ancestor nodes which may be rotated, you will need to loop through them and add all of their zRotations together.

OTHER TIPS

I know this is an old question, and the OP has probably found a solution or workaround, but I thought I would add by two pence worth, in case it helps anybody else.

I am just starting out with SpriteKit and Swift, and am developing a game where nodes fly around and collide with each other, changing direction of travel frequently.

I add an emitter to each node, and initially found that as the node rotated and changed direction, the emission 'trail' remained fixed. I then read that the emitter should have its target node set not as the node it is a child of, but the scene they both exist in.

That at least made the particle trail twist and turn realistically rather than just spurt out in one direction.

But the nodes can spin and changes direction, and I wanted the particles to trail away from the node in the opposite direction to its travel - just like a smoke trail from a plane or rocket.

As the node may rotate, setting the emitters angle of emission to the z-rotation of the node does not work. So...

I track all onscreen nodes in a dictionary, removing them from the scene as they travel off the screen.

So in didSimulatePhysics I use the same dictionary to get the node and calculate the angle of travel from the velocity vector, and then set the emitters emission angle accordingly :

import Darwin

// in your scene class...

override func didSimulatePhysics() {
    removeOffScreenParticles()
    for (key,particle) in particles {
        let v = particle.physicsBody!.velocity
        particle.trail!.emissionAngle = atan2(v.dy, v.dx) - CGFloat(M_PI)
    }
}

I am aware that this calculation and adjustment is being performed for each on screen node, in every frame, so will probably use my frame counter to only do this check every n'th frame if performance becomes an issue.

I hope this helps someone!

You are setting the emitter's targetNode property to the background. Note that this forces the emitter's particles to be rendered by the background node, and they will be treated as if the background was their parent. So, the particles aren't changing the angle, because the rotation of the background does not change.

Remove [emitter setTargetNode:backgroundNode] and the emitter's emission angle should be rotating correctly along with the player.

If you're looking for a 'middle ground', where the angle rotates along with the player, while the already-rendered particles aren't 'stuck' to the emitter, try this:

  • First, do not set the targetNode on the emitter (it defaults to nil).
  • Second, just as you are about to run the rotation action, temporarily set the targetNode to another node, and reset it back to nil when it completes (so that the particles can rotate along):

    emitter.targetNode = backgroundNode;
    [playerNode runAction:rotatePlayer completion:^{
       emitter.targetNode = nil;
    }];
    

Note that the results are also 'middle ground' - the emitter 'puffs' as the target node changes (the previous particles are removed, and new ones are rendered by the new target node). Hope that helps.

Other than that, there's no really straightforward way of achieving this - setting the emission angle manually might be the simplest way, after all. Here's one way to do it, right before running the rotation (the emitter target node is still nil, also, there's no need to hardcode emission angles):

    float originalAngle = emitter.emissionAngle;
    emitter.emissionAngle = emitter.emissionAngle - radDir;
    [playerNode runAction:rotatePlayer completion:^{
        emitter.emissionAngle = originalAngle;
   }];
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top