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.
Related
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
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
}
I am working on a game with a variety of collisions. I have created an Enumeration as follows:
enum CollisionCategories: UInt32 {
case localPlayer = 1
case ball = 2
case remotePlayer = 4
case scene = 8
case goal = 16
}
I then created some category variables:
let localPlayerCategory: UInt32 = CollisionCategories.localPlayer.rawValue
let ballCategory: UInt32 = CollisionCategories.ball.rawValue
let remotePlayerCategory: UInt32 = CollisionCategories.remotePlayer.rawValue
let sceneCategory: UInt32 = CollisionCategories.scene.rawValue
let goalCategory: UInt32 = CollisionCategories.goal.rawValue
Here is where I print the info
func didBeginContact(contact: SKPhysicsContact) {
print("Body A: " + String(contact.bodyA.categoryBitMask))
print("Body B: " + String (contact.bodyB.categoryBitMask) + "\n")
}
However, when the goal and ball collide it returns the following:
Body A: 4294967295
Body B: 2
Body B is correctly the ball category; however, Body A is clearly off. Here is how I create the goal node:
let goal2 = self.newGoal()
goal2.zRotation = CGFloat(M_PI)
goal2.position = CGPoint(x: (gameFrame!.frame.width / 2), y: 0)
let bottom2 = SKPhysicsBody(edgeFromPoint: CGPoint(x: -(goal2.frame.width), y:-(goal2.frame.height / 2)), toPoint: CGPoint(x: 0, y:-(goal2.frame.height / 2)))
bottom2.categoryBitMask = sceneCategory
let top2 = SKPhysicsBody(edgeFromPoint: CGPoint(x: -(goal2.frame.width), y:(goal2.frame.height / 2)), toPoint: CGPoint(x: 0, y:(goal2.frame.height / 2)))
top2.categoryBitMask = sceneCategory
let back2 = SKPhysicsBody(edgeFromPoint: CGPoint(x: -(goal2.frame.width), y:(goal2.frame.height / 2)), toPoint: CGPoint(x: -(goal2.frame.width), y:-(goal2.frame.height / 2)))
back2.categoryBitMask = goalCategory
goal2.physicsBody = SKPhysicsBody(bodies: [bottom2,top2,back2])
//goal2.physicsBody!.categoryBitMask = goalCategory
goal2.physicsBody!.usesPreciseCollisionDetection = true
goal2.physicsBody!.affectedByGravity = false
goal2.physicsBody!.dynamic = false
As you can see in the line I commented out above, I have also tried setting the entire physics body to be the goal, rather than just the section to see if that was causing the issue. It was not.
Here is how I create the ball's physics properties:
ball.physicsBody!.collisionBitMask = localPlayerCategory | sceneCategory | goalCategory
ball.physicsBody!.categoryBitMask = ballCategory
ball.physicsBody!.contactTestBitMask = goalCategory
So, can anyone can explain why they think bodyA's categoryBitMask is returning the max UInt32? Please note that EVERY collision except for the ball hitting the goal works just fine.
Also, quick side question: Does anyone know how bodyA and bodyB are selected? Why which is which? Thanks!
According to https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SKPhysicsBody_Ref/#//apple_ref/occ/clm/SKPhysicsBody/bodyWithBodies: The properties of the bodies used are ignored. Only their shapes are used to construct the new physics body. This means that individual sections cannot have their own categoryBitMask. I will have to use a separate node to get the result I want.
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
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)