SpriteKit游戏在多次切换场景后冻结

时间:2015-09-07 20:24:15

标签: swift sprite-kit

我的一个精灵在游戏场景和场景中的游戏多次切换后冻结。一旦我死了并重新开始大约6-7次,我的敌人精灵就会出现。不再响应我的设备的倾斜(如果它,它需要很长时间)。使用屏幕操纵杆时,我的播放器仍能正常移动。我的FPS读取60的整个时间。

class GameScene: SKScene, SKPhysicsContactDelegate {

    let joyStickSprite = SKSpriteNode(imageNamed: "flatLight09")
    let playerSprite = SKSpriteNode(imageNamed: "p3_front")
    let enemySprite = SKSpriteNode(imageNamed: "elementExplosive001")
    let coinSprite = SKSpriteNode(imageNamed: "gold_1")
    var left = false
    var right = false


    var enemyCount = 0
    var randomNextCoin = Int(arc4random_uniform(5)+1)

    var motionManager = CMMotionManager()
    var destY:CGFloat  = 0.0

    struct CollisionCategoryBitmask {
        static let Player: UInt32 = 0x00
        static let Enemy: UInt32 = 0x01
        static let Floor: UInt32 = 0x02
        static let Coin: UInt32 = 0x03
    }

    var background = SKSpriteNode(imageNamed: "blue_land")

    override func didMoveToView(view: SKView) {

        self.physicsWorld.contactDelegate = self


        createBackground()
        createPlayer()
        createJoyStick()
        createScreenBorder()
        createEnemy()


        motionManager.accelerometerUpdateInterval = 0.1
        motionManager.startAccelerometerUpdatesToQueue(NSOperationQueue.currentQueue(), withHandler: {
            (accelerometerData: CMAccelerometerData!, error: NSError!) in


                var currentY = self.enemySprite.position.y

                let acceleration = accelerometerData.acceleration
                self.destY = (CGFloat(acceleration.y) * 0.75) + (self.destY * 0.25)

            })
        }



    override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
        /* Called when a touch begins */



