iOS SpriteKit - collisions and contacts not working as expected [closed] - sprite-kit

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
Sometimes in my SpriteKit programs, my collisions and contacts (using SKPhysicsBody) don't trigger or work as expected. I think I have set up everything I need, but I am still not getting the right interactions.
Is there some code I can write that will check what will collide with what and what bodies are set up to generate contacts?

To help diagnose these types of problems, I have written a function that can be called from anywhere and which will analyse the current scene and produce a list of what nodes with collide with which others and which collisions my scene will be notified for.
The function is stand-alone and does not need to be told anything about the nodes in the scene.
The function is as follows:
//MARK: - Analyse the collision/contact set up.
func checkPhysics() {
// Create an array of all the nodes with physicsBodies
var physicsNodes = [SKNode]()
//Get all physics bodies
enumerateChildNodesWithName("//.") { node, _ in
if let _ = node.physicsBody {
physicsNodes.append(node)
} else {
print("\(node.name) does not have a physics body so cannot collide or be involved in contacts.")
}
}
//For each node, check it's category against every other node's collion and contctTest bit mask
for node in physicsNodes {
let category = node.physicsBody!.categoryBitMask
// Identify the node by its category if the name is blank
let name = node.name != nil ? node.name : "Category \(category)"
let collisionMask = node.physicsBody!.collisionBitMask
let contactMask = node.physicsBody!.contactTestBitMask
// If all bits of the collisonmask set, just say it collides with everything.
if collisionMask == UInt32.max {
print("\(name) collides with everything")
}
for otherNode in physicsNodes {
if (node != otherNode) && (node.physicsBody?.dynamic == true) {
let otherCategory = otherNode.physicsBody!.categoryBitMask
// Identify the node by its category if the name is blank
let otherName = otherNode.name != nil ? otherNode.name : "Category \(otherCategory)"
// If the collisonmask and category match, they will collide
if ((collisionMask & otherCategory) != 0) && (collisionMask != UInt32.max) {
print("\(name) collides with \(otherName)")
}
// If the contactMAsk and category match, they will contact
if (contactMask & otherCategory) != 0 {print("\(name) notifies when contacting \(otherName)")}
}
}
}
}
You also need to check these 3 things:
the scene must be a SKPhysicsContactDelegate
You must set physicsWorld.contactDelegate = self
You need to implement one of the optional methods of
SKPhysicsContactDelegate:
didBeginContact
didEndcontact
The function should be called once you have set up all of your pics - usually at the end of didMoveToView works:
checkPhysics()
When I call this function from the end of my didMoveToView in my practice Swift project, I get the following output:
Optional("shape_blueSquare") collides with Optional("Category
2147483648") Optional("shape_blueSquare") collides with
Optional("shape_redCircle") Optional("shape_blueSquare") collides with
Optional("shape_purpleSquare") Optional("shape_blueSquare") collides
with Optional("shape_yellowTriangle") Optional("shape_redCircle")
collides with Optional("Category 2147483648")
Optional("shape_redCircle") collides with Optional("shape_blueSquare")
Optional("shape_redCircle") notifies when contacting
Optional("shape_purpleSquare") Optional("shape_redCircle") collides
with Optional("shape_yellowTriangle") Optional("shape_redCircle")
notifies when contacting Optional("shape_yellowTriangle")
Optional("shape_purpleSquare") collides with Optional("Category
2147483648") Optional("shape_purpleSquare") collides with
Optional("shape_yellowTriangle") Optional("shape_yellowTriangle")
collides with everything didBeginContact entered for
Optional("shape_purpleSquare") and Optional("shape_redCircle")
didBeginContact entered for Optional("shape_purpleSquare") and
Optional("shape_redCircle") didBeginContact entered for
Optional("shape_yellowTriangle") and Optional("shape_redCircle")
Category 2147483648 is my edge boundary and it does not have a name. I gave it this category to match its collisionBitMask
Please feel free to try this function and let me know if it's useful or if there are any situations it does not handle.

Related

Swift Value of type 'SKNode' has no member 'particleBirthRate'

I have a scene where meteors (SKSpriteNodes) fall with trails (SKEmitterNodes). Each of these meteors has a unique name that matches the name of its trail.
You can shoot these meteors down, and they are removed upon contact with the bullet. To remove the meteor's accompanying trail, I loop through all existing nodes and delete the one with the same name as the meteor.
This sudden 'disappearance' of the trail looks unnatural, as I want particles that have already been emitted to finish their cycle. Therefore, I wrote the following:
for child in children {
if child.name == meteorite.name {
child.particleBirthRate = 0 //Stops new particles from being created
delay(2) {
child.removeFromParent()
}
}
}
XCode's compiler returns:
Value of type 'SKNode' has no member 'particleBirthRate'
I'm not sure how to work around this, as I know that the node with the same name has to be an SKEmitterNode, but, obviously, the compiler cannot know that, and therefore it returns the error.
You could use a conditional binding. Something like this:
for child in children where child.name == meteorite.name {
if let emitter = child as? SKEmitterNode {
emitter.particleBirthRate = 0 //Stops new particles from being created
delay(2) {
emitter.removeFromParent()
}
}
}
Another option is a for loop with a case pattern:
for case let emitter as SKEmitterNode in children {
if emitter.name == meteorite.name {
emitter.name.particleBirthRate = 0
// ...
}
}
The loop body is only executed for nodes of type SKEmitterNode.
This can be combined with the where constraint:
for case let emitter as SKEmitterNode in children where emitter.name == meteorite.name {
emitter.name.particleBirthRate = 0
// ...
}

best way to detect if node touch the frame

Im building some game with SpriteKit that include balls as SKShapeNode. I create a class that define the balls and their properties (including SKPhysicsBody). the balls should run on the screen, and the frame is the screen border (by using edgeLoopFrom: self.frame). I also created a path node that is located at the top of the screen. now, I want to do that if some ball reach the top border of the frame so some function will execute.
I read some about it and i'm not sure what is the right way to do so, if by using contactBitMask or if there is another and better option.
If the right way is by contactBitMask - do I have to set a struct for the balls node or can I set it inside their class?
thanks!
If I'm getting this right, when a ball hits the path node that is located at the top half of the screen you want a function to be called.
First, I'm not sure if a path node is more efficient than a sprite node, in fact I have never really used path nodes, but here is what you can do.
Spritekit
check out the link above. What you need to do is implement the SKPhysicsContactDelegate. This will allow you to access the functions didBegin() and didEnd(). These functions are called when a contact is made within the physicsworld.
class YourClass: SKScene, SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
}
func didEnd(_ contact: SKPhysicsContact) {
}
}
In order for these functions to be called, you need to set the physicsworld's contactDelegate to the class that will handle the calls. This would be your scene and a good place to set this is the didMove() function.
class YourClass: SKScene, SKPhysicsContactDelegate {
func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
}
Now when a contact happens that is detectable, your didBegin() will be called, and when the contact ends the didEnd() will be called.
Now we need to give our nodes some physicsbodies and we can set the different bitmasks on them in order to detect collisions/contacts. These are the 3 bitmasks we are concerned about:
categoryTestBitMask
collisionTestBitMask
contactTestBitMask
categoryTestBitMask: You can give nodes of similar type a category, example "ball" could be the category. All your different ball objects could have this same category. I use the "noCollision" category for when I want to detect a contact, but I don't want a collision to happen. You are limited to 32 different categories though so don't go crazy with tons of different ones.
collisionTestBitMask: Give your "ball" a category that you want a collision to happen with. Ex: set the collisiontestmask for your "ball" to the category bitmask of "wall". A collision is when 2 objects will physically run into each other; so your ball will bounce off the walls.
contactTestBitMask: a Contact is when 2 nodes overlap. So instead of the ball bouncing off something, it would call the contact method for our delegate. Note that you can set both the collision and contact bit masks to the same thing.
Now how do we set these masks. I use a Struct so that I can assign names to the bitmasks and set these 3 different masks with code. Something like this:
struct Mask {
static var ball: UInt32 = 0b10 //2
static var wall: UInt32 = 0b100 //4
static var pathNode: UInt32 = 0b1000 //8
}
now within code you can set the masks:
let ball = SKSpriteNode()
ball.name = "ball"
ball.physicsBody = SKPhysicsBody()
ball.physicsBody.categoryTestBitMask = Mask.ball
ball.physicsBody.collisionTestBitMask = Mask.wall
ball.physicsBody.contactTestBitMask = Mask.pathNode | Mask.wall
let pathNode = SKSpriteNode()
pathNode.name = "pathNode"
pathNode.physicsBody = SKPhysicsBody()
pathNode.physicsBody.categoryTestBitMask = Mask.pathNode
pathNode.physicsBody.collisionTestBitMask = 0
pathNode.physicsBody.contactTestBitMask = Mask.pathNode
Lets look at what we are saying here. We create a ball object and we set its category to "ball", we say we want it to have collisions with "wall" objects and we want our contact delegate functions to trigger with "pathNode" objects OR "wall" objects. Our pathNode object will have no collisions, and will have contacts with the ball.
Basically the ball will bounce off the walls, and will pass through the pathNode. It will call the contact delegate functions didbegin() and didend() with both the pathNode and wall objects.
Not finished yet... So when the function is called, how do we handle this? When the didbegin or didend function is called, it has a parameter of "contact". this contact param has 2 bodies to work with and these are the bodies that contacted each other. There are multiple ways we can handle this, but I'll just show you a simple way for now.
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA!.node!.name == "ball" {
// bodyA is our ball
switch contact.bodyB!.node!.name {
case "pathNode":
thisIsMyBallHitPathNodeFunction()
case "wall":
thisIsMyBallHitWallFunction()
default:
break
}
}
else if contact.bodyB!.node!.name == "ball" {
// bodyB is our ball
switch contact.bodyA!.node!.name {
case "pathNode":
thisIsMyBallHitPathNodeFunction()
case "wall":
thisIsMyBallHitWallFunction()
default:
break
}
}
}
Update:
What we are doing here is figuring out the type of bodyA and bodyB. So it starts with bodyA, if bodyA is a "ball", then we know that bodyA is a "ball and bodyB is the thing the ball came in contact with. We then use a switch statement to figure out what bodyB is. Once we know what bodyB is, we call the function that we need to call for that specific contact between these 2 nodes.
Then you just put your code into those specified functions of what you want to do.
This could be a lot to take in at once, If you are new I would suggest trying this out and trying to get it to work. After, I would youTube some videos on how to do this. It is a good idea to see how different people handle the same thing and then you can decide for your self on how to do it. This might not be the most elegant way of handling the contacts, but it works well and with some practice it will become second nature, Good luck!

