Emitter not rotating with parent node - ios

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

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.

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

Related

How to make SKEmitter particles stay relative to the background when I move my SKEmitterNode around the screen?

I feel like this should be a very simple question, but I looked around and it seems to just be working for everybody automatically. But for some reason, when I move the particle emitter around the screen, the particles move with it, and there is no trail behind the emitter node of particles.
I am making a SpriteKit game of a character running around on the bottom of the screen, and he has a boost ability which releases small bubble particles behind him when he boosts because he is underwater. But the particles aren't staying relative to the screen, they are relative to the player and move with him.
Here's some code to add the boost bubbles:
func addBoostBubbles(){
boostBubbles = SKEmitterNode(fileNamed: "bubbleBoost")!
boostBubbles.particlePositionRange = CGVector(dx: player.frame.size.width, dy: player.frame.size.height)
boostBubbles.position = player.position
boostBubbles.zPosition = 0
self.addChild(boostBubbles)
}
I call this func to create the emitter node and set it to the player's position.
then to move the position of the bubbles in the didSimulatePhysics func I have this:
boostBubbles.position.x += xAcceleration
I see the bubbles on the screen and the emitter node is moving to the right places, but I want them relative to the background so the bubbles just slowly float up behind the player.
Any help would be greatly appreciated!!!!
Set the targetNode property of your emitter to be the background node. In addBoostBubbles, try adding the line:
boostBubbles.targetNode = <background node name>
https://developer.apple.com/documentation/spritekit/skemitternode
targetNode - The target node that renders the emitter’s particles.
This causes the particles to render as though they are children of the background.
If you don't have a background node then you'll need to add one.

Can I move a node across the screen without SKAction?

I am developing the iPhone version of an Android game using SpriteKit and objective-c. The Android version was developed using libGDX. There the movement across the screen was done by updating the coordinate values whenever the predefined render method is called. While reading the docs in SpriteKit, I found that here the nodes are moved across the screen by using SKAction where the end coordinates and the duration is given and the action is applied to the node. Is this the only way the node can be moved? Or can I update the coordinates(in the update method according to the time interval) even after the adding the node to the parent node(without calling the SKAction anywhere)?
There are a couple of ways to make a node move.
Changing the node's coordinates myNode.position = CGPointMake(myNode.position.x+1, myNode.position.y); will move the node to the right. This method works with regardless on whether they have a physics body or not.
Apply velocity to the node's physics body myNode.physicsBody.velocity = CGVectorMake(100, myNode.physicsBody.velocity.dy); moves the node to the right. Your node requires a physics body for this to work.
Apply an impulse [myNode.physicsBody applyImpulse:CGVectorMake(100, myNode.physicsBody.velocity.dy)]; moves the node to the right. Your node requires a physics body for this to work.
Apply a force [myNode.physicsBody applyForce:CGVectorMake(100, myNode.physicsBody.velocity.dy)]; moves the node to the right. Your node requires a physics body for this to work.
When you apply force is like a motor pushing all the time. If you apply impulse it's like kicking a ball once.
Read up on each method in more detail in the SKPhysicsBody reference.
You can move a node by specifying the position property (see the SKNode
documentation)
node.position = CGPointMake(x, y);

SKEmitterNode emitting particles before physics are simulated

I have an SKEmitterNode centered on my player to leave a trail of particles. My player has a physics body and it's moved by physics, not by manually updating it's position.
The issue I'm having is that when the speed of my player increases the particles are emitted from a point behind the player.
I discovered that this is happening because the particles are emitted in the "evaluate actions" part of the frame cycle. My player is afterwards moved by the "simulate physics" part.
The solution I found as a workaround is to move the SKEmitterNode (in the update callback) to the point where my player will be after the physics calculations. This is the code I used:
particleEmitter.position = CGPointMake(
player.position.x + player.physicsBody!.velocity.dx * dt,
player.position.y + player.physicsBody!.velocity.dy * dt
)
UPDATE
At first I had my emitter as a child node of the player, then is when I observed the problem.
I also tried to sync the emitter position exactly to the player position, without accounting for distance moved since the last update (velocity * dt), same problem.
My question is, what would be the correct way of solving this?
UPDATE 2
I've created a playground demonstrating the issue. Here I have the emitter as a child of the player. The more you increase the player's velocity the bigger the gap between the player and the emitted particles.
https://github.com/ovidiupruteanu/SKEmitterNodeTest-Playground
Here is the frame cycle from the apple docs
Your code only estimates the next position of the player. It doesn't consider other factors that may affect the player's position, such as collisions, force fields, linear damping, etc. I suggest you set the emitter's position to the player's position in the didSimulatePhysics callback or add the emitter as a child of player.
Rather than manually repositioning the emitter on every frame, let SpriteKit do that work for you: make the emitter a child node of the player sprite, and the emitter point will stay at the same location relative to the player whenever the player moves.
If you go this route, you might have a problem with the emitted particles also following the player in ways you don't want. You can resolve this by setting the emitter's targetNode to the node containing the player.

