【问题标题】:How to check if a node is within a certain distance of node and when it left that area?如何检查节点是否在节点的一定距离内以及何时离开该区域?
【发布时间】:2021-08-07 01:22:41
【问题描述】:

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

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

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

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

我认为我也可以使用 hitTestWithSegment,但不明白如何将其应用于我正在尝试做的事情。

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

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

【问题讨论】:

    标签: swift scenekit


    【解决方案1】:

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

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

    【讨论】:

    • 我不太确定第一个代码段如何回答我的问题。我还可以看到正在使用的第二个代码段的示例吗?话虽如此,你和我一样应该使用物理方法。我真的想出了一个答案。我可能会发布它,因为它并不完全有效,但它现在可以完成工作。
    • 这只是我如何做的一个例子。如果导弹与我的“透明”圆柱体版本接触,那么我会得到一个要处理的事件。第二个例子是如果 distance3D(node1, node2)
    【解决方案2】:

    更新答案:

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

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

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

    当节点继续通过另一个节点时,它每隔一段时间就会按此顺序通过:

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

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

    • 更新联系方式

    注意:在下面的示例中,当提到节点时,我指的是玩家节点和不可见的 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
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-31
      • 1970-01-01
      相关资源
      最近更新 更多