Swift Uint32 Recognizing All Even Numbers as the Same

I am using SpriteKit in Swift and have the following situation:
I construct a struct in the superclass level that has a list of UInt32s to represent the various bitmasks that I need to establish types of physics bodies. (This is stupid because struct properties cannot be overridden in the subclass. Considering removing.)
In the subclass I create a physics body that has collision, contact, and category bitmasks of UInt32(4), referenced by a static constant from the superclass struct.
These bodies fall through the ground that is created in the superclass with bitmasks of UInt(2) from the struct property.
When I change the static constant from UInt(4) to an unsigned int 32 of any odd number between 0 - 32, the bodies do not fall through the ground. Using any even number makes the bodies fall through the ground.
Superclass.swift
struct physicsBitMasks {
static let one = UInt32(1) //for player
static let two = UInt32(2) //for ground
static let three = UInt32(3) //for you don't need to know
static let four = UInt32(4) //for enemy
...
ground.physicsBody?.categoryBitMask = physicsBitMasks.one
ground.physicsBody?.contactTestBitMask = physicsBitMasks.one
ground.physicsBody?.collisionBitMask = physicsBitMasks.two
}
Subclass.swift
Subclass: Superclass {
//create enemy
thing.physicsBody?.categoryBitMask = physicsBitMasks.four
thing.physicsBody?.contactTestBitMask = physicsBitMasks.four
thing.physicsBody?.collisionBitMask = physicsBitMasks.four
}
Enemy falls through ground
REDEFINE
Superclass.swift
struct physicsBitMasks {
static let one = UInt32(1) //for player
static let two = UInt32(2) //for ground
static let three = UInt32(3) //for you don't need to know
static let four = UInt32(5) // or 7, 9, ...31 for enemy
}
Enemy does not fall through ground.
What is going on?
Wouldn't playerMask, groundMask, mysteryMask, and enemyMask be better names than one, two, three, and four?
Also, because they are bit mask components, you need each to have a single unique bit set. But 3, for example, contains the bits of 1 and 2. Let's define them this way:
struct BodyMask {
static let Player = UInt32(1)
static let Ground = UInt32(2)
static let EmbarrassingSecret = UInt32(4)
static let Enemy = UInt32(8)
}
Now you need to set the various masks on your bodies. You should set each's body's categoryBitMask to the mask for its type:
ground.physicsBody?.categoryBitMask = BodyMask.Ground
player.physicsBody?.categoryBitMask = BodyMask.Player
enemies.forEach { $0.physicsBody?.categoryBitMask = BodyMask.Enemy }
twilightNovel.physicsBody?.categoryBitMask = BodyMask.EmbarrassingSecret
You need to set each body's collisionBitMask based on what other bodies you want it to bounce off of. That is, do you want an enemy to bounce off the ground? Then the enemy's collisionBitMask must include the BodyMask.Ground bit. Do you want the ground to bounce off of an enemy? Or bounce off of anything else? Probably not. Note that this is asymmetric! The enemy can bounce of the ground without the ground bouncing. That is, Newton's third law (for every action there is an equal and opposite reaction) doesn't have to apply.
If you want a body to bounce off of multiple other body types, you can bitwise-or the masks together. Let's assume you want enemies to bounce off of the ground and each other.
ground.physicsBody?.collisionBitMask = 0
player.physicsBody?.collisionBitMask = BodyMask.Ground
enemies.forEach { $0.physicsBody?.collisionBitMask = BodyMask.Ground | BodyMask.Enemy }
twilightNovel.physicsBody?.collisionBitMask = BodyMask.Ground
Finally, if you want to be notified when two bodies collide (regardless of whether they bounce), you can use contactTestBitMask. You probably want to know when the player touches an enemy, or when the player does something embarrassing, even though you don't necessarily want the player to bounce off of enemies or trashy novels.
ground.physicsBody?.contactTestBitMask = 0
player.physicsBody?.contactTestBitMask = BodyMask.Enemy | BodyMask.EmbarrassingSecret
enemies.forEach { $0.physicsBody?.contactTestBitMask = 0 }
twilightNovel.physicsBody?.contactTestBitMask = 0

