Child node not keeping same physicsBody when added to parent - sprite-kit

Using the Xcode SpriteKit Scene editor I have a few objects added to the scene as "obstacles." I like to animate them across the screen. They are not dynamic. To make things easier I thought to add them to an obstacleParent node and just animate that one object. The problem is that adding the obstacles to a parent totally screws up their physics bodies. Meaning the obstacle physics bodies do not match the outline of the sprite.

I'm not sure if it is a bug in the Scene editor, but adding the physics bodies programmatically yields results as expected.
Some interesting things worth noting are:
the physicsBodies moved with the parent regardless of if they were not Dynamic or not.
I could not get them to overlap each other until I made them all have the same contactTestBitMask
It is difficult to tell in the screen shot but all 3 children kept their physics shapes
If the parent has a physics body and an physics impulse is applied to it, only the parent moves. However setting the position of the parent programmatically moves the parent and child nodes
testObject = SKSpriteNode(texture: nil, color: .greenColor(), size: CGSize(width: 100, height: 100))
testObject.zPosition = Layer.Controls.rawValue
testObject.position = CGPoint(x: self.size.width / 2, y: self.size.height / 2)
// testObject.physicsBody = SKPhysicsBody(circleOfRadius: testObject.width / 2)
// testObject.physicsBody!.dynamic = true
// testObject.physicsBody!.affectedByGravity = false
// testObject.physicsBody!.allowsRotation = true
addChild(testObject)
let testerObject = SKSpriteNode(texture: nil, color: .clearColor(), size: CGSize(width: 100, height: 100))
testerObject.zPosition = Layer.Controls.rawValue
testerObject.position = CGPoint(x:50, y: 50)
testerObject.physicsBody = SKPhysicsBody(circleOfRadius: testerObject.width / 2)
testerObject.physicsBody!.affectedByGravity = false
testerObject.physicsBody!.dynamic = true
testerObject.physicsBody!.allowsRotation = true
testerObject.physicsBody!.collisionBitMask = 0
testerObject.physicsBody!.contactTestBitMask = 0
testObject.addChild(testerObject)
let testerObject2 = SKSpriteNode(texture: nil, color: .clearColor(), size: CGSize(width: 100, height: 100))
testerObject2.zPosition = Layer.Controls.rawValue
testerObject2.position = CGPoint(x:-50, y: -50)
testerObject2.physicsBody = SKPhysicsBody(circleOfRadius: testerObject.width / 2)
testerObject2.physicsBody!.affectedByGravity = false
testerObject2.physicsBody!.dynamic = true
testerObject2.physicsBody!.allowsRotation = true
testerObject2.physicsBody!.collisionBitMask = 0
testerObject2.physicsBody!.contactTestBitMask = 0
testObject.addChild(testerObject2)
let testerObject3 = SKSpriteNode(texture: nil, color: .clearColor(), size: CGSize(width: 100, height: 100))
testerObject3.zPosition = Layer.Controls.rawValue
testerObject3.physicsBody = SKPhysicsBody(circleOfRadius: testerObject.width / 2)
testerObject3.physicsBody!.affectedByGravity = false
testerObject3.physicsBody!.dynamic = true
testerObject3.physicsBody!.allowsRotation = true
testerObject3.physicsBody!.collisionBitMask = 0
testerObject3.physicsBody!.contactTestBitMask = 0
testObject.addChild(testerObject3)

Related

SKSpriteNodes Colliding and sticking on top each other

