How to remove node from parent if touched more than once? - swift

I am making a game in which bullets are shot to the bad guy when the button is pressed. I made a function in which whenever it is called it adds more bad guys.
Here is the code: (This method is called multiple times)
func BadGuyPosition()
let BadGuyCircle = SKSpriteNode(imageNamed: "CircleBadGuy")
BadGuyCircle.zPosition = 1
//var mininmum = self.size.width / 600
let TooMuch = self.size.height - 60
let PointToShow = UInt32(TooMuch)
BadGuyCircle.position = CGPointMake(CGRectGetMaxX(self.frame) + 20 , CGFloat(arc4random_uniform(PointToShow) ))
let action2 = SKAction.moveToX(-100, duration: 5.0)
let remove = SKAction.removeFromParent()
BadGuyCircle.runAction(SKAction.sequence([action2,remove]))
//Physics BadGuy
BadGuyCircle.physicsBody = SKPhysicsBody(rectangleOfSize: BadGuyCircle.size)
BadGuyCircle.physicsBody?.categoryBitMask = Numbering.Badguy
BadGuyCircle.physicsBody?.contactTestBitMask = Numbering.Laser
BadGuyCircle.physicsBody?.affectedByGravity = false
BadGuyCircle.physicsBody?.dynamic = true
self.addChild(BadGuyCircle)
I want it so that the bad guy is removed from the parent if 2 bullets are made in contact with the bad guy.
I got it so that when 1 bullet makes contact with the enemy, it is removed from the parent. (here is the code)
func didBeginContact(contact: SKPhysicsContact) {
let A : SKPhysicsBody = contact.bodyA
let B : SKPhysicsBody = contact.bodyB
if (A.categoryBitMask == Numbering.Badguy) && (B.categoryBitMask == Numbering.Laser) || (A.categoryBitMask == Numbering.Laser) && (B.categoryBitMask == Numbering.Badguy)
{
runAction(BadGuyLostSound)
bulletsTouchedBadGuy(A.node as! SKSpriteNode, Laser: B.node as! SKSpriteNode)
}
}
func bulletsTouchedBadGuy(BadGuy: SKSpriteNode, Laser: SKSpriteNode){
Laser.removeFromParent()
BadGuy.removeFromParent()
}
Can Anyone Please Tell me how can I make so that it would take more than one bullet to make the enemy be removed from parent.

