为什么SceneKit的physicsWorld对于单次碰撞多次触发了BangContact?

时间:2016-09-10 08:23:34

标签: swift scenekit skphysicsworld

我正在使用SceneKit的physicsBody系统来检测对象之间的碰撞,并获得一些非常奇怪的结果。为了说明,我有一个最小的例子,它产生两个带有运动物理实体的球体,并将它们以直线移动,以便它们短暂重叠。

我希望看到physicsWorld(:didBeginContact :)只调用一次,当球体首次重叠时,physicsWorld(:didEndContact :)在它们停止重叠时调用一次。相反,我看到每个函数被调用了25次!

以下是重现的代码:在Xcode 8.0中,使用“Game”模板创建一个全新的项目。用以下代码替换GameViewController.swift的内容:

import UIKit
import SceneKit

class GameViewController: UIViewController, SCNSceneRendererDelegate, SCNPhysicsContactDelegate {

    var scnScene: SCNScene!
    var scnView: SCNView!
    var cameraNode: SCNNode!

    var nodeA: SCNNode!
    var nodeB: SCNNode!

    var countBeginnings: Int = 0
    var countEndings: Int = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        setupScene()
        setupNodes()
    }

    func setupScene() {
        // create a new SCNScene and feed it to the view
        scnView = self.view as! SCNView
        scnScene = SCNScene()
        scnView.scene = scnScene

        // assign self as SCNView delegate to get access to render loop
        scnView.delegate = self
        // assign self as contactDelegate to handle collisions
        scnScene.physicsWorld.contactDelegate = self

        // create the camera and position it at origin
        cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        cameraNode.position = SCNVector3Zero
        scnScene.rootNode.addChildNode(cameraNode)

        // tell scnView to update every frame
        scnView.isPlaying = true
    }

    func setupNodes() {
        // create two spheres with physicsBodies, one inside the other
        nodeA = SCNNode()
        nodeA.name = "Node A"
        nodeA.geometry = SCNSphere(radius: 1.0)
        nodeA.geometry!.firstMaterial?.diffuse.contents = UIColor.yellow.withAlphaComponent(0.6)
        // expected behavior
        // nodeA.position = SCNVector3(x: 0.0, y: -0.8, z: -10.0)
        // weird behavior
        nodeA.position = SCNVector3(x: 0.0, y: -0.9, z: -10.0)
        nodeA.physicsBody = SCNPhysicsBody(type: .kinematic, shape: SCNPhysicsShape(geometry: nodeA.geometry!, options: nil))
        scnScene.rootNode.addChildNode(nodeA)

        nodeB = SCNNode()
        nodeB.name = "Node B"
        nodeB.geometry = SCNSphere(radius: 0.5)
        nodeB.geometry!.firstMaterial?.diffuse.contents = UIColor.red
        nodeB.position = SCNVector3(x: -2.0, y: 0.0, z: -10.0)
        nodeB.physicsBody = SCNPhysicsBody(type: .kinematic, shape: SCNPhysicsShape(geometry: nodeB.geometry!, options: nil))
        scnScene.rootNode.addChildNode(nodeB)

        // node A can collide with node B but not the other way around
        nodeA.physicsBody!.categoryBitMask = 2
        nodeB.physicsBody!.categoryBitMask = 1
        nodeA.physicsBody!.contactTestBitMask = 1
    }

    func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
        countBeginnings += 1
        print("(" + String(countBeginnings) + ") " + contact.nodeA.name! + " began contact with " + contact.nodeB.name!)
    }
    func physicsWorld(_ world: SCNPhysicsWorld, didEnd contact: SCNPhysicsContact) {
        countEndings += 1
        print("(" + String(countEndings) + ") " + contact.nodeA.name! + " ended contact with " + contact.nodeB.name!)
    }

    var frameNumber = 0
    func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
        nodeB.position.x += 0.01
        nodeB.position.y -= 0.01
    }

}

还有其他奇怪的事情发生了。如果我稍微改变其中一个球体的初始位置,将y位置从-0.9移动到-0.8:

nodeA.position = SCNVector3(x: 0.0, y: -0.8, z: -10.0)

现在我得到了预期的行为,一个电话开始,一个电话结束!稍微不同的碰撞角度会导致完全不同的行为。

这可能是一个SceneKit错误,还是这实际上是预期的行为?

1 个答案:

答案 0 :(得分:1)

SCNRenderer在每一帧运行物理模拟,调用renderer(_:didSimulatePhysicsAtTime:)SCNSceneRendererDelegate方法。