        for touch in (touches as! Set<UITouch>) {
            let location = touch.locationInNode(self)

            let touchedNode = self.nodeAtPoint(location)

            if let name = touchedNode.name
            {
                if name == "joyStick"
                {

                    if location.x < joyStickSprite.position.x {
                        left = true
                        right = false
                    }

                    else {
                        right = true
                        left = false
                    }
                }
            }
        }

    }

    override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {

        left = false
        right = false
    }

    func presentGameOver () {

        removeAllChildren()

        let newScene = GameScene(size: size)
        newScene.scaleMode = scaleMode
        let reveal = SKTransition.flipHorizontalWithDuration(0.5)
        view?.presentScene(newScene, transition: reveal)
        enemySprite.physicsBody?.dynamic = false
    }

    func createBackground () {

        background.position = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
        background.size.height = self.size.height

        addChild(background)

    }


    func createPlayer () {

        playerSprite.position = CGPoint(x: self.size.width / 2, y: playerSprite.size.height/2)
        playerSprite.physicsBody = SKPhysicsBody(circleOfRadius: playerSprite.size.width / 2)
        playerSprite.physicsBody?.dynamic = false
        playerSprite.physicsBody?.categoryBitMask = CollisionCategoryBitmask.Player
        playerSprite.physicsBody?.collisionBitMask = 0
        playerSprite.physicsBody?.contactTestBitMask = CollisionCategoryBitmask.Enemy | CollisionCategoryBitmask.Coin


        addChild(playerSprite)

    }

    func movePlayerLeft () {
        let moveLeft = SKAction.moveByX(-10, y: 0, duration: 1)
        playerSprite.runAction(moveLeft)
    }

    func movePlayerRight () {
        let moveRight = SKAction.moveByX(10, y: 0, duration: 1)
        playerSprite.runAction(moveRight)
    }

    func createEnemy () {

        var randomX = Int(arc4random_uniform(600))
        var randomXCG = CGFloat(randomX)


        enemyCount += 1

        enemySprite.position = CGPoint(x: randomXCG, y: self.size.height)
        enemySprite.physicsBody = SKPhysicsBody(circleOfRadius: enemySprite.size.width / 2)
        enemySprite.physicsBody?.dynamic = true
        enemySprite.physicsBody?.allowsRotation = true
        enemySprite.physicsBody?.restitution = 0.0
        enemySprite.physicsBody?.friction = 0.0
        enemySprite.physicsBody?.angularDamping = 0.0
        enemySprite.physicsBody?.linearDamping = 0.0
        enemySprite.physicsBody?.affectedByGravity = true

        enemySprite.physicsBody?.categoryBitMask = CollisionCategoryBitmask.Enemy
        enemySprite.physicsBody?.collisionBitMask = CollisionCategoryBitmask.Floor


        println("enemey count \(enemyCount)")
        println("next coin \(randomNextCoin)")

        addChild(enemySprite)
    }

    func createCoins () {

        var randomX = Int(arc4random_uniform(600))
        var randomXCG = CGFloat(randomX)

        randomNextCoin = Int(arc4random_uniform(10))
        enemyCount = 0

        coinSprite.size.height = playerSprite.size.height/2
        coinSprite.size.width = coinSprite.size.height
        coinSprite.position = CGPoint(x: randomXCG, y: self.size.height)
        coinSprite.physicsBody = SKPhysicsBody(circleOfRadius: enemySprite.size.width/2)
        coinSprite.physicsBody?.dynamic = true
        coinSprite.physicsBody?.affectedByGravity = true
        coinSprite.physicsBody?.categoryBitMask = CollisionCategoryBitmask.Coin

        addChild(coinSprite)
    }


    func createJoyStick () {

        joyStickSprite.setScale(0.4)
        joyStickSprite.position = CGPoint(x: self.size.width/1.1, y: joyStickSprite.size.height)
        joyStickSprite.name = "joyStick"
        joyStickSprite.userInteractionEnabled = false

        addChild(joyStickSprite)
    }

    func updateEnemyPosition () {

        if enemySprite.size.height > enemySprite.position.y {

            enemySprite.position.x =  enemySprite.position.x + destY*20
        }


    }

    func didBeginContact(contact: SKPhysicsContact) {

        let firstNode = contact.bodyA.node as! SKSpriteNode
        let secondNode = contact.bodyB.node as! SKSpriteNode

        if (contact.bodyA.categoryBitMask == CollisionCategoryBitmask.Player) &&
            (contact.bodyB.categoryBitMask == CollisionCategoryBitmask.Enemy) {

                let transition = SKTransition.revealWithDirection(SKTransitionDirection.Down, duration: 1.0)

                let scene = SecondScene(size: self.scene!.size)
                scene.scaleMode = SKSceneScaleMode.AspectFill

                self.scene!.view!.presentScene(scene, transition: transition)
                }

        if (contact.bodyA.categoryBitMask == CollisionCategoryBitmask.Player) &&
            (contact.bodyB.categoryBitMask == CollisionCategoryBitmask.Coin) {

                coinSprite.removeFromParent()
        }
    }

    func createScreenBorder () {


        // 1. Create a physics body that borders the screen
        let borderBody = SKPhysicsBody(edgeFromPoint: CGPointMake(0.0, 0.0), toPoint: CGPointMake(self.size.width, 0.0))
        // 2. Set the friction of that physicsBody to 0
        borderBody.friction = 0

        borderBody.categoryBitMask = CollisionCategoryBitmask.Floor

        // 3. Set physicsBody of scene to borderBody
        self.physicsBody = borderBody
    }



    override func update(currentTime: CFTimeInterval) {

        //detect where on joystick player is touching
        if left == true {
            movePlayerLeft()
        }

        if right == true {
            movePlayerRight()
        }

        //move player to other side when going off screen
        if playerSprite.position.x < -20.0 {
            playerSprite.position = CGPoint(x: self.size.width + 20.0, y: playerSprite.position.y)
        } else if (playerSprite.position.x > self.size.width + 20.0) {
            playerSprite.position = CGPoint(x: -20.0, y: playerSprite.position.y)
        }


        //remove enemeny if off screen
        if enemySprite.position.x < -20.0 || enemySprite.position.x > self.size.width + 20.0 {
            self.enemySprite.removeFromParent()
            createEnemy()
        }

        if randomNextCoin == enemyCount {
            println("coin dropped")
            coinSprite.removeFromParent()
            createCoins()
        }

        updateEnemyPosition()


    }
}

有人有任何建议吗?

1 个答案:

答案 0 :(得分:2)

通过引用self.destY闭包中的motionManager.startAccelerometerUpdatesToQueue,您将创建一个强大的参考周期。来自文档,

  

如果为类实例的属性分配闭包,并且闭包通过引用实例或其成员来捕获该实例,则将在闭包和实例之间创建一个强引用循环。

此参考周期阻止您的场景被释放。由于您没有停止运动管理器,因此在转换场景时,旧管理器(复数)仍在运行。这可能导致当前场景在多次转换后冻结。

Swift使用捕获列表来避免强引用周期,其中捕获列表的格式为

{ [ /* weak or unowned + object, ...*/ ]
     /* parameters */ in

}

您可以将闭包中的捕获定义为 unowned weak 。来自文档,

  

当闭包和它捕获的实例将始终相互引用时,将闭包中的捕获定义为无主引用,并且将始终同时取消分配。

  

相反,当捕获的引用在将来的某个时刻变为零时,将捕获定义为弱引用。弱引用始终是可选类型,并且在它们引用的实例被释放时自动变为nil。这使您可以检查它们在闭包体内是否存在。

以下是如何向加速度计处理程序添加捕获列表的示例:

    motionManager.accelerometerUpdateInterval = 0.1
    motionManager.startAccelerometerUpdatesToQueue(NSOperationQueue.currentQueue()) {
        [unowned self] accelerometerData, error in

            var currentY = self.enemySprite.position.y
            let acceleration = accelerometerData.acceleration
            self.destY = (CGFloat(acceleration.y) * 0.75) + (self.destY * 0.25)
    }

最后,在转换场景之前停止加速度计更新是一个好主意

    override func willMoveFromView(view: SKView) {
        motionManager.stopAccelerometerUpdates()
    }