Swift Remove 1 node moving after collision - swift

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!

Related

Point system with SpriteKit

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

Not detecting collision

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).

Collision tests not working in SpriteKit Swift 2

I have been trying to figure out why my didBeginContact method isn't working. I have a triangle falling from the top of the screen to the bottom and I have a line at the bottom which if it collides with it. My score should increment.
Here is the code responsible for this.
func createScene(){
physicsWorld.contactDelegate = self
slider = SKSpriteNode(imageNamed: "Slider")
slider.setScale(0.20)
slider.position = CGPoint(x: self.frame.width / 2, y: 0 + slider.frame.height / 2)
slider.physicsBody = SKPhysicsBody(rectangleOfSize: slider.size)
slider.physicsBody?.categoryBitMask = PhysicsCatergory.slider
slider.physicsBody?.collisionBitMask = PhysicsCatergory.coin | PhysicsCatergory.greenTriangle | PhysicsCatergory.orangeHexagon | PhysicsCatergory.purpleOctagon | PhysicsCatergory.redSquare
slider.physicsBody?.contactTestBitMask = PhysicsCatergory.coin | PhysicsCatergory.greenTriangle | PhysicsCatergory.orangeHexagon | PhysicsCatergory.purpleOctagon | PhysicsCatergory.redSquare
slider.physicsBody?.affectedByGravity = false
slider.physicsBody?.dynamic = false
var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("SpawnShapes"), userInfo: nil, repeats: true)
self.addChild(slider)
}
func didBeginContact(contact: SKPhysicsContact) {
let firstBody = contact.bodyA
let secondBody = contact.bodyB
if firstBody.categoryBitMask == PhysicsCatergory.greenTriangle && secondBody.categoryBitMask == PhysicsCatergory.Score{
score++
print(score)
}
else if firstBody.categoryBitMask == PhysicsCatergory.Score && secondBody.categoryBitMask == PhysicsCatergory.greenTriangle{
score++
print(score)
}
}
override func didMoveToView(view: SKView) {
createScene()
invisibleBounderies()
}
func invisibleBounderies(){
let scoreNode = SKSpriteNode()
scoreNode.size = CGSize(width: 5, height: 600)
scoreNode.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 10)
scoreNode.physicsBody = SKPhysicsBody(rectangleOfSize: scoreNode.size)
scoreNode.physicsBody?.affectedByGravity = false
scoreNode.physicsBody?.dynamic = false
scoreNode.physicsBody?.categoryBitMask = PhysicsCatergory.Score
scoreNode.physicsBody?.collisionBitMask = PhysicsCatergory.greenTriangle
scoreNode.physicsBody?.contactTestBitMask = PhysicsCatergory.greenTriangle
scoreNode.zRotation = CGFloat(M_PI/2.0)
scoreNode.color = SKColor.blueColor()
self.addChild(scoreNode)
}
func SpawnShapes(){
greenTriangle = SKSpriteNode(imageNamed:"greenTriangle")
purpleOctagon = SKSpriteNode(imageNamed: "purpleOctagon")
redSquare = SKSpriteNode(imageNamed: "redSquare")
coin = SKSpriteNode(imageNamed: "coin")
greenTriangle.physicsBody = SKPhysicsBody(rectangleOfSize: greenTriangle.size)
greenTriangle.physicsBody?.categoryBitMask = 0
greenTriangle.physicsBody?.collisionBitMask = PhysicsCatergory.Score
greenTriangle.physicsBody?.contactTestBitMask = PhysicsCatergory.Score
greenTriangle.physicsBody?.affectedByGravity = false
greenTriangle.physicsBody?.dynamic = false
var MinValue = self.size.width / 8
var MaxValue = self.size.width - 150
var SpawnPoint = UInt32(MaxValue - MinValue)
let action = SKAction.moveToY(-30, duration: 2.0)
slider.zPosition = 1
coin.zPosition = 2
greenTriangle.zPosition = 3
orangeHexagon.zPosition = 4
purpleOctagon.zPosition = 5
redSquare.zPosition = 6
greenTriangle.position = CGPoint(x: CGFloat(arc4random_uniform(SpawnPoint)), y: self.size.height)
self.addChild(greenTriangle)
greenTriangle.runAction(SKAction.repeatActionForever(action))
}
You need to set only greenTriangle dynamic property to true.
You set greenTriangle.physicsBody?.categoryBitMask = 0, that's why you are not detecting contact. greenTriangle.physicsBody?.categoryBitMask = PhysicsCatergory.greenTriangle might do the trick.
If you are still not detecting contact after these changes, check if your physicsCatergory is alright. It would be good to implement an enum of type UInt32 where the values are all single bits, or like this, if you prefer:
enum PhysicsCatergory : UInt32{
case slider = 1
case coin = 2
case greenTriangle = 4
case purpleOctagon = 8
case redSquare = 16
case orangeHexagon = 32
case Score = 64
}
Doing like this will require to add .rawValue in every reference to one of these enums. Example: greenTriangle.physicsBody?.categoryBitMask = PhysicsCatergory.greenTriangle.rawValue
I know it is not related to the question, but I would recommend you not to use SKAction for movement in this case, because it's going to ignore physics collision, since you are "forcing" its position. Use the physicsBody.velocity property instead to let the physics work. You will notice that when using the later the block is going to collide with the score line, since you also set its collisionBitMask. This way you can properly control with that it collides. Set physicsBody.linearDamping to zero if don't want the block to lose speed while it travels (air friction).
A physicsBody with a dynamic property of false cannot be involved in collisions; set dynamic to true.

contacts not recognized when body is changed from circle to rectangle

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.

Identify which sprite node was hit

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]