The best way to remove your collided nodes is using the method didFinishUpdate, if you remove or launch a method to remove your node from didBeginContact your game could crash searching a collided node that meanwhile is in the process of being removed..
class BadGuy: SKSpriteNode {
var badGuyBulletCollisionsCounter: Int = 0
init() {
let texture = SKTexture(imageNamed: "CircleBadGuy")
super.init(texture: texture, color: nil, size: texture.size())
...
// fill this part with your BadGuy code
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Declare a global var :
var nodesToRemove = [SKNode]()
In the didBeginContact method:
func didBeginContact(contact: SKPhysicsContact) {
let A : SKPhysicsBody = contact.bodyA
let B : SKPhysicsBody = contact.bodyB
if (A.categoryBitMask == Numbering.Badguy) && (B.categoryBitMask == Numbering.Laser) || (A.categoryBitMask == Numbering.Laser) && (B.categoryBitMask == Numbering.Badguy)
{
badGuy = A.node as! BadGuy
badGuy.badGuyBulletCollisionsCounter += 1
runAction(BadGuyLostSound)
bulletsTouchedBadGuy(badGuy, Laser: B.node as! SKSpriteNode)
}
}
In the bulletsTouchedBadGuy method :
func bulletsTouchedBadGuy(badGuy: BadGuy, laser: SKSpriteNode){
nodesToRemove.append(laser)
if badGuy.badGuyBulletCollisionsCounter == 2 {
nodesToRemove.append(badGuy)
}
}
Finally:
override func didFinishUpdate()
{
nodesToRemove.forEach(){$0.removeFromParent()}
nodesToRemove = [SKNode]()
}

Use the nodes' userData to track it's hit status. When the bullet hits BadGuy, check the nodes' userData to see if it has been hit already. If it has, remove it; if not, indicate that it has :
//Add this when creating BadGuy:
BagGuy.userData = ["HasBeenHit" : false]
func bulletsTouchedBadGuy(BadGuy: SKSpriteNode, Laser: SKSpriteNode){
Laser.removeFromParent()
if BadGuy.userData["HasBeenHit"] == true {
BadGuy.removeFromParent()
} else {
BadGuy.userData["HasBeenHit"] = true
}
or even:
if BadGuy.userData["HasBeenHit"] == true ? BadGuy.removeFromParent() : BadGuy.userData["HasBeenHit"] = true
If you want to move the removeFromParent() to didFinishUpdate() , then simply change the action as follows:
if BadGuy.userData["HasBeenHit"] == true {
BadGuy.userData["HasBeenHitTwice"]
} else {
BadGuy.userData["HasBeenHit"] = true
}
and then in didFinishUpdate() remove all nodes with this value set, or construct an array of nodes to be removed as per the other answer.

Related

Inconsistent contact detection in Swift 3 using SpriteKit

I'm having an issue with contact detection in Swift 3 using SpriteKit. The contact detection is working...sometimes. It seems purely random as to when it fires and when it doesn't.
I have a yellow "bullet" that moves up on the screen to hit a red sprite named targetSprite. The desired behavior is to have the bullet removed when it hits the target, but sometimes it just passes through underneath.
I've found many questions about contact detection not working at all, but I haven't found any dealing with inconsistent detection.
What can I do to fix this?
Here's the code:
import SpriteKit
import GameplayKit
enum PhysicsCategory:UInt32 {
case bullet = 1
case sprite1 = 2
case targetSprite = 4
// each new value should double the previous
}
class GameScene: SKScene, SKPhysicsContactDelegate {
// Create sprites
let sprite1 = SKSpriteNode(color: SKColor.blue, size: CGSize(width:100,height:100))
let targetSprite = SKSpriteNode(color: SKColor.red, size: CGSize(width:100,height:100))
let bullet = SKSpriteNode(color: SKColor.yellow, size: CGSize(width: 20, height: 20))
// show the bullet?
var isShowingBullet = true
// Timers
//var timer:Timer? = nil
var fireBulletTimer:Timer? = nil
// set up bullet removal:
var bulletShouldBeRemoved = false
let bulletMask = PhysicsCategory.bullet.rawValue
override func didMove(to view: SKView) {
// Physics
targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.centerRect.size)
targetSprite.physicsBody?.affectedByGravity = false
bullet.physicsBody = SKPhysicsBody(rectangleOf: bullet.centerRect.size)
bullet.physicsBody?.affectedByGravity = false
// Contact Detection:
targetSprite.physicsBody?.categoryBitMask = PhysicsCategory.targetSprite.rawValue
targetSprite.physicsBody?.contactTestBitMask =
//PhysicsCategory.sprite1.rawValue |
PhysicsCategory.bullet.rawValue
targetSprite.physicsBody?.collisionBitMask = 0 // no collision detection
// bullet physics
bullet.physicsBody?.categoryBitMask = PhysicsCategory.bullet.rawValue
bullet.physicsBody?.contactTestBitMask =
PhysicsCategory.targetSprite.rawValue
bullet.physicsBody?.collisionBitMask = 0 // no collision detection
// execute once:
fireBulletTimer = Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(self.fireBullet),
userInfo: nil,
repeats: false)
// Add sprites to the scene:
self.addChild(sprite1)
self.addChild(bullet)
self.addChild(targetSprite)
// Positioning
targetSprite.position = CGPoint(x:0, y:300)
// Note: bullet and sprite1 are at 0,0 by default
// Delegate
self.physicsWorld.contactDelegate = self
}
func didBegin(_ contact: SKPhysicsContact) {
print("didBegin(contact:))")
//let firstBody:SKPhysicsBody
// let otherBody:SKPhysicsBody
// Use 'bitwise and' to see if both bits are 1:
if contact.bodyA.categoryBitMask & bulletMask > 0 {
//firstBody = contact.bodyA
//otherBody = contact.bodyB
print("if contact.bodyA....")
bulletShouldBeRemoved = true
}
else {
//firstBody = contact.bodyB
//otherBody = contact.bodyA
print("else - if not contacted?")
}
/*
// Find the type of contact:
switch otherBody.categoryBitMask {
case PhysicsCategory.targetSprite.rawValue: print(" targetSprite hit")
case PhysicsCategory.sprite1.rawValue: print(" sprite1 hit")
case PhysicsCategory.bullet.rawValue: print(" bullet hit")
default: print(" Contact with no game logic")
}
*/
} // end didBegin()
func didEnd(_ contact: SKPhysicsContact) {
print("didEnd()")
}
func fireBullet() {
let fireBulletAction = SKAction.move(to: CGPoint(x:0,y:500), duration: 1)
bullet.run(fireBulletAction)
}
func showBullet() {
// Toggle to display or not, every 1 second:
if isShowingBullet == true {
// remove (hide) it:
bullet.removeFromParent()
// set up the toggle for the next call:
isShowingBullet = false
// debug:
print("if")
}
else {
// show it again:
self.addChild(bullet)
// set up the toggle for the next call:
isShowingBullet = true
// debug:
print("else")
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if bulletShouldBeRemoved {
bullet.removeFromParent()
}
}
}
Sorry for the inconsistent indentation, I can't seem to find an easy way to do this...
EDIT:
I have found that using 'frame' instead of 'centerRect' makes the collision area the size of the sprite. For example:
targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.centerRect.size)
should be:
targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.frame.size)
First advice - Do not use NSTimer (aka Timer) in SpriteKit. It is not paired with a game loop and can cause different issues in a different situations. Read more here ( answer posted by LearnCocos2D)
So, do this:
let wait = SKAction.wait(forDuration: 1)
run(wait, completion: {
[unowned self] in
self.fireBullet()
})
What I have noticed is that if I run your code in Simulator, I get the behaviour you have described. didBegin(contact:) is being fired randomly. Still, this is not happening on a device for me, and device testing is what matters.
Now, when I have removed Timer and did the same thing with SKAction(s) everything worked, means contact were detected every time.
Have you tried adding
.physicsBody?.isDynamic = true
.physicsBody?.usesPreciseCollisionDetrction =true
SpriteKit physics engine will calculate collision correctly if you do following:
1) set "usesPreciseCollisionDetection" property to true for bullet's physics body. This will change collision detection algorithm for this body. You can found more information about this property here, chapter "Working with Collisions and Contacts".
2) move your bullet using applyImpulse or applyForce methods. Collision detection will not woking correctly if you move body by changing it's position manually. You can find more information here, chapter "Making Physics Bodies Move".

