当我只创建一个时,为什么会有多个 SKSpriteNode 和 SKAudioNode 对象?

Why are there multiple SKSpriteNode and SKAudioNode objects when I only created one of each?

我创建了一个 SKSpriteNode 对象,但是创建了多个对象,显示为它们的字符串。来自 SKAudioNode 对象的声音也被复制多次以产生回响效果。

下面是代码。当我在 didBegin 回调方法中添加代码以将 physicsBody 对象的固定属性设置为 true 时,我注意到它这样做了。

这是截图。注意有多个爆炸 SKSpriteNode 对象。我只为爆炸创建了一个 SKSpriteNode 对象。

我怀疑我可以更改某个设置以禁用此效果。

import UIKit
import SpriteKit

class GameScene: SKScene {

    let player = SKSpriteNode(imageNamed: "player")
    var moveRate: CGFloat!

    override func didMove(to view: SKView) {

        physicsWorld.contactDelegate = self

        if UIDevice.current.userInterfaceIdiom == .phone {
            moveRate = 5
        } else {
            moveRate = 15
        }

        player.physicsBody = SKPhysicsBody(rectangleOf: player.size)
        player.physicsBody?.isDynamic = true
        player.physicsBody?.affectedByGravity = false
        player.physicsBody?.categoryBitMask = 0b00001
//        player.physicsBody?.collisionBitMask = 0b00001
        player.physicsBody?.contactTestBitMask = 0b00001

        player.position = CGPoint(x: 20 + player.size.width/2, y: view.frame.height / 2)

        addChild(player)

        let carEngineStart = SKAudioNode(fileNamed: "car_engine_running")

        addChild(carEngineStart)

        run(SKAction.repeatForever(
            SKAction.sequence([
                SKAction.run(addCompetitor),
                SKAction.wait(forDuration: 4)
                ])
        ))

    }

    override func update(_ currentTime: TimeInterval) {

        let internalRollSign = TrialSpriteKit.sign(internalRoll)

        switch internalRollSign {
        case .zero:
            break
        case .positive:
            if player.position.y < self.size.height {
                player.position.y += moveRate
            }
        case .negative:
            if player.position.y > 0 {
                player.position.y -= moveRate
            }
        }

    }

    enum Car: String, CaseIterable {

        case blue = "blue"
        case green = "green"
        case orange = "orange"
        case purple = "purple"
        case utili = "utili"
        case white = "white"
        case yellow = "yellow"

        static func random<G: RandomNumberGenerator>(using generator: inout G) -> Car {
            return Car.allCases.randomElement(using: &generator)!
        }

        static func random() -> Car {
            var g = SystemRandomNumberGenerator()
            return Car.random(using: &g)
        }

    }

    func random() -> CGFloat {
        return CGFloat(Float(arc4random()) / /* 0xFFFFFFFF */ 4294967296)
    }

    func random(min: CGFloat, max: CGFloat) -> CGFloat {
        return random() * (max - min) + min
    }

    func addCompetitor() {

        // Create sprite
        let carString = Car.random().rawValue
        let car = SKSpriteNode(imageNamed: carString)

        car.physicsBody = SKPhysicsBody(rectangleOf: car.size) // 1
        car.physicsBody?.isDynamic = true
        car.physicsBody?.affectedByGravity = false
//        car.physicsBody?.categoryBitMask = 0b00001
        car.physicsBody?.collisionBitMask = 0b00001
        car.physicsBody?.contactTestBitMask = 0b00001

        // Determine where to spawn the car along the Y axis
        let actualY = random(min: car.size.height/2, max: size.height - car.size.height/2)

        // Position the car slightly off-screen along the right edge,
        // and along a random position along the Y axis as calculated above
        car.position = CGPoint(x: size.width + car.size.width/2, y: actualY)

        // Add the car to the scene
        addChild(car)

        // Determine speed of the car
        let actualDuration = random(min: CGFloat(2.0), max: CGFloat(4.0))

        // Create the actions
        let actionMove = SKAction.move(to: CGPoint(x: -car.size.width/2, y: actualY), duration: TimeInterval(actualDuration))
        let actionMoveDone = SKAction.removeFromParent()
        car.run(SKAction.sequence([actionMove, actionMoveDone]))

    }

}

extension GameScene: SKPhysicsContactDelegate {

    func didBegin(_ contact: SKPhysicsContact) {

        contact.bodyA.pinned = true
        player.physicsBody?.pinned = true

        let explosion = SKSpriteNode(imageNamed: "explosion")

        explosion.position = contact.contactPoint

        addChild(explosion)

        run(
            SKAction.sequence(
                [
                    SKAction.playSoundFileNamed("car_explosion", waitForCompletion: true),
                    SKAction.run({
                        explosion.removeFromParent()
                        contact.bodyB.node?.removeFromParent()
                    }),
                    SKAction.wait(forDuration: 1),
                    SKAction.run({
                        self.player.zRotation = 0
                        self.player.position = CGPoint(x: 20 + self.player.size.width/2, y: self.view!.frame.height / 2)
                        self.player.physicsBody?.pinned = false
                    })
                ]
            )
        )

     }

}

你的问题有多个节点的原因。

I noticed it doing this when I added the code to set the pinned properties of the physicsBody objects to true in the didBegin callback method.

根据 SpriteKit 文档,当您将 pinned 属性 设置为 true 且父节点具有物理体时,这两个体被视为通过销关节连接.

因为两个物体是相连的,即使没有碰撞,也会重复接触,导致重复调用didBegin函数。每次调用 didBegin 都会创建一个新的精灵节点。您的游戏调用 didBegin 的次数比您预期的要多,因此您最终得到的精灵节点比您预期的要多。

一般的解决方案是只有在发生碰撞时才创建爆炸精灵节点,并且每次碰撞只创建一次精灵节点。