If an SKSpriteNode has had an impulse applied to its SKPhysicsBody so that it now has momentum, is there a way I can get it to immediately stop moving? Applying an equal and opposite impulse would probably work in theory, but I would like to know if there is a simpler way. Thanks (:
You can set its velocity to zero:
body.velocity = CGVectorMake(0, 0)
You might also want to set its angular velocity to zero:
body.angularVelocity = 0
And if you want it to become immune to forces and impulses, turn off dynamic:
body.dynamic = false
Related
This's my first project using SpriteKit.
I'm using these nodes
let physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
physicsBody.isAffectedByGravity = true
physicsBody.mass = 1
geometryNode.physicsBody = physicsBody
let force = SCNVector3(0, 9.8, 0)
let position = SCNVector3(0, 0, 0)
geometryNode.physicsBody?.applyForce(force, at: position, asImpulse: false)
scnScene.rootNode.addChildNode(geometryNode)
My goal is to see the object stuck in the centre of my scene.
As read within SceneKit's documentation, SceneKit uses SI so Mass 1 means 1 kg.
Gravity force applied to the mass centre of this object is -9.8N ( 1kg * 9.8 m/s^2 ) on the Y-axis.
Applying 9.8N to the mass centre should bring the resultant force equal to 0 so no one force is applied and the object should be stuck in the centre but in my project, it falls down.
Where am I wrong?
It looks to me like you apply the force when you create the node.
From the developer docs on applyForce:
Discussion This method accelerates the body without imparting any
angular acceleration to it. The acceleration is applied for a single
simulation step (one frame).
I think you need to move your applyForce call to your update method in the scene. so that the force is constantly applied.
I really need to know how to lock the x axis of an SKSpriteNode and its physicsBody. I need to keep the SKSpriteNode dynamic and affectedByGravity. The node is on a slope, so this is why it's x axis is moved due to gravity. However, I don't want the x axis of this SKSpriteNode to move due to gravity. Is there a way to lock the x axis in order to achieve this?
Thanks for any help :D
Edit: I have tried to apply a constraint to the x value like this:
let xConstraint = SKConstraint.positionX(SKRange(constantValue: 195))
node.constraints?.append(xConstraint)
However this doesn't work and I'm not sure why and how to fix it. Anyone know of a solution?
Edit 2: SKPhysicsJointPin is actually looking more promising. In the comments of the first response/answer to this question, I have been trying to figure how to properly use it in my situation.
An example of my code:
let node = SKSpriteNode(imageNamed: "node")
enum collisionType: UInt32 {
case node = 1
case ground = 2
case other = 4 //the other node is unrelated to the ground and node
}
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMove(to view: SKView) {
//Setup node physicsBody
node.physicsBody = SKPhysicsBody(rectangleOf: node.size)
node.physicsBody?.categoryBitMask = collisionType.node.rawValue
node.physicsBody?.collisionBitMask = //[other node that isn't the ground or the node]
node.physicsBody?.contactTestBitMask = //[other node that isn't the ground or the node]
node.physicsBody?.isDynamic = true
node.physicsBody?.affectedByGravity = true
addChild(node)
//Physics Setup
physicsWorld.contactDelegate = self
}
The node is on top of the ground, and the ground is composed of various SKSpriteNode lines that have a volumeBased physicsBody. The ground keeps adding new lines at the front, and removing the ones at the back, and changing the x value of each line by a negative (so the ground appears to moving - this process is performed in an SKAction). These lines (the parts of the ground) are on an angle which is why the node's x axis moves. I want the node to always be at the front of the ground (e.g. always on top of the newly created line). Currently, setting the position of the node like this locks the x axis (solving my issue):
override func didSimulatePhysics() {
//Manage node position
node.position.x = 195
node.position.y = CGFloat([yPosition of the first line of the ground - the yPosition keeps changing])
}
Note: ^This^ function is inside the GameScene class
The x axis actually stays the same like this. However, the issue is that now the physicsBody of the node is lower than the centre of the node (which didn't happen before).
A node's constraints property is nil by default. You'll need to create an array of one or more constraints and assign it to the property. For example
let xConstraint = SKConstraint.positionX(SKRange(constantValue: 195))
node.constraints = [xConstraint]
Update
You may want to use a camera node instead of moving the ground in the scene. With a camera node, you move the main character and the camera instead of the ground.
I think you could set the linearDamping property to 0.0
The linearDamping is a property that reduces the body’s linear velocity.
This property is used to simulate fluid or air friction forces on the
body. The property must be a value between 0.0 and 1.0. The default
value is 0.1. If the value is 0.0, no linear damping is applied to the
object.
You should pay attention also to the other forces applied to your SKSpriteNode. The gravitational force applied by the physics world for example where dx value, as you request , should be setted to 0.0:
CGVector(dx:0.0, dy:-4.9)
Remember also that when you apply other forces vectors like velocity you should maintain the dx property to 0.0 as constant if you want to block the x axis.
You could find more details to the official docs
Update (after your details to the comments below):
You could also anchored your sprite to the ground with an SKPhysicsJoint (I don't know your project so this is only for example):
var myJoint = SKPhysicsJointPin.joint(withBodyA: yourSprite.physicsBody!, bodyB: yourGround.physicsBody!, anchor: CGPoint(x: yourSprite.frame.minX, y: yourGround.frame.minY))
self.physicsWorld.add(myJoint)
You can work with the anchor property to create a good joint as you wish or adding more joints.
I have a plank, which undergoes constant vertical velocity changes. Therefore setting it's dynamic property to false will not do since physicsBody.velocity will not work.
plank.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(plankWidth, plankHeight))
plank.physicsBody?.affectedByGravity = false
plank.physicsBody?.linearDamping = 0
I also have a ball that rests on the plank:
ball.physicsBody = SKPhysicsBody(circleOfRadius: frame.width*CGFloat(1/Float(NumberOfPlanks+1))*0.2)
ball.physicsBody.affectedByGravity = true
ball.physicsBody.linearDamping = false
This ball must be affected by gravity, rest on the plank, but not affect it's motion. This ball must also be able to undergo applyForce and applyImpulse.
Is there a simple way of accomplishing my goal? Thanks :)
How can I stop a sprite after it has been applied an impulse like this:
player.physicsBody!.applyImpulse(CGVectorMake(50, 0))
And is it possible to make the movement decrease over a time period? (2 seconds)
In order to stop the movement of the physicsBody, you can utilise the 'velocity' variable like so:
//this will reset the x, y based velocity to a halt/stop
player.physicsBody?.velocity = CGVectorMake(0, 0)
//if you would also like to stop any rotation that may be present
player.physicsBody?.angularVelocity = 0
To address your second question you should look into 'linearDamping' to affect velocity and 'angularDamping' to affect angularVelocity (rotation). These physicsBody parameters allow you to slow the velocity over time once an impulse is applied (similar to friction).
//These values should be set when creating the physicsBody.
//should experiment with these values to get the desired effect.
player.physicsBody?.linearDamping = 1.10
player.physicsBody?.angularDamping = 0.25
I have two phyicsbodies in my spritekit game which are colliding and I am having a little difficulty getting them to stop.
I am posting the physics code for them below.
Problem is when the swarm touches my coins it pushes it, not exactly sure why since as far as my know my enemy is told to only contact with the player and my coin is told to only contact with the wall and the player (correct me if im wrong)
(P.S I commented out the collisionBitMask for the coin because when I dont my coins fall thru my walls )
Thanks
1St sprite
enemy = [SKSpriteNode spriteNodeWithImageNamed:#"Swarm"];
enemy.physicsBody =
[SKPhysicsBody bodyWithRectangleOfSize:enemy.size];
enemy.physicsBody.dynamic=NO;
enemy.name=#"Eagle";
enemy.physicsBody.categoryBitMask = PCFallersCategory;
// enemy.physicsBody.collisionBitMask =
// PCPlayerCategory;
enemy.physicsBody.contactTestBitMask = PCPlayerCategory;
enemy.physicsBody.restitution=0;
enemy.physicsBody.friction=0;
2nd Sprite.
self.name = #"coin";
CGFloat minDiam = MIN(self.size.width, self.size.height);
minDiam = MAX(minDiam-8, 8);
self.physicsBody =
[SKPhysicsBody bodyWithCircleOfRadius:minDiam/2.0];
self.physicsBody.dynamic=YES;
self.physicsBody.restitution =0;
self.physicsBody.friction = 0;
self.physicsBody.linearDamping = 0;
self.physicsBody.categoryBitMask = PCCollectableCategotry;
//self.physicsBody.collisionBitMask =PCPlayerCategory;
self.physicsBody.contactTestBitMask = PCPlayerCategory|PCWallCategory;
You need to explicitly set the collisionBitMask to 0.
enemy.physicsBody.collisionBitMask = 0;
According to the documentation
When two physics bodies contact each other, a collision may occur.
This body’s collision mask is compared to the other body’s category
mask by performing a logical AND operation. If the result is a
non-zero value, then this body is affected by the collision. Each body
independently chooses whether it wants to be affected by the other
body. For example, you might use this to avoid collision calculations
that would make negligible changes to a body’s velocity.
The default value is 0xFFFFFFFF (all bits set).
This means that by default, an SKPhysicsBody is configured to collide with all objects.