My SKSpritekit nodes are stacking on top each other and sticking to each other. I have added SKSprite nodes via the sks file and programmatically.
let texture = SKTexture(image: #imageLiteral(resourceName: "cone"))
trCone = SKSpriteNode(texture: texture, normalMap: texture)
//trCone?.texture = SKTexture(image: #imageLiteral(resourceName: "cone"))
trCone?.size = CGSize(width: 60, height: 65)
trCone?.position = CGPoint(x: 153, y: 267)
trCone?.zPosition = 15
trCone?.name = trainingStuff.text
trCone?.physicsBody = SKPhysicsBody(texture: SKTexture(image: #imageLiteral(resourceName: "cone")), size: CGSize(width: 60, height: 65))
trCone?.physicsBody?.allowsRotation = false
trCone?.physicsBody?.isDynamic = false
trCone?.physicsBody?.categoryBitMask = .zero
trCone?.physicsBody?.collisionBitMask = .zero
myTrainigArr?.append(trCone!)
self.myScene!.addChild(trCone!)
This is how I add the nodes. I would like them to overlap each other and pass through each other without any collisions but this is the outcome. When I try to move the nodes after they collide one is now stuck on top of the other. Any suggestions will be appreciated.

SpriteKit node going through wall

I'm working on an Xcode Playground and I'm having some problems with SpriteKit collisions.
The first box has a mass of 1 and the second box has a mass of 100000 (when the latter is 100, for example, everything works fine). The collisions are elastic (restitution was set to 1) and there is no friction or damping at all.
Here is the code for my scene (ColliderType is just an enum for the categories):
class GameScene: SKScene {
private var wall: SKSpriteNode!
private var floor: SKSpriteNode!
private var box1: SKSpriteNode!
private var box2: SKSpriteNode!
override func didMove(to view: SKView) {
self.backgroundColor = .white
wall = SKSpriteNode(color: SKColor.black, size: CGSize(width: 10, height: self.frame.height))
wall.position = CGPoint(x: 100, y: self.frame.height/2)
wall.physicsBody = SKPhysicsBody(rectangleOf: wall.frame.size)
wall.physicsBody?.isDynamic = false
floor = SKSpriteNode(color: SKColor.black, size: CGSize(width: self.frame.width, height: 10))
floor.position = CGPoint(x: self.frame.width/2, y: 100)
floor.physicsBody = SKPhysicsBody(rectangleOf: floor.frame.size)
floor.physicsBody?.isDynamic = false
box1 = SKSpriteNode(color: SKColor.black, size: CGSize(width: 100, height: 100))
box1.position = CGPoint(x: 300, y: floor.position.y+box1.size.height/2)
box1.physicsBody = SKPhysicsBody(circleOfRadius: box1.frame.size.width/2)
box2 = SKSpriteNode(color: SKColor.black, size: CGSize(width: 100, height: 100))
box2.position = CGPoint(x: 750, y: floor.position.y+box2.size.height/2)
box2.physicsBody = SKPhysicsBody(circleOfRadius: box2.frame.size.width/2)
self.addChild(wall)
self.addChild(floor)
self.addChild(box1)
self.addChild(box2)
box1.physicsBody?.allowsRotation = false
box2.physicsBody?.allowsRotation = false
box1.physicsBody?.restitution = 1
box2.physicsBody?.restitution = 1
box1.physicsBody?.mass = 1
box2.physicsBody?.mass = 100000
box1.physicsBody?.friction = 0
box2.physicsBody?.friction = 0
box1.physicsBody?.linearDamping = 0
box2.physicsBody?.linearDamping = 0
wall.physicsBody?.categoryBitMask = ColliderType.Wall.rawValue
box1.physicsBody?.categoryBitMask = ColliderType.Box1.rawValue
box2.physicsBody?.categoryBitMask = ColliderType.Box2.rawValue
box1.physicsBody?.collisionBitMask = ColliderType.Wall.rawValue | ColliderType.Floor.rawValue | ColliderType.Box2.rawValue
box2.physicsBody?.collisionBitMask = ColliderType.Floor.rawValue | ColliderType.Box1.rawValue
box1.physicsBody?.contactTestBitMask = ColliderType.Box2.rawValue | ColliderType.Wall.rawValue
box1.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
box2.physicsBody?.velocity = CGVector(dx: -50, dy: 0)
}
}
Things I've tried:
Set usesPreciseCollisionDetection = true on the first box
Update the positions and check for collisions by myself by overriding the update method (it wasn't as optimised as SpriteKit's engine so it was very slow)
Make the second box slower and set physicsWorld.speed to a number higher than 1
I think in general you're probably expecting too much. SpriteKit is a game engine, designed to give qualitatively reasonable behavior for scenarios involving normal-ish sorts of physics. It's not a high-precision physics simulation, and when you push it into a region where it's got to do something extreme, it's probably going to fail.
In this case, you're expecting it to simulate a huge number of perfectly elastic collisions within a small amount of time and distance (many collisions per animation frame of the simulation as the heavy block mashes the light one into a tiny gap). It's going to find the first collision in the frame, figure the impulses to apply to the blocks, and then advance to the next frame, with the effect that the big block happily mashes into the space where the small one resides. All the subsequent collisions that would prevent that are being missed in search of 60 fps.

Create Wall / Barrier Using SKSpriteNode physicsBody

I have a separate "Floor" class in my SKSpriteKit application. When I first created this class, I had a barrier around the whole frame using
self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
but that was actually stopping SKSpriteNodes from entering from the top of the screen so now I'm trying to figure out how I can create a left, right and bottom of the frame wall but not the top. This is so I can avoid one of my SKSpriteNodes at the bottom of the screen (still viewable by users) that moves left and right from leaving the display. I'm trying below which is building as if it's going to work but then when I start up the game I don't see the the walls? I'm not too sure what I'm doing wrong, I have
skView.showsPhysics = true
import Foundation
import SpriteKit
class Floor: SKNode {
override init() {
super.init()
let leftWall = SKSpriteNode(color: UIColor.brown, size: CGSize(width: 100, height: frame.height))
leftWall.position = CGPoint(x: 100, y: 100)
leftWall.physicsBody = SKPhysicsBody(rectangleOf: leftWall.size)
leftWall.physicsBody!.isDynamic = false
self.addChild(leftWall)
let rightWall = SKSpriteNode(color: UIColor.brown, size: CGSize(width: 100, height: frame.height))
rightWall.position = CGPoint(x: 200, y: 200)
rightWall.physicsBody = SKPhysicsBody(rectangleOf: rightWall.size)
rightWall.physicsBody!.isDynamic = false
self.addChild(rightWall)
// Set the bit mask properties
self.physicsBody?.categoryBitMask = balloonCategory
self.physicsBody?.contactTestBitMask = nailDropCategory
//self.physicsBody?.collisionBitMask = balloonCategory
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemted")
}
}
Gravity is affecting your walls, so they are forever falling into oblivion.
When you created your edge loop, you did not need to worry about this because edge loops are always static because it has no volumne
Now you are using volume based bodies, so you need to account for things like gravity and other forces affecting your bodies.
To have your body ignore these forces, simply set isDynamic = false
let leftWall = SKSpriteNode(color: UIColor.brown, size: CGSize(width: 100, height: frame.height))
leftWall.position = CGPoint(x: 100, y: 100)
leftWall.physicsBody = SKPhysicsBody(rectangleOf: leftWall.size)
leftWall.physicsBody!.isDynamic = false
self.addChild(leftWall)
let rightWall = SKSpriteNode(color: UIColor.brown, size: CGSize(width: 100, height: frame.height))
rightWall.position = CGPoint(x: 200, y: 200)
rightWall.physicsBody = SKPhysicsBody(rectangleOf: rightWall.size)
rightWall.physicsBody!.isDynamic = false
self.addChild(rightWall)

Does a parent have its children's physicsBodies?

I was wondering what the physicsBody of a parent would be if all I do is this:
let combinedBodies = SKSpriteNode()
addChild(combinedBodies)
//Create its children like this as an example:
let child = SKSpriteNode(color: UIColor.red, size: CGSize(width: 10, height: 20))
child.physicsBody = SKPhysicsBody(rectangleOf: child.size)
child.physicsBody?.categoryBitMask = collisionType.child.rawValue
combinedBodies.addChild(child)
allBodies.append(child.physicsBody?)
Would the physicsBody of combinedBodies be the combination of all its children's physicsBodies, or would it have none and I would have to manually add it? Also, what would the categoryBitMask of the parent be? Would it be collisionType.child.rawValue if all of its children had that category, or would I manually have to give it its own category?
Edit: I have used this:
combinedBodies.physicsBody = SKPhysicsBody(bodies: )
I have a list where I store all the children's physicsBodies and then put that list into this command (I keep updating the combinedBodies.physicsBody when new and old children get deleted). However, now I have the problem that the physicsbodies of the parent aren't where the original physicsbodies of the children were (they are at different positions on the screen), they are in the middle of the screen. Is there a way to fix this?
I have also changed the physicsBody of the combinedBodies like this:
combinedBodies.physicsBody = SKPhysicsBody(bodies: allBodies)
combinedBodies.physicsBody?.isDynamic = true
combinedBodies.physicsBody?.categoryBitMask = collisionType.combinedBodies.rawValue
combinedBodies.physicsBody?.collisionBitMask = 0
combinedBodies.physicsBody?.contactTestBitMask = 0
combinedBodies.physicsBody?.affectedByGravity = false
combinedBodies.physicsBody?.friction = 0.0
Please note that all the code in this question is basically my code, just that the new children are created through an iteration with different positions, rotations, and colors.
EDIT WITH EXAMPLE CODE: I have been able to recreate my issue in a test program which I have added here (the allLine is the parent of ScreenLine and line in this code).
class GameScene: SKScene, SKPhysicsContactDelegate {
enum type: UInt32 {
case line = 1
case screenLine = 2
case allLine = 4
}
override func didMove(to view: SKView) {
let allLine = SKSpriteNode()
addChild(allLine)
var points = [CGPoint]()
var bodies = [SKPhysicsBody]()
points = [CGPoint(x: 0, y: 300), CGPoint(x: -200, y: 100)]
let width = 5.0
let height = {() -> Double in
let distX = abs(points[1].x - points[0].x)
let distY = abs(points[1].y - points[0].y)
let dist = sqrt((distX * distX) + (distY * distY))
return Double(dist)
}()
let ScreenLine = SKSpriteNode(color: UIColor.init(red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0), size: CGSize(width: width, height: height))
ScreenLine.position = CGPoint(x: ((points[1].x + points[0].x)/2), y: ((points[1].y + points[0].y)/2))
let rotation = {() -> CGFloat in
let m = (points[1].y - points[0].y)/(points[1].x - points[0].x)
let angle = CGFloat(Double.pi/2) - atan(m)
return -angle
}()
ScreenLine.zRotation = rotation
ScreenLine.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: width, height: height), center: CGPoint(x: 0, y: 0))
ScreenLine.physicsBody?.contactTestBitMask = type.screenLine.rawValue
ScreenLine.physicsBody?.collisionBitMask = 0
ScreenLine.physicsBody?.contactTestBitMask = 0
ScreenLine.physicsBody?.isDynamic = true
ScreenLine.physicsBody?.affectedByGravity = false
allLine.addChild(ScreenLine)
bodies.append(ScreenLine.physicsBody!)
points = [CGPoint(x: -100, y: 300), CGPoint(x: -300, y: 100)]
let line = SKSpriteNode(color: UIColor.init(red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0), size: CGSize(width: width, height: height))
line.position = CGPoint(x: ((points[1].x + points[0].x)/2), y: ((points[1].y + points[0].y)/2))
line.zRotation = rotation
line.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: width, height: height), center: CGPoint(x: 0, y: 0))
line.physicsBody?.contactTestBitMask = type.screenLine.rawValue
line.physicsBody?.collisionBitMask = 0
line.physicsBody?.contactTestBitMask = 0
line.physicsBody?.isDynamic = true
line.physicsBody?.affectedByGravity = false
allLine.addChild(line)
bodies.append(line.physicsBody!)
allLine.physicsBody = SKPhysicsBody(bodies: bodies)
allLine.physicsBody?.isDynamic = true
allLine.physicsBody?.categoryBitMask = type.allLine.rawValue
allLine.physicsBody?.collisionBitMask = 0
allLine.physicsBody?.contactTestBitMask = 0
allLine.physicsBody?.affectedByGravity = false
allLine.physicsBody?.friction = 0.0
}
}
UPDATE: Now by centering the physicsBody of the children to where the node is (not CGPoint.zero), but where I place the child on the screen, the physicsBody is in the right position when I add it to the list.
I set the line and screen line physicsBody positions like this:
ScreenLine.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: width, height: height), center: ScreenLine.position)
line.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: width, height: height), center: line.position)
PROBLEM THAT I'M CURRENTLY TRYING TO FIGURE OUT:
This makes the physicsBodies of allLine centred. However, they aren't rotated like they should be. They don't preserve their rotations. How do I fix this??
Thanks for any answers and help :D

