如何检查节点是否在节点的一定距离内以及何时离开该区域?

How to check if a node is within a certain distance of node and when it left that area?

这是我想要做的: 在游戏中,当玩家接近NPC时,玩家会得到一个与NPC互动的指示器。当玩家在 npc 的一定距离内时,指示器就会出现。当玩家离开 NPC 时它也会消失。

这是我试过的:我原以为这会像使用didBegin/didEnd接触的物理世界方法和NPC周围的透明圆柱体一样简单作为接触触发器。不幸的是,这没有用,因为 didBegin/didEnd 每帧都会调用方法,而不是在建立联系时调用(这就是我认为它的工作方式)。

我也尝试使用 GitHub 中的 PHYKit,但它似乎与我尝试做的不兼容。

我考虑过给 NPC 一个磁场并检查玩家是否在该磁场范围内,但看起来没有办法检查(也许我漏掉了什么)。

我想我也可以使用 hitTestWithSegment,但不明白如何将它应用到我想做的事情上。

似乎也没有任何在线帮助解决这个问题(我已经检查了过去三天,所以如果有什么我愿意看看它是关于什么的)。

问题:如何检查一个节点是否在另一个节点的特定距离内以及它何时离开该区域?

我仍然认为你的物理答案有效。是的,它的工作方式也与我想象的不同,但你必须稍微尝试一下并检查两种方式:

func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact)
    {
        if(data.gameState == .run)
        {
            guard let nodeNameA = contact.nodeA.name else { return }
            guard let nodeNameB = contact.nodeB.name else { return }
            
            if(nodeNameA.prefix(5) == "Explo" && nodeNameB.prefix(5) == "Missi")
            {
                gameControl.enemyMissileHit(vIndex: Int(nodeNameB.suffix(4))!)
            }
            if(nodeNameA.prefix(5) == "Missi" && nodeNameB.prefix(5) == "Explo")
            {
                gameControl.enemyMissileHit(vIndex: Int(nodeNameA.suffix(4))!)
            }
            if(nodeNameA.prefix(5) == "Explo" && nodeNameB.prefix(5) == "Hive ")
            {
                gameControl.enemyHiveHit(vIndex: Int(nodeNameB.suffix(4))!)
            }
            if(nodeNameA.prefix(5) == "Hive " && nodeNameB.prefix(5) == "Explo")
            {
                gameControl.enemyHiveHit(vIndex: Int(nodeNameA.suffix(4))!)
            }
        }
    }

或者 - 在计时器上,定期检查节点距离:

func distance3D(vector1: SCNVector3, vector2: SCNVector3) -> Float
    {
        let x: Float = (vector1.x - vector2.x) * (vector1.x - vector2.x)
        let y: Float = (vector1.y - vector2.y) * (vector1.y - vector2.y)
        let z: Float = (vector1.z - vector2.z) * (vector1.z - vector2.z)
        
        let temp = x + y + z
        return Float(sqrtf(Float(temp)))
    }

更新答案:

这是一个更好的答案,总体上比我的旧答案更有效。它利用 SCNActions 和 physicsWorld 方法以及调用它们的时间。

当节点之间发生第一次接触时,将按以下顺序调用 physicsWorld 方法:

  1. 开始联系
  2. 更新联系人
  3. 结束联系

并且随着节点继续通过另一个节点,它每隔一段时间按此顺序进行:

  1. 开始联系
  2. 结束联系
  3. 更新联系人

一旦该节点完全位于另一个节点内并在该节点内移动,则仅调用此方法。

  • 更新联系人

注意:在我下面的示例中,当提到节点时,我指的是玩家节点和不可见的聊天半径作为 NPC 的另一个节点。

由于 beginsContact 方法仅在两个节点的边都接触时才被调用,因此我将其设置为等于 true 的布尔值。

注意:布尔值是我用来显示玩家交互指示器的值,无论您使用什么,都可以互换。

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
    }
}