更新答案:
这是一个更好的答案,总体上比我的旧答案更有效。它利用带有physicsWorld 方法的SCNActions 以及何时调用它们。
当节点之间发生第一次接触时,physicsWorld 方法按以下顺序调用:
- 开始联系
- 更新联系方式
- 结束联系
当节点继续通过另一个节点时,它每隔一段时间就会按此顺序通过:
- 开始联系
- 结束联系
- 更新联系方式
一旦节点完全在另一个节点内并在节点内移动,则仅调用此方法。
注意:在下面的示例中,当提到节点时,我指的是玩家节点和不可见的 chatRadius 作为 NPC 的另一个节点。
由于仅在两个节点的边缘接触时才调用 beginContact 方法,因此我将其设置为布尔值等于 true。
注意:bool 值是我用来为玩家显示交互指示符的值,无论你使用什么都可以互换。
var interaction: Bool = false
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
let node = (contact.nodeA.name == "player") ? contact.nodeB : contact.nodeA
if node.physicsBody!.categoryBitMask == control.CategoryInteraction && interaction {
interaction = true
}
}
endContact 方法几乎总是在 didBegin 联系方法之后调用,所以我让它等待一段时间,然后再将布尔值设置回 false。
注意:我让操作等待调用 updateContact 方法。
func physicsWorld(_ world: SCNPhysicsWorld, didEnd contact: SCNPhysicsContact) {
let node = (contact.nodeA.name == "player") ? contact.nodeB : contact.nodeA
if node.physicsBody!.categoryBitMask == control.CategoryInteraction && ViewManager.shared.interaction {
let wait = SCNAction.wait(duration: 0.1)
let leave = SCNAction.run { _ in
self.interaction = false
}
let sequence = SCNAction.sequence([wait, leave])
player.runAction(sequence, forKey: "endInteraction")
}
}
当节点在边缘移动或在另一个节点内移动时,总是会调用更新方法。所以我让它通过删除将 bool 值设置为 false 的操作来对抗 endContact 方法。
func physicsWorld(_ world: SCNPhysicsWorld, didUpdate contact: SCNPhysicsContact) {
if player.action(forKey: "endInteraction") != nil {
player.removeAction(forKey: "endInteraction")
}
}
endContact 方法在两个节点停止接触时也会被调用(谁会猜到),并且在我的测试中,它始终是最后一个被调用的方法。
旧答案
这根本不是一个完美的答案,它仍有改进的空间,但现在,我就是这样做的。
对于第一个代码部分,我正在检查玩家是否与聊天半径发生碰撞(或者在 NPC 的一定距离内)。如果玩家在范围内,则显示指示器以与 NPC 进行交互并将计数加 1。
var count = 0
var previousCount = 0
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
let node = (contact.nodeA.name == "Player") ? contact.nodeB : contact.nodeA
if node.physicsBody!.categoryBitMask == CategoryInteraction {
// Show chat indicator here.
count += 1
if count > 100 {
count = 0
}
}
}
对于第二个代码部分,我通过使previousCount 等于count 来检查玩家是否离开了聊天范围。如果count等于previousCount,那么玩家已经离开了聊天范围,所以隐藏了交互指示器。
func physicsWorld(_ world: SCNPhysicsWorld, didEnd contact: SCNPhysicsContact) {
if contact.nodeA.name != "chatRadius" && contact.nodeB.name != "chatRadius" { return }
if previousCount != count {
previousCount = count
} else {
// Hide chat indicator here.
count = 0
previousCount = 0
}
}