SpriteKit - Bouncing node constant speed/velocity? - swift

Simple setup - I have bouncing ball nodes (blue) and static ball nodes (green). The grey box has Physics body SKPhysicsBody(edgeLoopFrom: box.frame), so it limits the bouncing ball from moving outside. I set the bouncing balls node (blue) velocity to a random CGVector upon creation and it starts moving. It bounces off the static ball node (green), and off the edges of the grey box.
My general SKPhysicsBody setup with all nodes is:
node.physicsBody?.restitution = 1
node.physicsBody?.friction = 0.0
node.physicsBody?.linearDamping = 0.0
node.physicsBody?.angularDamping = 0.0
My question is: How do I make sure the bouncing ball does move at a constant speed? After a couple of bounces, the node is either speeding up, or slowing down.

In order to achieve this, I normalized the vector whenever the physics engine detects a collision. So in func didBegin(_ contact: SKPhysicsContact), the following happens:
let length = hypotf(Float(node.velocity.dx), Float(node.velocity.dy))
let multiplier = (290.0 / length)
firstBody.velocity.dx *= CGFloat(multiplier)
firstBody.velocity.dy *= CGFloat(multiplier)
290.0 is an arbitrary value that determines the speed of ball.
For reference, check this answer.

Related

Swift 3 (SpriteKit): Locking the x axis of a SKSpriteNode and its physicsBody

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.

SKNode Changing Direction on Flat Surface Unexpectedly in Swift 2