scoring system counting multiple times

My Goal: To achieve a scoring system that only counts when a level unit is behind the player once and therefore if the player goes back it doesn’t count the same one twice
I currently have a scoring system that adds one to the score label every time I tap my player to go forward in my game, this has its flaws as the player can go backwards and then tap to go forward which would count onto the score again if the player went backwards then forwards.
I came up with the idea to count how many platforms(LevelUnits) are behind the player so that if they go back it won’t count the same platform(LevelUnit) twice when they decide to go forward again.
This is what I came up with:
override func update(_ currentTime: TimeInterval) {
let scoreLabel = childNode(withName: "scoreLabel") as! Score // links the scorelabel with Score class
worldNode.enumerateChildNodes(withName: "levelUnit") {
node, stop in
let nodeLocation:CGPoint = self.convert(node.position, from: self.worldNode) //converts cordinates of level units with the world node.
let player1Location:CGPoint = self.convert(self.thePlayer.position, from: self.worldNode)
if (nodeLocation.y < (player1Location.y) - self.levelUnitHeight ) { // checks to see if the node is behind the player's position
self.countLevelUnits += 1
}
if (self.countLevelUnits > scoreLabel.number) {
while self.countLevelUnits > scoreLabel.number {
scoreLabel.addOneToScore()
}
} else if (self.countLevelUnits < scoreLabel.number) {
// Do Nothing
}
}
}
The only problem is that because they’re constantly behind the player the code counts it multiple times and continues to do so unless I turn off the game. Is there a way to just count the LevelUnits behind the player once for each one that goes behind the players position?
EDIT 1: LEVEL UNIT CLASS
class LevelUnit:SKNode {
//General Level Unit and Object Variables
var imageName:String = ""
var levelUnitSprite:SKSpriteNode = SKSpriteNode()
var levelUnitWidth:CGFloat = 0
var levelUnitHeight:CGFloat = 0
var theType:LevelType = LevelType.normal
let thePlayer:Player = Player(imageNamed: "Frog")
var levelUnitPicker = 0
var levelUnitsSpawned = 0
var levelUnitScored:Bool = false
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init () {
super.init()
}
func setUpLevel(){
imageName = “Wall”
let theSize:CGSize = CGSize(width: levelUnitWidth, height: levelUnitHeight) //defines the size background sprite as defined by the height and width of level unit
let tex:SKTexture = SKTexture(imageNamed: imageName) //defines the testue that the backgroundsprite will have.
levelUnitSprite = SKSpriteNode(texture: tex, color: SKColor.black, size: theSize)
scoredLevelUnit = false
self.addChild(levelUnitSprite) //adds the level unit to the scene
self.name = "levelUnit" //names the whole backgroundsprite under the name level Unit
self.position = CGPoint(x: levelUnitSprite.size.width / 2, y: 0)
//The Level Types Physics Body
if (theType == LevelType.blank) {
levelUnitSprite.physicsBody = SKPhysicsBody(rectangleOf: levelUnitSprite.size)
levelUnitSprite.physicsBody!.categoryBitMask = BodyType.normal.rawValue
levelUnitSprite.physicsBody!.contactTestBitMask = BodyType.normal.rawValue
levelUnitSprite.physicsBody!.isDynamic = false
} else if (theType == LevelType.normal) {
levelUnitSprite.physicsBody = SKPhysicsBody(rectangleOf: levelUnitSprite.size)
levelUnitSprite.physicsBody!.categoryBitMask = BodyType.normal.rawValue
levelUnitSprite.physicsBody!.contactTestBitMask = BodyType.normal.rawValue
levelUnitSprite.physicsBody!.isDynamic = false
} else if (theType == LevelType.floating) {
levelUnitSprite.physicsBody = SKPhysicsBody(rectangleOf: levelUnitSprite.size)
levelUnitSprite.physicsBody!.categoryBitMask = BodyType.floating.rawValue
levelUnitSprite.physicsBody!.contactTestBitMask = BodyType.floating.rawValue
levelUnitSprite.physicsBody!.isDynamic = false
}
}
If you use a custom subclass for your level unit, you can easily add a Bool property scored; when you "score" this object, set scored = true, then next time you can check if scored == true and not score it again
override func update(_ currentTime: TimeInterval) {
let scoreLabel = childNode(withName: "scoreLabel") as! Score // links the scorelabel with Score class
worldNode.enumerateChildNodes(withName: "levelUnit") {
node, stop in
if let levelNode = node as? LevelUnit
let nodeLocation:CGPoint = self.convert(levelNode.position, from: self.worldNode) //converts cordinates of level units with the world node.
let player1Location:CGPoint = self.convert(self.thePlayer.position, from: self.worldNode)
if (nodeLocation.y < (player1Location.y) - self.levelUnitHeight ) { // checks to see if the node is behind the player's position
if !levelNode.levelUnitScored {
scoreLabel.addOneToScore()
levelNode.levelUnitScored = true
}
}
}
}
}

