SpriteKit: child does not rotate with parent SKNode - ios

The goal is to rotate a physics body around a point using angular velocity, not zRotation. Because of this, it seems necessary to add a second physics body (the one controlling the rotations), but changing the angular velocity on this parent doesn't move the child. In other words, the parent rotates, but the child remains in place.
How can you rotate both the parent and child?
parent = SKNode()
sprite = SKSpriteNode(color: SKColor.whiteColor(), size: spriteSize)
sprite.position = CGPoint(x: 0, y: 50)
parent.addChild(sprite)
parent.physicsBody = SKPhysicsBody(rectangleOfSize: parentSize)
parent.physicsBody?.affectedByGravity = false
parent.physicsBody?.friction = 0
parent.physicsBody?.linearDamping = 0
parent.physicsBody?.angularDamping = 0
parent.physicsBody?.collisionBitMask = 0
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: spriteSize)
sprite.physicsBody?.affectedByGravity = false
sprite.physicsBody?.friction = 0
sprite.physicsBody?.linearDamping = 0
sprite.physicsBody?.angularDamping = 0
sprite.physicsBody?.categoryBitMask = bitMask
sprite.physicsBody?.collisionBitMask = 0
parent.physicsBody?.angularVelocity = 2.0

You can try to create joint between parent and child before setting angular velocity
let fixedJoint = SKPhysicsJointFixed()
fixedJoint.bodyA = parent.physicsBody
fixedJoint.bodyB = sprite.physicsBody
self.physicsWorld.addJoint(fixedJoint)

Related

Stopping an object falling through another using Physics in SceneKit

Swift 5, iOS 14
I have a box, that I give a dynamic physical body too and a plane on which I drop said box since I set the box to be affected by gravity.
I set up collision and contacts tests and detect the fact that one hits the other.
But I what I want to do is stop the box falling though the plane. Even if I turn gravity off when they collide, the stupid box keeps going...
I also tried changing the type from dynamic to static on the box, no effect.
I also tried changing the velocity of the box to zero on collision, no effect.
box.physicsBody?.isAffectedByGravity = true
box.physicsBody?.friction = 0
box.physicsBody?.restitution = 1 //bounceness of the object
box.physicsBody?.angularDamping = 1 // rotationess
box.physicsBody = SCNPhysicsBody(type: .dynamic, shape:SCNPhysicsShape(geometry: targetGeometry, options:nil))
box.physicsBody?.mass = 0.01
box.physicsBody?.categoryBitMask = foodCategory;
box.physicsBody?.contactTestBitMask = heroCategory;
box.physicsBody?.collisionBitMask = 0;
And
let planeGeo = SCNPlane(width: 8, height: 8)
planeGeo.firstMaterial?.diffuse.contents = UIColor.blue
planeGeo.firstMaterial?.isDoubleSided = true
let planeNode = SCNNode(geometry: planeGeo)
planeNode.simdPosition = SIMD3(x: 0, y: -4, z: -4)
planeNode.eulerAngles = SCNVector3(x: GLKMathDegreesToRadians(45), y: 0, z: 0)
planeNode.physicsBody?.isAffectedByGravity = false
planeNode.physicsBody?.friction = 0
planeNode.physicsBody?.restitution = 0 //bounceness of the object
planeNode.physicsBody?.angularDamping = 1 // rotationess
planeNode.physicsBody = SCNPhysicsBody(type: .static, shape:SCNPhysicsShape(geometry: planeGeo, options:nil))
planeNode.physicsBody?.categoryBitMask = heroCategory;
planeNode.physicsBody?.contactTestBitMask = foodCategory;
planeNode.physicsBody?.collisionBitMask = 0;
scene.rootNode.addChildNode(planeNode)
What have I missed here?
Try to use binary notation for your Physics Categories like:
let BitmaskCollision = Int(1 << 2)
let BitmaskCollectable = Int(1 << 3)
let BitmaskEnemy = Int(1 << 4)
let BitmaskSuperCollision = Int(1 << 5)
let BitmaskWater = Int(1 << 6)
Avoid using the 0 for the Collision Mask. Try using Int(1 << 2)
You also must use the category Bitmasks from your box within the collision detection of the plane i.Ex. like so:
planeNode.physicsBody?.collisionBitMask = BitmaskCollision | foodCategory
(example with multiple masks)
or
box.physicsBody?.collisionBitMask = heroCategory

Sprite physics showing up but not visually showing up in Swift (SpriteKit)