I am trying to make a SpriteKit game in swift similar to Mario Brothers or Pitfall. In this game, there are zoo keepers that move back and forth in the horizontal direction, i.e. along the x-coordinate axis. The zoo keeper is walking on a base referred to as platform nodes. When the zoo keeper collides with a platform in front of them, for example a crate or a zoo cage, the zoo keeper turn and walk the other way. The zoo keeper is expected to move over the ground. The game uses the built in physics properties. The zoo keeper has a restitution of 1.0. I start the zoo keeper with a small impulse. In the update function, I set the velocity to zero in the y direction and set the x velocity to the current zoo keeper x velocity such that the zoo keeper doesn't bounce in the y direction and continues in whatever x velocity/direction they were moving. I set the y velocity to zero such that if the zoo keeper walk off a higher level platform, I don't don't want them to bounce in the y direction. The zoo keeper starts off in his daily rounds and moves to the right across 8 ground platform units, bounces off of a crate, turns around and starts walking the other direction. Although, on the way back the after walking over 3 ground platform units, the zoo keeper unexpectedly collides with the ground platform that he previously already walked over and turns around. Each time I re-run the game in xCode, the collision may occur in different places although once running, the collision occurs consistently at the same place. If I give the zoo keeper a lot more initial impulse, the issue goes away although then my zoo keeper moves way too fast. The ground nodes are all the same size and placed with a for loop at the same y position. Here is an example of the zoo keeper (CharacterNode) walking (contacting) ground a node just prior to turning around and contacting a ground node (PlatformNode) that turns the node around, but shouldn't.
Node that is walked over (Before):
Printing description of contact._bodyA:
type: representedObject:[ name:'Platform' position:{974.613037109375, 24.98663330078125} scale:{1.00, 1.00} accumulatedFrame:{{962.11297607421875, 12.487000465393066}, {25, 25}}]
Printing description of contact._bodyB:
type: representedObject:[ name:'ZooKeeper' position:{1000.2339477539062, 62.757087707519531} scale:{1.00, 1.00} accumulatedFrame:{{987.7340087890625, 37.756999969482422}, {25, 50}}]
Ground node that causes the unexpected blocking:
Printing description of contact._bodyA:
type: representedObject:[ name:'Platform' position:{774.5714111328125, 24.986606597900391} scale:{1.00, 1.00} accumulatedFrame:{{762.07098388671875, 12.487000465393066}, {25, 25}}]
Printing description of contact._bodyB:
type: representedObject:[ name:'ZooKeeper' position:{749.27301025390625, 62.757034301757812} scale:{1.00, 1.00} accumulatedFrame:{{736.77301025390625, 37.756999969482422}, {25, 50}}]
Here is the code that I believe is relevant. I create the platforms and zoo keepers by reading a file of integer x, y coordinates from excel files, scale the objects and place them in sknodes with skspritenodes. The "pattern" for a platform could be 1 ground block, two, three, ten, a stair pattern, etc. The ground pattern is 10 consecutive boxes. All platforms are exactly the same size (25x25). Although if you look a the print description, precision appears to be slightly off.
Platform logic: The pattern for a ground node is a set of 10 single ground units placed side by side, i.e., same y coordinate and spaced by 1 unit in the x direction.
let kScenePositionScale: CGFloat = 25 // scale all scene positions read from a file by this factor
let kPlatformSizeScale:CGFloat = 0.5
let kCharacterSizeScale:CGFloat = 0.25
for platform in platformArray {
for patternItem in platform.pattern.patternItemArray {
let positionX = platform.xCoordinate + patternItem.xCoordinate
let positionY = platform.yCoordinate + patternItem.yCoordinate
let platformNode = SceneObject.createPlatformAtPosition(CGPoint(x: positionX, y: positionY), ofType: PlatformType(rawValue: patternItem.imageName)!)
foregroundNode.addChild(platformNode)
}
}
class func createPlatformAtPosition(position: CGPoint, ofType type: PlatformType) -> PlatformNode {
let node = PlatformNode()
node.position = position
node.name = kPlatformNodeName
node.platformType = type
var sprite: SKSpriteNode
sprite = SKSpriteNode(imageNamed: type.rawValue)
sprite.setScale(kPlatformSizeScale)
node.addChild(sprite)
node.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
node.physicsBody?.dynamic = false
node.physicsBody?.categoryBitMask = PhysicsCategoryBitmask.Platform
node.physicsBody?.collisionBitMask = 0
return node
}
Zoo Keeper: I only use the contact bitmask to be able to print the contact causing the unexpected turn around feature. The pattern is irrelevant at this point for the zoo keeper. The pattern is at 0,0
// for each item in the character array
for character in characterArray {
for patternItem in character.pattern.patternItemArray {
let positionX = character.xCoordinate + patternItem.xCoordinate
let positionY = character.yCoordinate + patternItem.yCoordinate
let characterNode = Character.createCharacterAtPosition(CGPoint(x: positionX, y: positionY), ofType: CharacterType(rawValue: patternItem.imageName)!, ofMotionType: character.motionType)
foregroundNode.addChild(characterNode)
characterNode.physicsBody?.applyImpulse(CGVectorMake(1.0, 0.0))
}
}
class func createCharacterAtPosition(position: CGPoint, ofType type: CharacterType, ofMotionType motionType: CharacterMotionType) -> CharacterNode {
let node = CharacterNode()
node.position = position
node.name = type.rawValue // named after the character type
node.characterMotionType = motionType
node.textureArray = loadPrimaryAtlas(type.rawValue)
var sprite: SKSpriteNode
sprite = SKSpriteNode(texture: node.textureArray[0])
// anchor at the feet
sprite.setScale(kCharacterSizeScale)
node.sprite = sprite
node.addChild(sprite)
node.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.frame.size)
node.physicsBody?.dynamic = true
node.physicsBody?.allowsRotation = false
node.physicsBody?.restitution = 1.0 // bounciness
node.physicsBody?.friction = 0.0 // keep the character moving if needed
node.physicsBody?.angularDamping = 0.0
node.physicsBody?.linearDamping = 0.0
node.physicsBody?.categoryBitMask = PhysicsCategoryBitmask.Character
node.physicsBody?.collisionBitMask = PhysicsCategoryBitmask.Platform //Collision will occur when hero hits land
node.physicsBody?.contactTestBitMask = PhysicsCategoryBitmask.Platform //Contact will be detected when make a contact with land and fruit
return node
}

How to track node movement after contact in SpriteKit