Detect if a SKSpriteNode left the screen at the bottom and call a function

I have tried to find a way to detect if a SKSpriteNode left the screen (I would like to call a Game Over function).
I have declared the node within a function (It is not global if I get that right?) and made and SKAction that moves the Node out of the screen and removes it afterwards.
This is what I came up with:
var node = SKSpriteNode()
let nodeTexture = SKTexture(imageNamed: "node")
nodeTexture.filteringMode = .Nearest
node = SKSpriteNode(texture: nodeTexture)
let nodeFalling = SKAction.moveToY(-70, duration: 1.6)
let nodeRemoving = SKAction.removeFromParent()
node.runAction(SKAction.sequence([nodeFalling, nodeRemoving]))
self.addChild(node)
Now what I need is to call GameOver() if the node left the screen.
I am very thankful for every answer!
In your scene you have to remember the reference for node you want to check and then in update method you just do the following:
if node.position.y < -node.size.height/2.0 {
node.removeFromParent()
gameOver()
}
Edit:
class MyScene: SKScene {
// ....
//here is the list of nodes which you want to check
var nodesToCheck = [SKSpriteNode]()
//here is your spawn function
func spawnNewNode() {
var node = SKSpriteNode()
let nodeTexture = SKTexture(imageNamed: "node")
nodeTexture.filteringMode = .Nearest
node = SKSpriteNode(texture: nodeTexture)
let nodeFalling = SKAction.moveToY(-70, duration: 1.6)
let nodeRemoving = SKAction.removeFromParent()
node.runAction(SKAction.sequence([nodeFalling, nodeRemoving]))
self.addChild(node)
nodesToCheck.append(node)
}
//and here is the update method
override func update(currentTime: NSTimeInterval) {
super.update(currentTime)
// ... every other update logic
for node in nodesToCheck {
if node.position.y < -node.size.height/2.0 {
node.removeFromParent()
gameOver()
}
}
}
func gameOver() {
println("Damn!")
}
}
Dont forget to remove your node from nodeToCheck array when they are no longer scene members.