Swift Sprite kit Collision Bitmask

I was wondering if anyone would be able to offer me some help with using 3 nodes, I understand if I just have 2 nodes E.g. body A and body B but I'm struggling with adding a third body, I hoped it was as simple as body C but that wasn't the case.
This is the related code below:
struct CollisionCategoryBitmask {
static let Player: UInt32 = 0x00
static let Enemy1: UInt32 = 0x01
static let Enemy2: UInt32 = 0x02
}
func didBeginContact(contact: SKPhysicsContact) {
var updateHud = false
_ = (contact.bodyA.node != player) ? contact.bodyA.node : contact.bodyB.node
updateHud = slowDown(player)
}
This function below is intended to slow the player down when contacting Enemy1
func slowDown (player: SKNode) -> Bool {
player.physicsBody?.velocity = CGVector(dx: 0, dy: -50.0)
return true
}
and this below is intended to get the game to end when contacting Enemy2
func endGame (player: SKNode) -> Bool {
endOfGame()
return true
}
At the moment both functions work, but only one at a time, so when the player contacts either Enemy 1 or 2. I can currently either get the player to slow down or get the game to end by just changing the
updateHud = slowDown(player) line. So how can I adjust the code to have the two different outcomes happen when the player touches either Enemy 1 or 2.
Thanks
You are not understanding how didBeginContact works. Basically what is happening is when any 2 nodes make a contact, the first thing that the system will do is make sure that the category and contact bit masks line up to make a valid contact. If it does, then didBeginContact gets called. now inside this function, you have the 2 arbitrary nodes that made contact, called bodyA, and bodyB. What you need to do first, is figure out what bodyA and bodyB are. So the first thing you do, is check the categoryBitMask.
if(contact.bodyA.categoryBitMask == HERO) then contact.bodyA is a hero
Now that we know what categories these nodes refer to, we can then check what the node actually is. To do this, you can either check the name of the node
if(contact.bodyA.node.name == "Hero") then bodyA is the hero
Or compare the node itself
if(contact.bodyA.node == heroNode) then bodyA is the hero
Now do the same for bodyB.
You have established at this point what your nodes are, you can proceed with the outcome results that you need based on this information.
If Hero hits both enemy nodes at the same time, then you need to handle this as well. What is going to happen is 2 calls to didBeginContact will happen, you need to find a way to remember this, and do your contact code in either the didEvaluateActions, or didSimulatePhysicsMethod. I do not remember which of these follows didBeginContact, so you will have to test it out on your own.

