Change position of a SKSpriteNode that has a physicsBody - ios

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.

Related

How to tell if a node is touching the ground in SceneKit

I am trying to figure out a way to have a property called isJumping on a SCNNode subclass. The property is set to true whenever the nodes jump() function is called and the jump is implemented using .applyForce(x: 0, y: jumpForce, z: 0, asImpulse: true). However, there is no way (that I can figure out) that allows the node to set the isJumping property back to false. My thinking is that if there was a way to tell if the node was touching the ground or not, then this would be an ideal solution. Does anyone have any ideas on how this isJumping property could be implemented? Or perhaps a way to compute the isJumping property using physics?
Things I've tried:
Checking vertical velocity != 0
This doesn't work because at the peak of the jump the vertical velocity is 0. In addition, when the node is rotated to "stand up" and "lie down" using SCNActions, even though the pivot point is the nodes "foot" and never really leaves the floor, it is still considered to have a vertical velocity and thus is incorrectly labelled as in a jumping state.
Checking position
This is unreliable because if the node were to hypothetically jump onto a platform, the node would be considered in a jumping state.
Something that may work:
Applying a downward force on the physics body and check in the next frame if the y-position changed at all. If it didn't change that means it rebounded off of something and in that instance I can change the isJumping property to false. Keep in mind, ideally, I wan't to be able to do this without leaving the subclass (however, using helper classes is completely fine).

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

How to make a specific object defy gravity? (SpriteKit / Objective-C)

I'm working on a piece of code, and I want there to be gravity in the view but I want specific projectiles to defy gravity and fly across the screen. I know to get rid of the gravity on the whole view its just:
self.physicsWorld.gravity = CGVectorMake(0, 0);
But as stated I want gravity on the scene.
So I'm wondering if there is a way to take the gravity off one specific item? (i.e. the SKSpriteNode _debris item in my case)
By setting the physicsBody to not be affected by gravity.
E.g.
myNoGravityObject.physicsBody.affectedByGravity = NO;
See the documentation of SKPhysicsBody.
If you don't want a node to interact with physics calculations at all, but still want it to have a physicsBody (i.e. to make it begin to interact with things later on, or to check for collisions with other nodes), you can set
node.physicsBody.dynamic = NO;
This will cause the node to ignore gravity, as well as collisions, impulses, and the like. If you are setting up the contact delegate, note that at least one node in any given contact must be dynamic for the contact delegate to be notified of the event.

Emitter not rotating with parent node

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

SpriteKit: rotation without friction

Is there a way to make a sprite rotate after contact with other sprites but without having frictional forces applied to it?
If i set
sprite.physicsBody.friction = 0.0;
sprite.physicsBody.allowsRotation = YES;
no rotation occurs.
If you want a body to rotate upon sliding contact with other bodies, some friction is necessary. (Just like in real-world physics!) Do you want friction to cause rotation, but exhibit frictionless behavior once it's rotating? If so, rereading the previous sentence should give a clue to the answer: you need to change the friction coefficient after the body starts rotating. Setting a contact delegate gives you an opportunity to make this change. It's also good for just about any other case where you want to "fudge" the results of a collision after it occurs, such as re-setting the body's velocity to a predetermined value.
If you want to manage the rotation by yourself, you can detect the collision with the delegate and the apply angular impulse like this
[sprite.physicsBody applyAngularImpulse:0.05];
Maybe I'm not getting the point but what you seem to want to do is really just:
sprite.zRotation = 1.0; // Note: zRotation is in radians
Or use SKAction rotateToAngle:duration: if you want the rotation to occur over a period of time.

Resources