How to pause an SKSpriteNode, Swift

I created this game using sprite kit. During the game sprite nodes are moving. When the "Game Over" Label pops up, I would like the monster sprite node to stop moving or pause, but the rest of the scene to still move on. I know where to put the code, I just don't know how to write it. This is my monster code.
func addMonster() {
// Create sprite
let monster = SKSpriteNode(imageNamed: "box")
monster.setScale(0.6)
monster.physicsBody = SKPhysicsBody(rectangleOfSize: monster.size)
monster.physicsBody?.dynamic = true
monster.physicsBody?.categoryBitMask = UInt32(monsterCategory)
monster.physicsBody?.contactTestBitMask = UInt32(laserCategory)
monster.physicsBody?.collisionBitMask = 0
monster.name = "box"
var random : CGFloat = CGFloat(arc4random_uniform(320))
monster.position = CGPointMake(random, self.frame.size.height + 20)
self.addChild(monster)
}
EDIT
override func update(currentTime: CFTimeInterval) {
if isStarted == true {
if currentTime - self.lastMonsterAdded > 1 {
self.lastMonsterAdded = currentTime + 3.0
self.addMonster()
}
self.moveObstacle()
} else {
}
self.moveBackground()
if isGameOver == true {
}
}
If I understand your question correctly, you want to pause a single (or small number of) nodes. In that case...
monster.paused = true
Is probably what you want.

SpriteKit: cannot change node position in contact callback

I have a node with a dynamic physics body. And I would like to make it static and change its position when it comes in contact with another body.
I managed to make the body static with the solution provided in this question: Sprite Kit failing assertion: (typeA == b2_dynamicBody || typeB == b2_dynamicBody)
However if I change the position property of the node in one of the contact callback methods (e.g didBeginContact) the new position is not taken into account.
How could I achieve that?
I believe this is a bug in SpriteKit. (I was able to reproduce this problem with SpriteKit 7.1).
Here is a quick workaround:
- (void) didBeginContact:(SKPhysicsContact *)contact
{
contact.bodyB.node.position = CGPointMake(newX, newY);
contact.bodyB.node.physicsBody = contact.bodyB.node.physicsBody; // <-- Add this line
}
JKallio's solution did not work for me (Xcode 7.2.1 on OS X 10.10.5, targeting iOS 8.1). I figured out that I was able to change the position in update: method, so I got around the bug by setting a flag in didBeginContact and changing the position in update:.
#implementation GameScene {
SKNode *_victim;
CGPoint _target;
}
- (void)didMoveToView:(SKView *)view {
_victim = nil;
}
- (void)didBeginContact:(SKPhysicsContact *)contact {
_victim = contact.bodyB.node;
_target = CGPointMake(newX, newY);
}
- (void)update:(CFTimeInterval)currentTime {
if (_victim != nil) {
_victim.position = _target;
_victim = nil;
}
}
#end
Note that _victim serves as both the node and the flag.
My solution is more complicated that JKallio's; try that one before coming for this.
As another option for a workaround, I instead call a method in a SKSpriteNode subclass for the object I want to move, passing in that body. The position gets set correctly.
/* In GameScene.swift */
func didBegin(_ contact: SKPhysicsContact) {
let dotBody: SKPhysicsBody
if contact.bodyA.categoryBitMask == 0b1 {
dotBody = contact.bodyB
} else {
dotBody = contact.bodyA
}
if let dot = dotBody.node as? Dot {
dot.move()
}
}
Here is the Dot class:
import SpriteKit
class Dot : SKSpriteNode {
let dotTex = SKTexture(imageNamed: "dot")
init() {
super.init(texture: dotTex, color: .clear, size: dotTex.size())
self.physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2)
self.physicsBody?.categoryBitMask = 0b1 << 1
self.physicsBody?.contactTestBitMask = 0b1
}
func move() {
let reset = SKAction.run {
self.position.x = 10000
}
self.run(reset)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}