SpriteKit physics in Swift - Ball slides against wall instead of reflecting

I have been creating my own very simple test game based on Breakout while learning SpriteKit (using iOS Games by Tutorials by Ray Wenderlich et al.) to see if I can apply concepts that I have learned. I have decided to simplify my code by using an .sks file to create the sprite nodes and replacing my manual bounds checking and collision with physics bodies.
However, my ball keeps running parallel to walls/other rectangles (as in, simply sliding up and down them) any time it collides with them at a steep angle. Here is the relevant code--I have moved the physics body properties into code to make them more visible:
import SpriteKit
struct PhysicsCategory {
static let None: UInt32 = 0 // 0
static let Edge: UInt32 = 0b1 // 1
static let Paddle: UInt32 = 0b10 // 2
static let Ball: UInt32 = 0b100 // 4
}
var paddle: SKSpriteNode!
var ball: SKSpriteNode!
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
physicsWorld.gravity = CGVector.zeroVector
let edge = SKNode()
edge.physicsBody = SKPhysicsBody(edgeLoopFromRect: frame)
edge.physicsBody!.usesPreciseCollisionDetection = true
edge.physicsBody!.categoryBitMask = PhysicsCategory.Edge
edge.physicsBody!.friction = 0
edge.physicsBody!.restitution = 1
edge.physicsBody!.angularDamping = 0
edge.physicsBody!.linearDamping = 0
edge.physicsBody!.dynamic = false
addChild(edge)
ball = childNodeWithName("ball") as SKSpriteNode
ball.physicsBody = SKPhysicsBody(rectangleOfSize: ball.size))
ball.physicsBody!.usesPreciseCollisionDetection = true
ball.physicsBody!.categoryBitMask = PhysicsCategory.Ball
ball.physicsBody!.collisionBitMask = PhysicsCategory.Edge | PhysicsCategory.Paddle
ball.physicsBody!.allowsRotation = false
ball.physicsBody!.friction = 0
ball.physicsBody!.restitution = 1
ball.physicsBody!.angularDamping = 0
ball.physicsBody!.linearDamping = 0
physicsWorld.contactDelegate = self
}
Forgot to mention this before, but I added a simple touchesBegan function to debug the bounces - it just adjusts the velocity to point the ball at the touch point:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject() as UITouch
let moveToward = touch.locationInNode(self)
let targetVector = (moveToward - ball.position).normalized() * 300.0
ball.physicsBody!.velocity = CGVector(point: targetVector)
}
The normalized() function just reduces the ball/touch position delta to a unit vector, and there is an override of the minus operator that allows for CGPoint subtraction.
The ball/edge collisions should always reflect the ball at a precisely opposite angle but for some reason the ball really seems to have a thing for right angles. I can of course implement some workaround to reflect the ball's angle manually, but the point is that I want to do this all using the built in physics functionality in SpriteKit. Is there something obvious that I am missing?
This appears to be an issue with collision detection. Most have found solutions by using the didBeginContact and reapplying the force at an opposite direction. Note he says didMoveToView but corrects himself in a later comment to didBeginContact.
See comments at the bottom of the Ray Wenderlich tutorial here
I have a fix for the problem with the ball "riding the rail" if it
strikes at a shallow angle (#aziz76 and #colinf). I added another
category, "BorderCategory" and assigned it to the border PhysicsBody
we create in didMoveToView.
and a similar SO question here explaining why it is happening.
Even if you do that, though, many physics engines (including
SpriteKit's) have trouble with situations like this because of
floating point rounding errors. I've found that when I want a body to
keep a constant speed after a collision, it's best to force it to --
use a didEndContact: or didSimulatePhysics handler to reset the moving
body's velocity so it's going the same speed it was before the
collision (but in the opposite direction).
Also another thing I noticed is you are using a square instead of a circle for your ball and you may want to consider using...
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width/2)
So turns out you aren't crazy which is always good to hear from someone else and hopefully this will help you find a solution that works best for your application.
I came up with a temporary solution that is working surprisingly well. Simply apply a very small impulse opposite of the border. You may need to change the strength based on the masses in your system.
func didBeginContact(contact: SKPhysicsContact) {
let otherNode = contact.bodyA.node == ball.sprite ? contact.bodyB.node : contact.bodyA.node
if let obstacle = otherNode as? Obstacle {
ball.onCollision(obstacle)
}
else if let border = otherNode as? SKSpriteNode {
assert(border.name == "border", "Bad assumption")
let strength = 1.0 * (ball.sprite.position.x < frame.width / 2 ? 1 : -1)
let body = ball.sprite.physicsBody!
body.applyImpulse(CGVector(dx: strength, dy: 0))
}
}
In reality, this should not be necessary, since as described in the question, frictionless, fully elastic collision dictates that the ball should rebound by inverting the x velocity (assuming side borders) no matter how small the collision angle is.
Instead, what is happening in the game is as if sprite kit ignores the X velocity if it is smaller than a certain value, making the ball slide against the wall without rebound.
Final Note
After reading this and this, it's obvious to me that the real answer is for any serious physics game you have, you should be using Box2D instead. You get way too many perks from the migration.
This problem only seems to occur when the velocity is small in either direction. However to reduce the effect it is possible to decrease the speed of the physicsWorld, e.g.,
physicsWorld.speed = 0.1
and then increase the velocity of the physicsBody, e.g.,
let targetVector = (moveToward - ball.position).normalized() * 300.0 * 10
ball.physicsBody!.velocity = CGVector(point: targetVector)
Add code below:
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
border.restitution = 1
self.physicsBody = border
which will make your ball bounce back when it collides with wall.
Restitution is the bounciness of the physics body so setting it to 1 will bounce ball back.
I was seeing exactly the same issue, but the fix for me was not related to the collision detection issues mentioned in the other answers. Turns out I was setting the ball into motion by using an SKAction that repeats forever. I eventually discovered that this conflicts with SpriteKit's physics simulation leading to the node/ball travelling along the wall instead of bouncing off it.
I'm assuming that the repeating SKAction continues to be applied and overrides/conflicts with the physics simulation's auto-adjustment of the the ball's physicsBody.velocity property.
The fix for this was to set the ball into motion by setting the velocity on its physicsBody property. Once I'd done this the ball began bouncing correctly. I'm guessing that manipulating its position via physicsBody by using forces and impulses will also work given that they are a part of the physics simulation.
It took me an embarrassing amount of time to realise this issue, so I'm posting this here in case I can save anyone else some time. Thank you to 0x141e! Your comment put me (and my ball) on the right path.
The problem is twofold in that 1) it will not be solved by altering friction/restitution of the physics bodies and 2) will not be reliably addressed by a return impulse in the renderer() loop due to the contact occurring after the body has already begun decelerating.
Issue 1: Adjusting physics properties has no effect --
Because the angular component of the collision is below some predetermined threshold, the physics engine will not register it as a physical collision and therefore, the bodies will not react per the physics properties you've set. In this case, restitution will not be considered, regardless of the setting.
Issue 2: Applying an impulse force when the collision is detected will not produce consistent results -- This is due to the fact that in order to simulate restitution, one needs the velocity of the object just prior to impact.
-->For instance, if an object hits the floor at -10m/s and you want to simulate 0.8 restitution, you would want that object to be propelled 8m/s in the oppostie direction.
Unfortunately, due to the render loop, the velocity registered when the collision occurs is much lower since the object has already decelerated.
-->For example, in the simulations I was running, a ball hitting a floor at a low angle was arriving at -9m/s, but the velocity registered when the collision was detected was -2m/s.
This is important since in order to create a consistent representation of restitution, we must know the pre-collision velocity in order to arrive at our desired post-collision velocity...you can't ascertain this in the Swift collision callback delegate.
Solution:
Step 1. During the render cycle, record the velocity of the object.
//Prior to the extension define two variables:
var objectNode : SCNNode!
var objectVelocity : SCNVector3!
//Then, in the renderer delegate, capture the velocity of the object
extension GameViewController: SCNSceneRendererDelegate
{
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval)
{
if objectNode != nil {
//Capture the object's velocity here, which will be saved prior to the collision
if objectNode.physicsBody != nil {
objectVelocity = objectNode.physicsBody!.velocity
}
}
}
}
Step 2: Apply a return impulse when the object collides, using the velocity saved prior to the collision. In this example, I am only using the y-component since I am simulating restitution in that axis.
extension GameViewController: SCNPhysicsContactDelegate {
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
let contactNode: SCNNode!
//Bounceback factor is in essence restitution. It is negative signifying the direction of the vector will be opposite the impact
let bounceBackFactor : Float! = -0.8
//This is the slowest impact registered before the restitution will no longer take place
let minYVelocity : Float! = -2.5
// This is the smallest return force that can be applied (optional)
let minBounceBack : Float! = 2.5
if contact.nodeA.name == "YourMovingObjectName" && contact.nodeB.name == "Border" {
//Using the velocity saved during the render loop
let yVel = objectVelocity.y
let vel = contact.nodeA.physicsBody?.velocity
let bounceBack : Float! = yVel * bounceBackFactor
if yVel < minYVelocity
{
// Here, the opposite force is applied (in the y axis in this example)
contact.nodeA.physicsBody?.velocity = SCNVector3(x: vel!.x, y: bounceBack, z: vel!.z)
}
}
if contact.nodeB.name == "YourMovingObjectName" && contact.nodeA.name == "Border" {
//Using the velocity saved during the render loop
let yVel = objectVelocity.y
let vel = contact.nodeB.physicsBody?.velocity
let bounceBack : Float! = yVel * bounceBackFactor
if yVel < minYVelocity
{
// Here, the opposite force is applied (in the y axis in this example)
contact.nodeB.physicsBody?.velocity = SCNVector3(x: vel!.x, y: bounceBack, z: vel!.z)
}
}
}
}