为什么SceneKit的physicsWorld确实为一次碰撞多次射击?



我使用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错误或这实际上是预期的行为?

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

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

这是预期的。这取决于你如何处理碰撞并使两个球体处于不再碰撞的状态。例如,在游戏中,一旦投掷物击中玩家,它就会消失。碰撞已被处理,因此不再触发。


我以以下方式使用您的代码,XCode Version 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
    }
}

最新更新