Any help would be greatly appreciated. I am relatively new to this and just feel like I beginning to understand coding.
My issue -----
I am having difficult trying to fix a problem with a SpriteKit tutorial that I have been enhancing as a way to hone and improve my skills as a newbie.
I am experiencing multiple contacts when my player “crashes” on the ground.
More than one life is removed, which causes my game to generate a 'Attemped to add a SKNode which already has a parent: ’ error adding the player back to the scene. I have tried player.removeFromParent everywhere I can think of inside my code.
The game works flawlessly as long as I “crash” into “enemies” in the air. Once the player contacts the ground, it’s all over. If I “kill” the player upon contact with the ground, there is no issue, but I want the game to continue as long as the player still has lives whether the contact the ground or and enemy.
I really believe the issue would be fixed if the multiple contact problem could be resolved.
class GameScene: SKScene, SKPhysicsContactDelegate {
// Set up the Texure Atlases
var images = SKSpriteNode()
var textureAtlas = SKTextureAtlas()
var textureArray = [SKTexture]()
var touchingScreen = false
var obstacle = SKSpriteNode()
// Generates a Random number between -350 and 350 (the center of the axis being 0)
let rand = GKRandomDistribution(lowestValue: -350, highestValue: 350)
var timer: Timer?
// This method is called when the Game Launches
override func didMove(to view: SKView) {
// Adds a pixel perfect physicsBody to the player
player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.texture!.size())
player.physicsBody?.categoryBitMask = 1
player.position = CGPoint(x: -400, y: 275)
// Disables the affects of a collisions (+pushing, rotation etc.) on the player when a collision with another SKSpriteNode occurs
player.physicsBody?.allowsRotation = false
player.physicsBody?.collisionBitMask = 0
addChild(player)
physicsWorld.gravity = CGVector(dx: 0, dy: -2)
physicsWorld.contactDelegate = self
timer = Timer.scheduledTimer(timeInterval: 1.5, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)
}
// This method is called when the User touches the Screen
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
touchingScreen = true
}
// This method is called when the User stops touching the Screen
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
touchingScreen = false
}
// This method is called before each frame is rendered
override func update(_ currentTime: TimeInterval) {
// Constrains the player to the scene area
if player.position.y > 275 {
player.position.y = 275
}
// Moves the player up when the screen is being touched
if touchingScreen {
player.physicsBody?.velocity = CGVector(dx: 0, dy: 200
)
}
}
func didBegin(_ contact: SKPhysicsContact) {
// Exits the method if either node is nil (doesn't exist)
guard let nodeA = contact.bodyA.node else { return }
guard let nodeB = contact.bodyB.node else { return }
// Check to see if either node is player and, if so, call the playerHit method and pass in the other node
if nodeA == player {
playerHit(nodeB)
} else if nodeB == player {
playerHit(nodeA)
}
}
func createEnemy() {
// Check for Bonus Creation
checkForBonusCreation()
// Choose a Random Enemy
let pickEnemy = Int(arc4random_uniform(3))
switch pickEnemy {
case 0:
obstacle = SKSpriteNode(imageNamed: "enemy-balloon")
animateBalloon()
case 1:
obstacle = SKSpriteNode(imageNamed: "enemy-bird")
animateBird()
case 2:
obstacle = SKSpriteNode(imageNamed: "enemy-plane")
animatePlane()
default:
return
}
// Positions the enemy
obstacle.zPosition = -2
obstacle.position.x = 768
obstacle.size = (CGSize(width: obstacle.size.width * 0.7, height: obstacle.size.width * 0.7))
// Prevents the obstacle from being spawned too low on the scene
if obstacle.position.y < -150 {
obstacle.position.y = -150
}
addChild(obstacle)
// Adds pixel perfect collision detection to the enemies
obstacle.physicsBody = SKPhysicsBody(texture: obstacle.texture!, size: obstacle.texture!.size())
// Then we set isDynamic to false so grivty will not affect the obstacles
obstacle.physicsBody?.isDynamic = false
// Assigns 1 to it's contactTestBitMask so that it know to detect a collision with the player
obstacle.physicsBody?.contactTestBitMask = 1
// Names the obstacle so we can track collisions properly
obstacle.name = "enemy"
// Spawn an enemy at a random y-axis
obstacle.position.y = CGFloat(rand.nextInt())
// Moves the obstacles across to and off the left hand side of the screen over 9 seconds athen removes thier nodes so they don't chew up memory
let move = SKAction.moveTo(x: -768, duration: 9)
let remove = SKAction.removeFromParent()
let action = SKAction.sequence([move, remove])
obstacle.run(action)
}
func playerHit(_ node: SKNode) {
if node.name == "enemy" {
player.removeFromParent()
node.removeFromParent()
lives -= 1
balloonPop()
showLivesRemaining()
} else if node.name == "ground" {
player.removeFromParent()
lives -= 1
balloonPop()
showLivesRemaining()
}
}
func balloonPop() {
player.removeFromParent()
}
func showLivesRemaining() {
if lives >= 3 {
lives = 3
} else if lives <= 0 {
lives = 0
player.removeFromParent()
}
}
// Jump to the restart or quit scene
func restartGame() {
player.removeFromParent()
let wait = SKAction.wait(forDuration: 2.0)
let showPlayer = SKAction.run {
player.position = CGPoint(x: -400, y: 275)
self.addChild(player)
}
let sequence = SKAction.sequence([wait, showPlayer])
run(sequence)
}
// Displays GAME OVER and takes the player to the "Restart or Quit" choice scene
func endGame() {
player.removeFromParent()
let gameOver = SKSpriteNode(imageNamed: "game-over")
gameOver.zPosition = 15
addChild(gameOver)
// Waits 2 seconds and fade into the Restart Scene
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
if let scene = RestartScene(fileNamed: "RestartScene") {
scene.scaleMode = .aspectFill
self.view?.presentScene(scene, transition: SKTransition.crossFade(withDuration: 1.5))
}
}
}
}
What a lot of games do is give a buffer period after a player is hit where they flash the player or turn the player red for a couple of seconds. during this time the player is not prone to more hits and gives the player an opportunity to move to safety.
the contact detection code fires at up to 60 times a second. When your player hits an enemy you remove that enemy, making more contact to the enemy not possible. However when the player hits the ground the ground does not get removed and thus player keeps losing lives. after the player hits the ground try bumping the player up a 100px away from the ground and see if that stops the multiple contact issue.