I have a ball node and a hole node. I want to detect when will the ball fall into the hold.
Currently I'm using didBeginContact, but it only gives me the distance of two nodes at the begin of contact, in which case the ball won't necessarily fall into the hole (it falls when distance between two centers < hole's radius).
Is there a way I can track the position of the ball after the contact had happened?
You can add invisible SKSpriteNodes and track when the ball touches them. You would have node1 just in top of the hole (a zone that the ball MUST go through - or touch - before entering the hole) before going into the hole, and node2 just after the hole (again, a zone that the ball MUST go through - or touch - after entering the hole).
If the ball touches node1 but not node2, you know it was almost going into the hole but then it didn't. If the ball touches node2, you know the ball is trying to go through the hole from the bottom part.
I guess you can make a physics body of a hole a lot smaller. That way, when contact occurs, you will be sure that ball can fall into a hole. Take a look at this picture (second example):
On top of that as an additional idea, when contact is detected, you can let ball to move little more, and then scale it down to make impression of falling.
There is no need to test for a contact between the ball and the hole, since it doesn't tell you if the objective was completed. Alternatively, you can simply add a check in didSimulatePhysics to see if the ball is sufficiently close to the hole before starting the falling animation sequence. For example,
override func didSimulatePhysics() {
let dx = ball.position.x - hole.position.x
let dy = ball.position.y - hole.position.y
let distance = sqrt(dx*dx+dy*dy)
if (distance < hole.size.width / 2.0) {
// Start falling animation sequence
}
}
Optionally, you can test if the ball is moving fast enough to skip over the hole by
override func didSimulatePhysics() {
let dx = ball.position.x - hole.position.x
let dy = ball.position.y - hole.position.y
let distance = sqrt(dx*dx+dy*dy)
if (distance < hole.size.width / 2.0) {
// Check if the ball is moving fast
let vx = ball.physicsBody!.velocity.dx
let vy = ball.physicsBody!.velocity.dy
let speed = sqrt(vx*vx+vy*vy)
if (speed < minSpeedToSkipOverHole) {
// Start falling animation sequence
}
}
}
This problem is solved by this: I maintain a possible_falling_list of all contacted holes for each ball and use another timer to frequently check if a ball is falling into any hole. Since the list is small for each ball, the performance is not an issue.

How do I make Character(SCNNode) bounce off of a wall in Scenekit?

I am trying to make a simple maze game in Scenekit. I have a sphere node and wall nodes. The sphere moves by SCNActions. The actions I use on the sphere are moveByX actions. My problem is that whenever the sphere hits a wall, it just moves the wall back with it. My sphere is kinematic and my walls are dynamic. I don't have any forces in the scene, only my moveByX actions. How can I make it so my sphere bounces off of the walls?
I am using Swift.
Change walls to .static and sphere to .dynamic
One way you could do it would be to have variables for xSpeed and ySpeed, and whenever you collide with a wall make the corresponding variable negative. This is a pretty basic approach and doesn't take diagonal walls into account.
For example:
var xSpeed = 5.0 var ySpeed = 5.0
if collision with wall in x direction { xSpeed = -xSpeed }
if collision with wall in y direction { ySpeed = -ySpeed }
It might be a bit tricky to detect the collisions, you might have to look up some tutorials on SpriteKit collisions.
For Swift3
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
border.restitution = 1
self.physicsBody = border

Giving a physicsBody a constant force

I have a ball bouncing up and down. I've accomplished this by setting it's physics body up like this:
linearDamping = 0
friction = 0
restitution = 1
And then just applyForce(CGVectorMake(0, 10)) in the setup call. The ball is now bouncing up and down, up and down with not interruption.
Now I want to add player control. The player can move the ball sideways. I could do this by just setting the balls velocity when the controls are pressed. BUT by doing that I lose all the stuff the physics engine gives me.
Here's a short snippet of code I have right now:
override func update(currentTime: CFTimeInterval) {
/* Apply damping only in x */
if !movingLeft && !movingRight {
let dx = ball!.physicsBody!.velocity.dx * xAlpha
let dy = ball!.physicsBody!.velocity.dy
ball!.physicsBody?.velocity = CGVectorMake(dx, dy)
}
if movingRight {
ball!.physicsBody?.applyImpulse(CGVectorMake(ballVelocityX, 0))
}
if movingLeft {
ball!.physicsBody?.applyImpulse(CGVectorMake(-ballVelocityX, 0))
}
}
The problem, as you might imagine, is that applying impulse upon impulse on each frame like this makes the ball go superfast, which is not what I want, when the controls are held down. But if I apply the force only on keyDown the ball will stop moving in that direction once the ball hits something. And also the keyDown event works wonky on OS X (because of key repeat settings).
So what I want to do is apply an impulse in the update function in a way that makes the force of the ball not exceed my limit but also not go away completely if the ball hits something.