Search code examples
swiftsprite-kitgame-physics

Node prematurely removed from scene (Swift game)


I have the following code in Swift for a simple platform game:

import SpriteKit
import GameplayKit

class GameScene: SKScene, SKPhysicsContactDelegate {

    var me : SKSpriteNode?
    var ceiling : SKSpriteNode?
    var studentTimer : Timer?
    var cloudTimer : Timer?
    let meCategory : UInt32 = 0x1 << 1
    let studentCategory : UInt32 = 0x1 << 2
    let cloudCategory : UInt32 = 0x1 << 3
    let groundAndCeilingCategory : UInt32 = 0x1 << 4

    var numberofStudents = 0
    var education = ["edu1", "edu2", "edu3", "edu4"]

    override func didMove(to view: SKView) {

        physicsWorld.contactDelegate = self

        me = childNode(withName: "me") as? SKSpriteNode
        me?.physicsBody?.categoryBitMask = meCategory
        me?.physicsBody?.contactTestBitMask = studentCategory
        me?.physicsBody?.collisionBitMask = groundAndCeilingCategory

        // make me run
        var meRun : [SKTexture] = []
        for number in 1...6 {
            meRun.append(SKTexture(imageNamed: "Armature_Run_\(number)"))
        }
        me?.run(SKAction.repeatForever(SKAction.animate(with: meRun, timePerFrame: 0.1)))

        ceiling = childNode(withName: "ceiling") as? SKSpriteNode
        ceiling?.physicsBody?.categoryBitMask = groundAndCeilingCategory
        ceiling?.physicsBody?.collisionBitMask = meCategory

        startTimers()
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

        me?.physicsBody?.applyForce(CGVector(dx: 0, dy: 20000))

        var meJump : [SKTexture] = []
        for number in 0...9 {
            meJump.append(SKTexture(imageNamed: "Armature_Jump_0\(number)"))
        }
        me?.run(SKAction.animate(with: meJump, timePerFrame: 0.1))
    }

    func createStudent() {

        let student = SKSpriteNode(imageNamed: "student")
        student.physicsBody = SKPhysicsBody(rectangleOf: student.size)
        student.physicsBody?.affectedByGravity = false
        student.physicsBody?.categoryBitMask = studentCategory
        student.physicsBody?.contactTestBitMask = meCategory
        student.physicsBody?.collisionBitMask = 0
        addChild(student)
        student.position = CGPoint(x: size.width / 2 + student.size.width, y: (-size.height / 2) + ((student.size.height)*2))
        let moveLeft = SKAction.moveBy(x: -size.width - student.size.width, y: 0, duration: 4)
        student.run(SKAction.sequence([moveLeft, SKAction.removeFromParent()]))
    }

    func startTimers() {

        studentTimer = Timer.scheduledTimer(withTimeInterval: 4, repeats: true, block: { (timer) in
            if self.numberofStudents < 4 {
            self.createStudent()
            }
        })

        cloudTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true, block: { (timer) in
            self.createCloud()
        })
    }

    func createCloud() {

        let cloud = SKSpriteNode(imageNamed: "cloud")
        cloud.zPosition = -1
        cloud.physicsBody = SKPhysicsBody(rectangleOf: cloud.size)
        cloud.physicsBody?.affectedByGravity = false
        cloud.physicsBody?.collisionBitMask = 0
        cloud.physicsBody?.categoryBitMask = cloudCategory
        addChild(cloud)
        let maxY = size.height / 2 - cloud.size.height / 2
        let minY = CGFloat(0)
        let range = maxY - minY
        let cloudY = maxY - CGFloat(arc4random_uniform(UInt32(range)))
        cloud.position = CGPoint(x: size.width / 2 + cloud.size.width / 2, y: cloudY)
        let moveLeft = SKAction.moveBy(x: -size.width - cloud.size.width, y: 0, duration: 4)
        cloud.run(SKAction.sequence([moveLeft, SKAction.removeFromParent()]))
    }

    func didBegin(_ contact: SKPhysicsContact) {

        if contact.bodyA.categoryBitMask == studentCategory {
            contact.bodyA.node?.removeFromParent()
            numberofStudents += 1
            print(numberofStudents)
            print(education[(numberofStudents)-1])
        }

        if contact.bodyB.categoryBitMask == studentCategory {
            contact.bodyB.node?.removeFromParent()
            numberofStudents += 1
            print(numberofStudents)
            print(education[(numberofStudents)-1])
        }
    }
}

I'm working on what happens when the main character ("me") collides with the "student".

Prior to adding 'physicsWorld.contactDelegate = self' the "student" showed up perfectly, moving across the screen. However, when I add this line of code and run the app, the student is invisible. I believe it is still there somewhere as the print common is still running when the character collides, but it is not visibly colliding with anything.

I removed the contact.bodyB.node?.removeFromParent() and ran it, and indeed the "student" does show again, but obviously does not disappear on contact.

I've looked over this for ages and am sure I'm missing something obvious but cannot work out what it is.


Solution

  • you are not supplying us with all of the information.

    As it is your code works as expected.

    Here is a vid of your code running (I had to use my own images)

    Here is my analysis of what might be wrong.

    I removed all the animation sequence items because they have no bearing on this.

    I notice you are not putting zPositions...tsk tsk tsk. All items should have a zPosition.

    you are not showing us how you define the physicsBody on your "me" sprite. Assumably in the editor? You should include that info next time.

    I had to create the "me" character and position it in code since you don't show us where that comes from either. I also created the physicsBody in code to get it to work.

    Here is the code I used

    me = SKSpriteNode(imageNamed: "orange")
    me.position = CGPoint(x: -400, y: (-size.height / 2) + ((me.size.height) * 2))
    me.zPosition = 10
    addChild(me)
    
    me.physicsBody = SKPhysicsBody(rectangleOf: me.size)
    me.physicsBody?.categoryBitMask = meCategory
    me.physicsBody?.contactTestBitMask = studentCategory
    me.physicsBody?.collisionBitMask = groundAndCeilingCategory
    me.physicsBody?.affectedByGravity = false
    
    func createStudent() {
    
        let student = SKSpriteNode(imageNamed: "apple")
        student.setScale(0.5)
        student.position = CGPoint(x: size.width / 2 + student.size.width, y: (-size.height / 2) + ((student.size.height) * 2))
        student.zPosition = 10
        addChild(student)
    
        student.physicsBody = SKPhysicsBody(rectangleOf: student.size)
        student.physicsBody?.affectedByGravity = false
        student.physicsBody?.categoryBitMask = studentCategory
        student.physicsBody?.contactTestBitMask = meCategory
        student.physicsBody?.collisionBitMask = 0
    
        let moveLeft = SKAction.moveBy(x: -size.width - student.size.width, y: 0, duration: 4)
        student.run(SKAction.sequence([moveLeft, SKAction.removeFromParent()]))
    }
    

    On a side note, it is not necessary to use timers. SpriteKit has a built in feature for this the "update" func.

    enter image description here