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

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

Related

Different results when using isNode(_:insideFrustumOf:) vs nodesInsideFrustum(of:)

Have a quick question to see if I'm using isNode(_:insideFrustumOf:) correctly.
I'm putting an node (SCNNode) into an ARSCNView with standard configuration. The geometry in the node is off-centered so I used the node's pivot property to adjust its center point.
let c = object.boundingSphere.center
object.pivot = SCNMatrix4MakeTranslation(c.x, c.y, c.z)
object.position = c
My issue arises after I update the object's scale or rotation using a pinch gesture. After this happens, I get different results from isNode(_:insideFrustumOf:) and from nodesInsideFrustum(of:).
The node I'm testing is clearly visible in the screen, but isNode(_:insideFrustumOf:) fails to see it. However, the node is in the [SCNNode] results from nodesInsideFrustum(of:).
My question is whether this is a bug, or is there some other proper way of centering geometry to a node that may fix this issue. For now, I'm going to use the nodesInsideFrustum(of:) function and test if the object is in the array.
Thanks!
The trick is making sure you are using the ”presentation” node for any moving objects, even the pointOfView if it’s moving.
This is a working snippet from my app inside the renderer: (updateAtTime)
if renderer.isNode(stationaryObjectNode, insideFrustrumOf: renderer.pointOfView!.presentation) == true {
//do stuff if seen
}
If your object is not stationary, use movingObject.presentation instead of stationaryObjectNode

Control an object by physics

I read the example in the apple documentation (Scene Kit Vehicle) and they use SCNPhysicsVehicle on the vehicle. SCNPhysicsVehicle allows to set speed, brake and everything. I want to be able to control a SCNNode (containing a SCNSphere). What is the way to do that by physic?
Use SCNPhysicsVehicle only when you want an element in your scene to behave like a wheeled vehicle — to control it in terms of engine speed, braking, and steering, and to display it with wheels that rotate as it moves and pivot as it steers.
For simpler physics-based control of an element in your scene, create an SNCPhysicsBody and attach it to the node for which you want physical behaviors. Then, to set it in motion, apply forces or impulses to it, directly set its velocity, set it up to collide with other bodies, or just let it fall due to the scene's gravity. There's far too many things to do with physics to fit in one answer — read the SCNPhysicsBody Class Reference to see them all.
If you're looking specifically for joystick/tilt control, even then there are multiple ways to go, depending on what kind of gameplay "feel" you're looking for. But there are some common themes:
Since you want continuous control, you probably want to poll for input in renderer:didUpdateAtTime:or one of the other render-loop methods.
Tilt and joysticks provide variable input, so you probably want variable control. Whatever you do to the physics body should scale with the accelerometer input or joystick axis value.
One thing you could do is apply a force on every frame based on the joystick direction; e.g. in your update method:
GCControllerAxisInput *joystickX = controllers[0].extendedGamepad.xAxis;
[sphereNode.physicsBody applyForce:SCNVector3(xAxis.value * SCALE_FACTOR, 0, 0) impulse:NO];
With this option, holding the joystick one direction or the other is like firing thrusters: your sphere will move faster and faster the longer (and stronger) you hold the joystick in a particular direction. And you'll have to hold the joystick just as much in the opposite direction to apply enough opposing force to stop that crazy thing.
Another way to do it would be to set velocity directly:
sphereNode.physicsBody.velocity = SCNVector3(xAxis.value * SCALE_FACTOR, 0, 0);
With this option, the sphere holds still when you're not pushing the stick, and it moves faster the farther you push the stick, up to the maximum speed of SCALE_FACTOR.
(In both examples, SCALE_FACTOR is something that translates the -1 to 1 range of the joystick into units meaningful to your game.)
There are loads of other options — for example, you could do your own math to derive a delta between the joystick position and the current direction/velocity — experiment with some to find out what best fits the gameplay you're looking for.

Controlling movement of flying a vehicle with SceneKit

I currently have a scene which contains a central node at the root of the scene with earth-like geometry and a node representing a flying vehicle.
However I cannot find the right approach to control the movement of the vehicle. I need the ability to turn left and right while orbiting at a static altitude and speed.
I have tried many combinations of animations and physics body forces all leading to undesirable results.
The closest I've come is:
Setting the pivot property of the vehicle to the centre of the scene
Then setting an Action like below to control moving forward
[_vehicleNode runAction:[SCNAction repeatActionForever:[SCNAction rotateByX:-1 y:0 z:0 duration:10.0]]];
Then finally applying forces for turning left and right with
[_vehicleNode.physicsBody applyTorque:SCNVector4Make(0, 1, 0, 1) impulse:YES];
However I cannot seem to set the pivot and/or position to the right value to get the desired result.
Edit: It appears as the above method would be the solution I'm looking for, however for some reason when I add geometry to the vehicle node, it's position in the scene graph gets changed dramatically. When I add hardcoded buttons to change it's position to where it belongs it appears correct for only that single frame then straight back to being in the middle of nowhere.
Edit 2: After replacing all geometry with a primitive sphere for testing the node is now rotating as intended but is now unaffected by physics forces appearing to ignore it's declaration as a dynamicBody.
If I understand what you are trying to achieve correctly, you can try this:
add a first node to your scene, positioned at (0,0,0) and make it rotate forever along the Y axis using an SCNAction
add your ship node as a child of the first node. Position it at (X,0,0) so that it orbits around the earth
rotate your ship node along the X axis with an action or an animation

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.

Create one or multiple gravitational points in sprite-kit scene

As this question, I want to add some gravity in my sprite-kit scene. But the difference is my scene objects should be attracted to a point (at the middle of the scene). Or to multiple points.
I'm pretty sure I found something like that on the web, but I can't find it again.
I there a common way to acheive this, or should I calculate my own forces in the update: method?
Thanks
I believe all you have to do is add gravity field nodes, and then turn OFF the gravity of the physicsWord so that your objects don't fall "down". You might have to set up some invisible borders to prevent the objects from orbiting the gravity field nodes, or possibly set a really friction.
You can also set the field strength of the gravity field nodes, which may save you from having to calculate the forces manually.
To turn off gravity (swift notation):
physicsWorld.gravity = CGVectorMake(0, 0)

Resources