I set up the sprite's physics body, zPosition, color, size, etc., but it won't appear visually. (It has the highest zPosition of my sprites, and YES I did import SpriteKit)
let wall = SKSpriteNode()
wall.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
//Takes screen size / width to make each piece one/(value set by "maze" at top) of the screen
let width: CGFloat = (UIScreen.main.bounds.width) / CGFloat(mazeSize)
let height: CGFloat = (UIScreen.main.bounds.height - 134) / CGFloat(mazeSize) //subtract so there's extra room on top+bottom of screen
wall.color = UIColor(ciColor: .black)
wall.isHidden = false
wall.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: width, height: height))
wall.physicsBody?.restitution = 0
wall.physicsBody?.friction = 0
wall.physicsBody?.isDynamic = false
wall.zPosition = 4
//sets up x+y location based on row count and which row this is on
//locations (ONLY positive)
let xLoc: CGFloat = ((UIScreen.main.bounds.width) / CGFloat((mazeSize/2))) * CGFloat(i/mazeSize)
let yLoc: CGFloat = ((UIScreen.main.bounds.height - 134) / CGFloat(mazeSize/2)) * CGFloat(i/mazeSize)
if((i/mazeSize) > (mazeSize/2)){ //if positive on x
wall.position.x = xLoc
}
else{ //if negative on x
wall.position.x = -xLoc
}
if(i > ((mazeSize^2)/2)){ //if positive on y
wall.position.x = yLoc
}
else{ //if negative on y
wall.position.y = yLoc
}
I'm using this to set up walls in a maze (Yes I know the x+y positions are wrong, but they still "appear" clumped in the middle), but only the physics of these nodes show up. Is there a way I can make these black walls appear?
The geometry of the sprite node and the geometry of the physics body don't have to be related, and in this case you've made a rectangular physics body and basically an empty sprite node. Try something like
let wall = SKSpriteNode(color: .black, size: CGSize(width: 100, height: 100)
or using the SKSpriteNode(texture:) initializer.

How to apply a SCNAction & An Impulse Force to a SCNNode

Hey I have a ball that gets moved by a force-applied. What im trying to get it to do is basically have the causal effect of gravity acting upon it while its moving through the air to its destination. basically when the "move to" action is playing gravity does not take affect so instead of slowly falling down to the ground it instead moves to its final position then it just falls straight down when the "move to" action stops. do to the gravity in the scene.
Im trying to get the ball to be thrown in an arc and land on the target?
Code:
func CreateBall() {
let BallScene = SCNScene(named: "art.scnassets/Footballs.dae")
Ball = BallScene!.rootNode.childNodeWithName("Armature", recursively: true)! //the Amature/Bones
Ballbody = BallScene!.rootNode.childNodeWithName("Ball", recursively: true)!
let collisionCapsuleRadius3 = CGFloat(0.01) // Width of physicsBody
let collisionCapsuleHeight3 = CGFloat(0.01) // Height of physicsBody
Ball.position = SCNVector3Make(Guy.position.x, Guy.position.y, Guy.position.z)
Ball.scale = SCNVector3Make(5, 5, 5)
Ball.rotation = SCNVector4Make(0.0,0.0,0.0,0.0) // x,y,z,w
Ball.physicsBody = SCNPhysicsBody(type: .Dynamic, shape:SCNPhysicsShape(geometry: SCNCapsule(capRadius: collisionCapsuleRadius3, height: collisionCapsuleHeight3), options:nil))
Ball.physicsBody?.affectedByGravity = true
Ball.physicsBody?.friction = 1 //
Ball.physicsBody?.restitution = 0 //bounceness of the object. 1.0 will boounce forever
Ball.physicsBody?.angularDamping = 1 // ability to rotate
Ball.physicsBody?.mass = 1
Ball.physicsBody?.rollingFriction = 1
Ball.physicsBody!.categoryBitMask = BitmaskCollision4
Ball.physicsBody?.contactTestBitMask = BitmaskCollision3 //| BitmaskCollision2
Ballbody.physicsBody?.collisionBitMask = BitmaskCollision2 | BitmaskCollision3 | BitmaskCollision//| BitmaskCollision2
scnView.scene!.rootNode.addChildNode(Ball)
scnView.scene!.rootNode.addChildNode(Ballbody)
}
CreateBall()
now this is where the magic happens:
scnView.scene!.physicsWorld.gravity = SCNVector3(x: 0, y: -9.8, z: 0)
let location = SCNVector3(Guy2.presentationNode.position.x, 0.0, Guy2.presentationNode.position.z + Float(50) )
let moveAction = SCNAction.moveTo(location, duration: 2.0)
Ball.runAction(SCNAction.sequence([moveAction]))
let forceApplyed = SCNVector3(x: 0.0, y: 100.0 , z: 0.0)
Ball.physicsBody?.applyForce(forceApplyed, atPosition: Ball.presentationNode.position, impulse: true)
Combining SCNActions and physics doesn't work, you need to use one or the other. Using physics you can calculate the exact force needed to propel your node to a target.
I have adapted a solution for Unity found here and utilised an SCNVector3 extension that makes some of the calculations much easier.
Basically you pass in an SCNNode that you want to throw, an SCNVector3 for the target and an angle (in radians) that you want the node to be thrown at. This function will then work out the force required to reach the target.
func shootProjectile() {
let velocity = ballisticVelocity(ball, target: target.position, angle: Float(0.4))
ball.physicsBody?.applyForce(velocity, impulse: true)
}
func ballisticVelocity(projectile:SCNNode, target: SCNVector3, angle: Float) -> SCNVector3 {
let origin = projectile.presentationNode.position
var dir = target - origin // get target direction
let h = dir.y // get height difference
dir.y = 0 // retain only the horizontal direction
var dist = dir.length() // get horizontal distance
dir.y = dist * tan(angle) // set dir to the elevation angle
dist += h / tan(angle) // correct for small height differences
// calculate the velocity magnitude
let vel = sqrt(dist * -scene.physicsWorld.gravity.y / sin(2 * angle))
return dir.normalized() * vel * Float(projectile.physicsBody!.mass)
}
It is also important to set the damping of the physicsBody to 0, otherwise it will be affected by air resistance.
I’m not going to pretend to know exactly how this works, but Wikipedia has articles that explain all the maths behind it.
UPDATE
Since using the code above I've noticed it doesn't always work, especially when the heights of the origin and target are different. From the same forum this function seems more reliable.
func calculateBestThrowSpeed(origin: SCNVector3, target: SCNVector3, timeToTarget:Float) -> SCNVector3 {
let gravity:SCNVector3 = sceneView.scene!.physicsWorld.gravity
let toTarget = target - origin
var toTargetXZ = toTarget
toTargetXZ.y = 0
let y = toTarget.y
let xz = toTargetXZ.length()
let t = timeToTarget
let v0y = y / t + 0.5 * gravity.length() * t
let v0xz = xz / t
var result = toTargetXZ.normalized()
result *= v0xz
result.y = v0y
return result
}

Make Node Dynamic for Only One Object Swift and Spritekit

I am creating a space shooter game, and I want to make one of my nodes only be dynamic when it interacts with ones specific node, as opposed to all nodes. Essentially, I only want the node to act as dynamic when it interacts with one specified node. How can I do this?
Thanks.
I know the question is a but confusing, but if anyone has any ideas it would be extremely helpful!!
let Bullet = SKSpriteNode(imageNamed: "BulletGalaga.png")
Bullet.zPosition = -5
Bullet.position = CGPoint(x: ship.position.x, y: ship.position.y)
Bullet.zRotation = ship.zRotation
//let action = SKAction.moveTo(CGPointMake(400 * cos(Bullet.zRotation),(400 * -sin(Bullet.zRotation))), duration: 0.8)
let action = SKAction.move(
to: CGPoint(
x: 1200 * -cos(Bullet.zRotation - 1.57079633) + Bullet.position.x,
y: 1200 * -sin(Bullet.zRotation - 1.57079633) + Bullet.position.y
),
duration: 2.4)
//let action = SKAction.moveToY(self.size.height + 30, duration: 0.8)
//let action = SKAction.moveTo(self.ship.size.height, duration: 0.8)
let actionDone = SKAction.removeFromParent()
Bullet.run(SKAction.sequence([action, actionDone]), withKey: "bulletAction")
Bullet.physicsBody = SKPhysicsBody(rectangleOf: Bullet.size)
Bullet.physicsBody?.affectedByGravity = false
Bullet.physicsBody?.isDynamic = false
self.addChild(Bullet)
I added this:
ship.physicsBody?.categoryBitMask = shipCategoryBitMask
Bullet.physicsBody?.categoryBitMask = bulletCategoryBitMask
enemy.physicsBody?.categoryBitMask = enemyCategoryBitMask
enemyBullet.physicsBody?.categoryBitMask = enemyBulletCategoryBitMask
ship.physicsBody?.collisionBitMask = enemyBulletCategoryBitMask
Bullet.physicsBody?.collisionBitMask = enemyCategoryBitMask
enemyBullet.physicsBody?.collisionBitMask = shipCategoryBitMask
enemy.physicsBody?.collisionBitMask = bulletCategoryBitMask
Once you've set a SpriteNode's physics body, you can use the collisionBitMask to specify which other SpriteNodes it will collide with. You'll need to first set each node's physics body's categoryBitMask.
let node1CategoryBitMask = 0x1 << 0
let node2CategoryBitMask = 0x1 << 1
let node3CategoryBitMask = 0x1 << 2
node1.physicsBody.categoryBitMask = node1CategoryBitMask
node2.physicsBody.categoryBitMask = node2CategoryBitMask
node3.physicsBody.categoryBitMask = node3CategoryBitMask
At this point, three of the sprites have their categoryBitMask set. This is what identifies the physics body. Now to set the collisionBitMasks.
node1.collisionBitMask = node2CategoryBitMask
node2.collisionBitMask = node1CategoryBitMask | node3CategoryBitMask
This tells node1 to by collide with node2, but not node3. Node2 will collide with node1 and node3.
Hope this helps.

Swift Physicsbody pinned and joined not working as expected

I'm trying to create a simple car with SKSpriteNode. Physics and everything works great, except for one tiny thing: when my car lands on the ground from a greater distance the body of the car is pushed down and then goes back to its place.
See image:
I want to get the left image, but I get the one on the right.
When it lands on the ground I don't want the body to go into the wheels, I want it to be in a fixed place relative to the wheels or vice-versa.
So here is how I do it:
This is in the car class:
carWheelJoint1 = SKPhysicsJointPin.jointWithBodyA(self.physicsBody!, bodyB: leftWheel.physicsBody!, anchor: leftWheel.position)
carWheelJoint2 = SKPhysicsJointPin.jointWithBodyA(self.physicsBody!, bodyB: rightWheel.physicsBody!, anchor: rightWheel.position)
And this is in the GameScene class:
//Add vehicle
vehicle = DevCar(color: UIColor.blueColor(), size: CGSize(width: 250, height: 50), cameraThreshold: cameraThreshold)
vehicle.position = (CGPoint(x: 0, y: cameraThreshold))
addChild(vehicle)
self.physicsWorld.addJoint(vehicle.carWheelJoint1)
self.physicsWorld.addJoint(vehicle.carWheelJoint2)
I tried using SKPhysicsJointFixed but that did not work either.
Any ideas?
EDIT: this is how the physicsBody is defined:
Here is the car body in the car class.
The car class is an SKSpriteNode and is the body of the car itself, the wheels are its children.
let baseBody = SKPhysicsBody(rectangleOfSize: size)
baseBody.mass = CGFloat(1)
baseBody.dynamic = true
baseBody.affectedByGravity = true
baseBody.friction = 0.2
baseBody.allowsRotation = false
baseBody.density = CGFloat(10)
self.physicsBody = baseBody
And here is the wheel body in the same class:
leftWheel = SKShapeNode(circleOfRadius: wheelSize)
leftWheel.position = CGPoint(x: size.width / -2 + wheelSize, y: size.height / -2 - wheelSize)
rightWheel = SKShapeNode(circleOfRadius: wheelSize)
rightWheel.position = CGPoint(x: size.width / 2 - wheelSize, y: size.height / -2 - wheelSize)
leftWheel.physicsBody = SKPhysicsBody(circleOfRadius: wheelSize)
leftWheel.physicsBody!.mass = CGFloat(0.2)
leftWheel.physicsBody!.dynamic = true
leftWheel.physicsBody!.affectedByGravity = true
leftWheel.physicsBody!.friction = 0.3
rightWheel.physicsBody = SKPhysicsBody(circleOfRadius: wheelSize)
rightWheel.physicsBody!.mass = CGFloat(0.2)
rightWheel.physicsBody!.dynamic = true
rightWheel.physicsBody!.affectedByGravity = true
rightWheel.physicsBody!.friction = 0.3
addChild(leftWheel)
addChild(rightWheel)
EDIT2: Added video
https://www.youtube.com/watch?v=TTwa-t4u4pI

Resources