SpriteKit - Making a sprite defy gravity (like a balloon)

Does anyone have any idea how I can make my SKSpriteNode defy gravity? I thought of inverting the default gravity but realise I also need things to fall too!
It seems like it should be easy but reading through the documentation I can’t see how I would do it!
Thanks
Update: In iOS 8 / OS X Yosemite (10.10), physics fields provide an elegant solution to these sorts of problems. Here's a quick take on using them to add buoyancy (for a specific set of nodes) to your scene.
Create an SKFieldNode with the linearGravityFieldWithVector constructor, providing a vector that's the opposite of gravity, and add the field node to your scene.
Set the fieldBitMask on your balloons to something unique.
Set the categoryBitMask on the field to something that overlaps with the balloons' fieldBitMask, but that does not overlap with the fieldBitMask of any other bodies.
Now, the balloons will rise or hold steady, but other objects will fall. Tweaking the field's strength will let you tune whether the balloons' buoyancy is perfectly balancing gravity (so that they float in place, but are disturbed when touched), or slightly more or less than gravity (so that they slowly rise or fall).
By default, a field is infinite, covering the whole scene, but you can change that with the field's region property to limit it to a portion of the scene. (This is useful if you want to simulate buoyancy in water — once an object rises past the top of the field at the water's surface, it falls back in.)
Also, if you want variable buoyancy as per #TheisEgeberg's answer, you can control its variation over distance with the falloff property.
In iOS 7 / OS X Mavericks (10.9), or if you want more precise control over which forces apply where and when, you can use the approach from my original answer below.
If you want an object to really float like a balloon — that is, to be buoyant, affected by gravity but also counteracting it — you'll need to apply a force to it on every frame (i.e. in your update: method).
Beware scaling: gravity is a constant acceleration, but if you're applying a force to counteract gravity, a force is proportional to mass. To make a vector that perfectly balances gravity for use in applyForce:, you'll need to:
scale the gravity vector by {-1,-1,-1} to point in the opposite direction
scale by the mass of the body you're applying the force to (F = ma, where gravity or anti-gravity is a).
scale by 150 — there's a bug where the SKPhysicsWorld.gravity property isn't in the same units as applyForce:. (If you turn SKPhysicsWorld gravity off and use SKFieldNode gravity instead, you don't need to do this.)
Unlike turning off affectedByGravity and applying an action to make the balloon rise, this approach works well with the rest of the physics sim. With a balancing force exactly equal to gravity, the balloon will float in place — after colliding with other things it'll return to equilibrium. If the balancing force is greater than gravity, the balloon will rise, but its rise will be hindered by other bodies in its way.
First off, an SKSpriteNode isn't affected by gravity at all. It is the SKPhysicsBody that belongs to the node that is affected by gravity.
Second...
myNode.physicsBody.afectedByGravity = NO;
:D
If you want it to rise upwards then you can add an action to it...
SKAction *moveAction = [SKAction moveByX:0 y:-10 duration:1];
SKAction *repeatingAction = [SKAction repeatActionForever:moveAction];
[myNode runAction:repeatingAction];
As many have said it's about counterforce.
So yes, you can apply a counterforce to the balloon to make it go upwards.
But to make it look like a balloon you need to understand what makes a balloon go up: Air pressure. Since the helium or whatever light gas you are using is lighter than air it will start to go up, or in other words the heavier air will go under the balloon. It's like a piece of wood in water, the heavier water will go under the wood, till the wood is soaked and gets even heavier than the water.
So what you should do is to make the counterforce adapt to the height of the balloon, the higher it gets the less pressure you apply upwards. This is a way to simulate buoyancy.
Here’s my answer:
- (MyClass *)newRisingObject
{
MyClass *risingObject = [MyClass spriteNodeWithImageNamed:#"image"];
[risingObject setPosition:CGPointMake(CGRectGetMidX(self.frame), CGRectGetMinY(self.frame))];
[risingObject setName:#"name"];
[risingObject setUserInteractionEnabled:YES];
// Physics
//
[risingObject setPhysicsBody:[SKPhysicsBody bodyWithCircleOfRadius:risingObject.size.width / 2.0f]];
[risingObject.physicsBody setRestitution:1.0f];
[risingObject.physicsBody setFriction:0.0f];
[risingObject.physicsBody setLinearDamping:0.0f];
[risingObject.physicsBody setAngularDamping:0.0f];
[risingObject.physicsBody setMass:1.3e-6f];
return risingObject;
}
.
- (void)didMoveToView:(SKView *)view
{
/* Setup your scene here */
MyClass *risingObject = [self newRisingObject];
[self addChild:risingObject];
}
.
- (void)update:(CFTimeInterval)currentTime
{
/* Called before each frame is rendered */
[self enumerateChildNodesWithName:#"name"
usingBlock:^(SKNode *node, BOOL *stop)
{
// Push object up
//
[node.physicsBody applyImpulse:CGVectorMake(0.0f, node.physicsBody.mass * OBJECT_ACCELERATION)];
}];
}

Change position of a SKSpriteNode that has a physicsBody

I can't change the position of a SKSpriteNode by changing
self.position = newPosition;
It doesn't work anymore if it has a physicsBody.
A workaround I got is:
self.myStar.physicsBody = nil;
[self.myStar changePosition];
self.myStar.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.myStar.size.width/2];
Or
[self runAction:[SKAction moveTo:newPosiion duration:0.0]];
But #2 isn't smooth. It needs to pop up on another position without a moving action.
I had the same problem. Apparently you cannot explicitly set the position of a Sprite node once it has a PhysicsBody. I solved it by temporarily removing the sprite node's PhysicsBody.
CGFloat yPosition = 400.f;
SKPhysicsBody* goldfishPhysicsBody = _goldfish.physicsBody;
_goldfish.physicsBody = nil;
_goldfish.position = CGPointMake(_goldfish.position.x, yPosition);
_goldfish.physicsBody = goldfishPhysicsBody;
Yes, you can!
I'm not sure what exactly you're doing and where exactly you run this code and what the observed effect is that you meant by "can't change position" but changing a node's position always works, whether the node has a physicsBody or not, and whether the physicsBody is dynamic or static. I verified this in a simple test project with dynamic set to both YES and NO, with both circle and edge loop body types, using both position property and move actions.
You can change the node's position either by setting the position property directly or by using a move action - both variants work. If you use a 0 duration for the move action, it effectively becomes the same as setting the position property.
So whatever problem you're observing, it's not because you can't generally change a node's position once it has a physicsBody. That's absolutely not the case.
I'm guessing you may have run into one of the following problems:
node is already running a different move action, overriding your position changes
you were looking at the wrong node (use logs to verify actual position of the node in question)
you change position elsewhere, for example setting the node's position every frame thus invalidating any other position change
if it's not one of the above, then possibly something else I couldn't think of ...
I had a problem like this where I tried to update position inside didBeginContact:, but it was not having any effect, I think because the physics simulation immediately clobbers the value.
My solution was to cache the new position in a property on my scene and then update the position of the node next time update: was called on my scene.
[SKAction moveTo:newPosition duration:0.0] worked for me, too, and there was no popping. I haven't decided yet which is more elegant.
I think the problem you are having is that in order for you to control a phsyics body, you need to set it to dynamic.
self.myStar.physicsBody.dynamic = NO;
If the dynamic property is set to YES, then the physics engine is in control of it's movement.
As noted in the comments, setting dynamic to NO shouldn't restrict movement via SKAction or position property. However, if it is set to YES, it's possible that something in the physics engine is affecting your attempt to move the node.
If you want the movement to not pop, then you need to set a duration higher than zero to your SKAction or it will pop as you have described. Setting duration to 0.0 is the same as just changing the position.
For example :
[self runAction:[SKAction moveTo:newPosition duration:1.0]];
will move the node to the new position over 1 second.
I ran into a similar problem. When using SKAction, even with duration set to 0.0 I got strange behaviours especially when two SKActions had been triggered at the same time.
I tried setting position directly but as mentioned by others this doesn't work when using the SKPhysicsContactDelegate.
However for me it worked to remove the node from its parent, I then set the new position, and other things I want to change, and then I add the node again to its former parent.
It's not ideal but in some cases it might help.
As an example with the SKPhysicsContactDelegate method didBegin:
func didBegin(_ contact: SKPhysicsContact) {
guard let node = contact.bodyB.node else { return }
node.removeFromParent()
node.position = CGPoint(x: 10, y: 10)
addChild(node)
}
Seems similar to SKSPriteNode position changes to 0,0 for no reason
As stated in answer and comments of this question, it seems you must either set the position before setting the physicsBody and/or set the physicsBody after adding the node to your scene.
I've had this problem in several scenarios and I haven't found out what exactly causes it to fail sometimes.
Yes you can, but it has its price.
I think you have to make a decision: Either let the position being calculated by the PhysicsEngine OR set the position on your behalf.
Look, for an object in a physics world, there is no magical movement from 'outside', there is only forces, collisions, drifts, ... and that will lead to any position. You can not manipulate the position without putting forces on some related nodes.
And in the moment you try BOTH, having a physicsBody (e.g. on your player), but also try to move them around by setting position manually or running actions for moving, YOU LEAVE the world of physics. Your player will be MOVED through walls and so on, against all physics rules.
So, to want an object being manipulated by the physics engine on the one hand and also to want "positioning" by code is kind a contradiction.
There are - of course ways - to combine both ways (e.g. the mentioned 'workaround' by temporarily removing the physicsBody), but this has also its price and has to be sequentially. You can not have both ways at the very same time.

Resources