Swift - sprite kit - make SKPhysicsJointPin have less motion

I'm trying to create a snake with multiple body parts that moves left and right. Im using a pin, but upon the snake stopping, the body keeps moving and doesn't stop. I've messed around with the max and min angles, and the torque, but nothing seems to work. Should I use a different type of joint?
Sorry i cant log into my other account, but heres the code. Basically the second part just wobbles a lot. I wish to add 7-8 parts, but they just keep on wobbling, especially after moving "head". i would like a fluid "swoop" motion when i move the snake.
self.physicsWorld.gravity = CGVectorMake(0, -100)
self.physicsWorld.contactDelegate = self
head.size = CGSize(width: 25,height: 25)
head.physicsBody = SKPhysicsBody(texture: head.texture, size: head.size)
head.position = CGPointMake(100,400)
head.anchorPoint = CGPoint(x: 0.5, y: 1)
head.physicsBody!.dynamic = false
head.zPosition = 3
self.addChild(head)
var p1 = SKSpriteNode(imageNamed: "snakeBodyPart.png")
p1.size = CGSize(width: 25,height: 25)
p1.physicsBody = SKPhysicsBody(texture: p1.texture, size: p1.size)
p1.position = CGPointMake(100,380)
p1.anchorPoint = CGPoint(x: 0.5, y: 1)
p1.physicsBody!.dynamic = true
addChild(p1)
var joint = SKPhysicsJointPin.jointWithBodyA(head.physicsBody, bodyB: p1.physicsBody, anchor: CGPoint(x: CGRectGetMidX(head.frame), y: CGRectGetMinY(head.frame) + 10))
joint.upperAngleLimit = 0.1
joint.rotationSpeed = 0.1
self.physicsWorld.addJoint(joint)