With help from here I have made a circle body traverse a given path. I have some bodies at some of the path points and have logged contact in didBeginContact. When the body gets in contact with a specific body the circle body is changed to a rectangle. This rectangular body is suppose to traverse the same path as the original circle body but it doesn't reach the path points as the contact is not logged. I tried changing radiusPoint to the width or height of the rectangle also but that didn't work. Also the rectangle body is bigger than the circle body. How can I get the rectangle to traverse the points with the contact recognised? Please see code below.
Code related to path traversal:
let repeats: Bool = true //Whether to repeat the path.
var pathIndex = 0 //The index of the current point to travel.
var pointRadius: CGFloat = SKTexture(imageNamed: "circle").size().width //How close the node must be to reach the destination point.
let travelSpeed: CGFloat = 250 //Speed the node will travel at.
let rate: CGFloat = 0.9 //Motion smoothing. 0.5
circlePath = [
CGPoint(x:screenSize.width , y: screenSize.height/3),
CGPoint(x: screenSize.width/2, y: platform.sprite.frame.height),
CGPoint(x: 0.0, y: screenSize.height/3),
CGPoint(x: CGFloat(pos1) + screenSize.width/20, y: upperSpearPosHeight)]
final func didReachPoint() {
//reached point!
pathIndex++
if pathIndex >= ballPath.count && repeats {
pathIndex = 0
}
}
func updatePath() {
if pathIndex >= 0 && pathIndex < circlePath.count {
let destination = circlePath[pathIndex]
//currentPosition = destination
let displacement = CGVector(dx: destination.x-circle!.sprite.position.x, dy: destination.y-circle!.sprite.position.y)
let radius = sqrt(displacement.dx*displacement.dx+displacement.dy*displacement.dy)
let normal = CGVector(dx: displacement.dx/radius, dy: displacement.dy/radius)
let impulse = CGVector(dx: normal.dx*travelSpeed, dy: normal.dy*travelSpeed)
let relativeVelocity = CGVector(dx:impulse.dx-circle!.sprite.physicsBody!.velocity.dx, dy:impulse.dy-circle!.sprite.physicsBody!.velocity.dy);
circle!.sprite.physicsBody!.velocity=CGVectorMake(circle!.sprite.physicsBody!.velocity.dx+relativeVelocity.dx*rate, circle!.sprite.physicsBody!.velocity.dy+relativeVelocity.dy*rate);
if radius < pointRadius {
didReachPoint()
}
}
}
Contact code:
func didBeginContact(contact: SKPhysicsContact) {
var firstBody : SKPhysicsBody
var secondBody : SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
}
else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if firstBody.categoryBitMask == circleCategory && secondBody.categoryBitMask == bonusCategory {
let img = SKTexture(imageNamed: "rectangular")
(firstBody.node! as? SKSpriteNode)?.size = img.size()
firstBody.node!.physicsBody = SKPhysicsBody(texture: img, size: img.size())
firstBody.node!.physicsBody?.allowsRotation = false
changeCircleAction = SKAction.setTexture(img)
firstBody.node!.runAction(changeCircleAction)
}
if firstBody.categoryBitMask == circleCategory && secondBody.categoryBitMask == platformCategory {
print("touched platform")
}
if firstBody.categoryBitMask == circleCategory && secondBody.categoryBitMask == smallStarCategory {
removeStar(secondBody.node!)
}
It seems like when you change the firstBody to a rectangle (in the didBeginContact method), you don't set the bit mask. From what you described, it appears you want to set it to circleCategory as such:
firstBody.node!.physicsBody?.categoryBitMask = circleCategory
I would put this right below the firstBody.node!.physicsBody?.allowsRotation = false.
Related
I'm making a game where I have a ball(the player) that is suppose to get from on side to the other of the screen. On both of the sides I have a SKSpriteNode that the ball needs to touch to score one point. When the ball touches the SKSpriteNode on on of the sides, then the ball needs to get to the other side and touch the other SKSpriteNode to score another point. And so on. How can I make so that when the game starts, the spritenode at the top is active, and when that is touched, the one at the bottom is active?
I've made a "incrementScore" function that adds one to the score everytime the ball touches the "points":
func incrementScore() {
score += 1
scoreLabel.text = String(score)
}
Then I have my two "goals" that the ball needs to touch to score:
func createGoals() {
let goalTop = SKSpriteNode(imageNamed: "Goal")
goalTop.name = "Goal"
goalTop.anchorPoint = CGPoint(x: 0.5, y: 0.5)
goalTop.position = CGPoint(x: 0, y: 465)
goalTop.physicsBody = SKPhysicsBody(rectangleOf: goalTop.size)
goalTop.physicsBody?.affectedByGravity = false
goalTop.physicsBody?.isDynamic = false
goalTop.zPosition = 2
self.addChild(goalTop)
let goalBottom = SKSpriteNode(imageNamed: "Goal")
goalBottom.name = "Goal"
goalBottom.anchorPoint = CGPoint(x: 0.5, y: 0.5)
goalBottom.position = CGPoint(x: 0, y: -435)
goalBottom.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width:
goalBottom.size.width - 10, height: goalBottom.size.height - 10))
goalBottom.physicsBody?.affectedByGravity = false
goalBottom.physicsBody?.isDynamic = false
goalBottom.zPosition = 2
self.addChild(goalBottom)
}
Also I've started on the didBegin contact function:
func didBegin(_ contact: SKPhysicsContact) {
var firstBody = SKPhysicsBody()
var secondBody = SKPhysicsBody()
if contact.bodyA.node?.name == "Player" {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if firstBody.node?.name == "Player" && secondBody.node?.name ==
"Point" {
incrementScore()
}
}
use the categoryBitMask to enable active bodies:
When you want goalTop to be active:
goalTop.categoryBitMask = category.Goal
goalBottom.categoryBitMask = 0
When you want goalBottom to be active:
goalTop.categoryBitMask = 0
goalBottom.categoryBitMask = category.Goal
Note: category.Goal is whatever number you use as your category
I'm getting some difficulty detecting collision in my SpriteKit Game. I simply want to detect collision between the missiles and the enemies and boats.
I have the ColliderType:
struct ColliderType {
static let Boat: UInt32 = 1
static let Enemy: UInt32 = 2
static let Wall: UInt32 = 3
static let Bullet: UInt32 = 4
}
class GameplayScene: SKScene, SKPhysicsContactDelegate {
var player = SKSpriteNode()
var enemy = SKSpriteNode()
var boat = SKSpriteNode()
var missile = SKSpriteNode()
The didBegin contact:
func didBegin(_ contact: SKPhysicsContact) {
var firstBody = SKPhysicsBody()
var secondBody = SKPhysicsBody()
if contact.bodyA.node?.name == "Missile" {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if firstBody.node?.name == "Missile" && secondBody.node?.name ==
"Enemy" {
incrementScore()
secondBody.node?.removeFromParent()
} else if firstBody.node?.name == "Missile" &&
secondBody.node?.name == "Boat" {
incrementScore()
}
}
I have added the "physicsWorld.contactDelegate = self" in the didMove toview
I have also added physicsBodies to all the relevant spritenodes:
func fireMissile() {
let missile = SKSpriteNode(color: .yellow, size: CGSize(width: 20,
height: 5))
missile.name = "Missile"
missile.position = CGPoint(x: player.position.x + 28, y:
player.position.y + 10)
missile.zPosition = 2
missile.physicsBody = SKPhysicsBody(rectangleOf: missile.size)
missile.physicsBody?.isDynamic = false
missile.physicsBody?.categoryBitMask = ColliderType.Bullet
missile.physicsBody?.collisionBitMask = ColliderType.Enemy |
ColliderType.Boat
missile.physicsBody?.contactTestBitMask = ColliderType.Enemy |
ColliderType.Boat
self.addChild(missile)
}
func createEnemies() {
let enemy = SKSpriteNode(imageNamed: "Enemy1")
enemy.name = "Enemy"
enemy.anchorPoint = CGPoint(x: 0.5, y: 0.5)
enemy.physicsBody = SKPhysicsBody(circleOfRadius: enemy.size.height
/ 2)
enemy.physicsBody?.categoryBitMask = ColliderType.Enemy
enemy.physicsBody?.affectedByGravity = false
enemy.physicsBody?.isDynamic = false
enemy.zPosition = 3
enemy.position.y = self.frame.height + 100
enemy.position.x = CGFloat.randomBetweenNumbers(firstNum: -347.5,
secondNum: -85)
self.addChild(enemy)
}
func createBoat() {
let boat = SKSpriteNode(imageNamed: "Boat")
boat.name = "Boat"
boat.anchorPoint = CGPoint(x: 0.5, y: 0.5)
boat.physicsBody = SKPhysicsBody(circleOfRadius: boat.size.height /
2)
boat.physicsBody?.categoryBitMask = ColliderType.Boat
boat.physicsBody?.affectedByGravity = false
boat.physicsBody?.isDynamic = false
boat.zPosition = 3
boat.position.y = self.frame.height + 100
boat.position.x = CGFloat.randomBetweenNumbers(firstNum: 0,
secondNum: 0)
self.addChild(boat)
}
Firstly, some of your categories are wrong:
static let Wall: UInt32 = 3
static let Bullet: UInt32 = 4
This effectively defines Wall as being both a Boat and an Enemy. Change them to:
static let Wall: UInt32 = 4
static let Bullet: UInt32 = 8
(Categories should always be unique powers of 2 - 1, 2, 4, 8, 16 etc).
The rest looks ok, so try that and let us know if it’s working.
Edit:
OK - just noticed that all of your physics bodies have their isDynamic property set to false - this means that, among other things, the body will not trigger contacts. so if you want missile to generate contacts with either enemy or boat, then either missile should be dynamic or both enemy and boat should be dynamic (only 1 of the 2 objects involved in a contact needs to by dynamic).
I have nodes falling from the top of the screen every second or so.
When the player (at the bottom of the screen) collides with a falling node I want that particular node to be removed from the screen but have the other continue to fall.
I thought calling node.removeFromParent() might do this or might remove all of the nodes but nothing is happening regardless.
Here is what I have:
Making the nodes fall:
func makeMete() {
let meteTexture = SKTexture(imageNamed: "mete.png")
let movementAmount = arc4random() % UInt32(self.frame.width)
let meteOffset = CGFloat(movementAmount) - self.frame.width / 2
let moveMete = SKAction.move(by: CGVector(dx: 0, dy: -2 * self.frame.height), duration: TimeInterval(self.frame.height / 300))
let mete = SKSpriteNode(texture: meteTexture)
mete.position = CGPoint(x: self.frame.midX + meteOffset, y: self.frame.midY + self.frame.height / 2)
mete.physicsBody = SKPhysicsBody(circleOfRadius: meteTexture.size().height / 2)
mete.physicsBody!.isDynamic = false
mete.physicsBody!.contactTestBitMask = ColliderType.object.rawValue
mete.physicsBody!.categoryBitMask = ColliderType.object.rawValue
mete.physicsBody!.collisionBitMask = ColliderType.object.rawValue
mete.run(moveMete)
self.addChild(mete)
}
Detecting contact:
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask == ColliderType.object.rawValue || contact.bodyB.categoryBitMask == ColliderType.object.rawValue {
player.physicsBody!.velocity = CGVector(dx: 0, dy: 0)
isUserInteractionEnabled = false
mete.removeFromParent()
}
.removeFromParent() seems to only work for me when there is one node on screen. Any more then it doesn't work.
If you use mete it references the actual object mete and I assume there are many metes on screen. Try referencing the actual node from the physics body and removing that from the parent.
Replace
if contact.bodyA.categoryBitMask == ColliderType.object.rawValue || contact.bodyB.categoryBitMask == ColliderType.object.rawValue {
player.physicsBody!.velocity = CGVector(dx: 0, dy: 0)
isUserInteractionEnabled = false
mete.removeFromParent()
}
With
if contact.bodyA.categoryBitMask == ColliderType.object.rawValue {
contact.bodyA.node?.removeFromParent()
}else if contact.bodyB.categoryBitMask == ColliderType.object.rawValue {
contact.bodyB.node?.removeFromParent()
}
The .node gives you access to the actual instance in question.
Good Luck!
I want to override collisions in SpriteKit.
The idea is that I have a ball which bounces around the scene. When didBeginContact detects contact between an edge and the ball, I want the ball to rebound in a random direction and speed.
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
let kEdgeCollisionCategory:UInt32 = 0x1 << 1
let kSquareCollisionCategory:UInt32 = 0x1 << 2
override func didMoveToView(view: SKView) {
/* Setup your scene here */
// Physics world
self.physicsWorld.gravity = CGVectorMake(0.0, 0.0)
self.physicsWorld.contactDelegate = self
// Edge
let frameEdges = SKNode()
frameEdges.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
frameEdges.physicsBody?.categoryBitMask = kEdgeCollisionCategory
self.addChild(frameEdges)
// Sprite
var sprite = SKSpriteNode(imageNamed: "blue")
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(sprite.size.width, sprite.size.height))
sprite.physicsBody?.restitution = 1.0
sprite.physicsBody?.linearDamping = 0.0
sprite.physicsBody?.angularDamping = 0.0
sprite.physicsBody?.friction = 0.0
sprite.physicsBody?.dynamic = true
sprite.physicsBody?.categoryBitMask = kSquareCollisionCategory
sprite.physicsBody?.collisionBitMask = kEdgeCollisionCategory
sprite.physicsBody?.contactTestBitMask = kEdgeCollisionCategory
self.addChild(sprite)
}
func didBeginContact(contact: SKPhysicsContact) {
var firstBody:SKPhysicsBody?
var second:SKPhysicsBody?
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
firstBody = contact.bodyA
secondBody = contact.bodyB
}
else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
var randomX = CGFloat(arc4random_uniform(UInt32(4))))
var randomY = CGFloat(arc4random_uniform(UInt32(4))))
if firstBody!.categoryBitMask == kSquareCollisionCategory && secondBody!.categoryBitMask == kEdgeCollisionCategory {
firstBody!.velocity = (CGVectorMake(firstBody!.velocity.dx * randomX, firstBody!.velocity.dy * randomY))
}
}
}
Upon contact you can apply a force or impulse to the ball depending on the ball's current vector. For example, upon contact the ball's vector is (30,-20). This example vector translates to the ball moving right and and down. You can then apply a new vector making the ball move left and up (-20, 25). You can use arc4random to set new values or set static ones.
If you need, read up on using vectors with physics bodies.
Your problem is here ,
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
firstBody = contact.bodyA
secondBody = contact.bodyB
}
else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
This means first body has categoryBitMask of kEdgeCollisionCategory and second body has categoryBitMask of kSquareCollisionCategory. So change this to,
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
else {
firstBody = contact.bodyA
secondBody = contact.bodyB
}
To get random number use this,
int minSpeed = 1;
int maxSpeed = 20;
int randNum = rand() % (max-min) + min;
Then apply impulse over the required body.
I would like to be able to identify which exact target sprite node is hit when the user flings another sprite at a group of target sprites. Here is how I set up my sprites under the didMoveToView function (only including the relevant lines of code here)
let flingerTexture = SKTexture(imageNamed: "flinger")
flingerNode.position = CGPoint(x: 768, y: 440)
flingerNode.physicsBody = SKPhysicsBody(texture: flingerTexture, size: flingerNode.size)
flingerNode.physicsBody?.dynamic = true
flingerNode.physicsBody!.categoryBitMask = PhysicsCategory.Flinger
flingerNode.physicsBody!.collisionBitMask = PhysicsCategory.Edge | PhysicsCategory.Bubble | PhysicsCategory.Ball
flingerNode.physicsBody?.contactTestBitMask = PhysicsCategory.Ball
let rotationConstraint = SKConstraint.zRotation(SKRange(lowerLimit: -π/4, upperLimit: π/4))
flingerNode.constraints = [rotationConstraint]
addChild(flingerNode)
// -------------Setup targets---------------
let range: Range<Int> = 1...10
for numbers in range {
let ballNode: BallNode = BallNode(imageNamed: "\(numbers)a")
let positionX = CGFloat.random(min: size.width / 6, max: size.width * 5/6)
let positionY = CGFloat.random(min: size.height * 4/9, max: size.height * 8/9)
ballNode.position = CGPoint(x: positionX, y: positionY)
ballNode.name = "Ball"
ballNode.ballIndex = Int(numbers)
index = ballNode.ballIndex
ballNode.ballHit = false
addChild(ballNode)
ballNode.physicsBody = SKPhysicsBody(circleOfRadius: 100)
ballNode.physicsBody!.affectedByGravity = false
ballNode.physicsBody?.dynamic = true
ballNode.physicsBody!.restitution = 0.5
ballNode.physicsBody!.friction = 0.0
ballNode.physicsBody!.categoryBitMask = PhysicsCategory.Ball
ballNode.physicsBody!.collisionBitMask = PhysicsCategory.Ball | PhysicsCategory.Bubble | PhysicsCategory.Edge | PhysicsCategory.Flinger | PhysicsCategory.Wall
ballNode.physicsBody?.contactTestBitMask = PhysicsCategory.Flinger
ballNode.userData = NSMutableDictionary()
ballArray.append(ballNode.ballIndex)
}
I am able to detect the collision, but am unable to retrieve the additional userData that would identify which exact ballNode was struck. When I tried the following code, it only returns an output of "nil".
func didBeginContact(contact: SKPhysicsContact!) {
let collision = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if collision == PhysicsCategory.Flinger | PhysicsCategory.Ball {
println(ballNode.userData)
}
}
I am assuming that PhysicsCategory.Flinger is less than PhysicsCategory.Ball. Then in didContactBegan you can use this code.
func didBeginContact(contact: SKPhysicsContact) {
var body1 : SKPhysicsBody!
var body2 : SKPhysicsBody!
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
body1 = contact.bodyA
body2 = contact.bodyB
}
else {
body1 = contact.bodyB
body2 = contact.bodyA
}
if body1.categoryBitMask == PhysicsCategory.Flinger &&
body2.categoryBitMask == PhysicsCategory.Ball {
if let ballNode = body2.node {
println(ballNode.userData)
}
}
}
The conditions have to be reversed if PhysicsCategory.Flinger is greater than PhysicsCategory.Ball.
if body1.categoryBitMask == PhysicsCategory.Ball &&
body2.categoryBitMask == PhysicsCategory.Flinger {
if let ballNode = body1.node {
println(ballNode.userData)
}
}
Thanks so much for pointing me in the right direction #rakeshbs! Your didBeginContact method works - the problem was I wasn't adding the userData correctly. I resolved this by adding the following line to my didMoveToView function:
ballNode.userData = ["ballNumber" : ballNode.ballIndex]