在两个球体交叉的过程中,将会渲染几个帧,每次运行物理模拟时,都会检测到碰撞。

这是预期的。由你来处理碰撞并将两个球体置于不再碰撞的状态。例如,在游戏中,一旦射弹击中玩家,它就会消失。碰撞被处理,因此不再发射。

我使用XCode版本8.0 beta 5(8S193k)作为OS X应用程序以下列方式使用您的代码。我在控制台中看到以下跟踪。 begin和end方法只调用一次!

(1) Node A began contact with Node B
(1) Node A ended contact with Node B


import SceneKit
import QuartzCore

class GameViewController: NSViewController, SCNSceneRendererDelegate, SCNPhysicsContactDelegate {

    @IBOutlet weak var gameView: GameView!

    // MARK: Properties

    var cameraNode: SCNNode!

    var nodeA: SCNNode!
    var nodeB: SCNNode!

    var countBeginnings: Int = 0
    var countEndings: Int = 0

    // MARK: Initialization

    override func awakeFromNib(){
        super.awakeFromNib()

        // create a new scene
        let scene = SCNScene(named: "art.scnassets/scene.scn")!

        // create and add a camera to the scene
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        scene.rootNode.addChildNode(cameraNode)

        // place the camera
        cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)

        // create and add a light to the scene
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light!.type = SCNLightTypeOmni
        lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
        scene.rootNode.addChildNode(lightNode)

        // create and add an ambient light to the scene
        let ambientLightNode = SCNNode()
        ambientLightNode.light = SCNLight()
        ambientLightNode.light!.type = SCNLightTypeAmbient
        ambientLightNode.light!.color = NSColor.darkGray
        scene.rootNode.addChildNode(ambientLightNode)

        // set the scene to the view
        self.gameView!.scene = scene

        // allows the user to manipulate the camera
        self.gameView!.allowsCameraControl = true

        // show statistics such as fps and timing information
        self.gameView!.showsStatistics = true

        // configure the view
        self.gameView!.backgroundColor = NSColor.black

        self.gameView!.delegate = self

        setupScene()
        setupNodes()
    }

    func setupScene() {

        // assign self as contactDelegate to handle collisions
        self.gameView!.scene?.physicsWorld.contactDelegate = self

        // create the camera and position it at origin
        cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        cameraNode.position = SCNVector3Zero
        self.gameView!.scene?.rootNode.addChildNode(cameraNode)

        // tell scnView to update every frame
        self.gameView.isPlaying = true
    }

    func setupNodes() {
        // create two spheres with physicsBodies, one inside the other
        nodeA = SCNNode()
        nodeA.name = "Node A"
        nodeA.geometry = SCNSphere(radius: 1.0)
        nodeA.geometry!.firstMaterial?.diffuse.contents = NSColor.yellow.withAlphaComponent(0.6)
        nodeA.position = SCNVector3(x: 0.0, y: -0.8, z: -10.0)
        nodeA.physicsBody = SCNPhysicsBody(type: .kinematic, shape: SCNPhysicsShape(geometry: nodeA.geometry!, options: nil))
        self.gameView!.scene?.rootNode.addChildNode(nodeA)

        nodeB = SCNNode()
        nodeB.name = "Node B"
        nodeB.geometry = SCNSphere(radius: 0.5)
        nodeB.geometry!.firstMaterial?.diffuse.contents = NSColor.red
        nodeB.position = SCNVector3(x: -2.0, y: 0.0, z: -10.0)
        nodeB.physicsBody = SCNPhysicsBody(type: .kinematic, shape: SCNPhysicsShape(geometry: nodeB.geometry!, options: nil))
        self.gameView!.scene?.rootNode.addChildNode(nodeB)

        // node A can collide with node B but not the other way around
        nodeA.physicsBody!.categoryBitMask = 2
        nodeB.physicsBody!.categoryBitMask = 1
        nodeA.physicsBody!.contactTestBitMask = 1
    }

    // MARK: SCNPhysicsContactDelegate

    func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
        countBeginnings += 1
        print("(" + String(countBeginnings) + ") " + contact.nodeA.name! + " began contact with " + contact.nodeB.name!)
    }
    func physicsWorld(_ world: SCNPhysicsWorld, didEnd contact: SCNPhysicsContact) {
        countEndings += 1
        print("(" + String(countEndings) + ") " + contact.nodeA.name! + " ended contact with " + contact.nodeB.name!)
    }

    // MARK: SCNSceneRendererDelegate

    var frameNumber = 0
    func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
        nodeB.position.x += 0.01
        nodeB.position.y -= 0.01
    }
}