如何获取两个 SKSpriteNode 对象之间的距离以用于势场算法?

How to get the distance between two SKSpriteNode objects for use in Potential Fields algorithm?

首先作为免责声明,我完全知道 SpriteKit 物理体能够收集碰撞。这不是我想要做的。我正在尝试构建一个势场 AI 并进行吸引力和排斥矢量的那部分计算需要两个物体之间的距离。

寻找距离我认为只需使用距离公式即可,但由于某种原因,我得到的距离结果非常不稳定,所以我想咨询一下是否有人能发现我做错了什么.

下面我使用了一个简单的距离公式,点的位置与它们的中心对齐。

func getDistanceBetweenTwoObjects(sourceNode:SKSpriteNode, destinationNode:SKSpriteNode) -> CGFloat{
    var centerSourceNode = CGPoint(x: sourceNode.position.x, y: sourceNode.position.y)
    centerSourceNode.x += sourceNode.size.width/2
    centerSourceNode.y += sourceNode.size.width/2

    var centerDestNode = CGPoint(x: destinationNode.position.x, y: destinationNode.position.y)
    centerDestNode.x += destinationNode.size.width/2
    centerDestNode.y += destinationNode.size.width/2
    var distance = sqrt(pow((centerSourceNode.x - centerDestNode.x), 2) + pow((centerSourceNode.y - centerDestNode.y), 2))
    return distance
}

这是我用来创建吸引力向量的函数。

func getAttractionVector(node:SKSpriteNode, destinationNode:SKSpriteNode, spread:CGFloat, strength:CGFloat) -> (xSpeed: CGFloat, ySpeed: CGFloat){
    var sourceNodeRadius = sqrt(pow(node.size.width/2,2))
    var destNodeRadius = sqrt(pow(destinationNode.size.width/2,2))
    var radius = sourceNodeRadius + destNodeRadius
    var distance = getDistanceBetweenTwoObjects(node, destinationNode: destinationNode)
    if distance < radius{
        return (0,0)
    }
    var angle = atan((destinationNode.position.y - node.position.y)/(destinationNode.position.x - node.position.x))
    var xSpeed:CGFloat = 0
    var ySpeed:CGFloat = 0

    if radius <= distance && distance <= spread + radius{
        xSpeed = strength * (distance - radius) * cos(angle)
        ySpeed = strength * (distance - radius) * sin(angle)
    }else if distance > spread + radius{
        xSpeed = strength * spread * cos(angle)
        ySpeed = strength * spread * sin(angle)
    }
    return (xSpeed, ySpeed)
}

我使用类似的数学计算排斥向量:

    func getRepelVector(node:SKSpriteNode, destinationNode:SKSpriteNode, spread:CGFloat, strength:CGFloat) -> (xSpeed: CGFloat, ySpeed: CGFloat){

    var sourceNodeRadius = sqrt(pow(node.size.width/2,2))
    var destNodeRadius = sqrt(pow(destinationNode.size.width/2,2))
    var radius = sourceNodeRadius + destNodeRadius
    var distance = getDistanceBetweenTwoObjects(node, destinationNode: destinationNode)
    if distance > spread + radius{
        return (0,0)
    }

    var angle = atan((destinationNode.position.y - node.position.y)/(destinationNode.position.x - node.position.x))
    var xSpeed:CGFloat = 0
    var ySpeed:CGFloat = 0
    println("\(distance) : \(radius)")
    if radius <= distance && distance <= spread + radius{
        xSpeed = strength * (spread + radius - distance) * cos(angle)
        ySpeed = strength * (spread + radius - distance) * sin(angle)
    }else if distance < radius{
        xSpeed = getSign(cos(angle)) * CGFloat.max
        ySpeed = getSign(sin(angle)) * CGFloat.max
        println("HERE")
    }
    return (xSpeed, ySpeed)
}

不幸的是,距离公式显然让我失望了,我多次捕捉到节点接触但它没有捡起它,或者没有接触但认为它接触。任何人都可以看到我在哪里做错了或建议一种更简单的方法来准确计算两个 SpriteNode 之间的距离吗?希望它简单。谢谢。

下图显示了我的 AINode 正在解决我的迷宫(橙色)、要避免的障碍(蓝色)和我的目标(绿色)。

在本例中,AINode 的宽度为 20。 本例中障碍物的宽度为 50。

当调试器暂停在 AINode 非法与障碍物相交的无效状态时,使用上面提供的公式计算的中心点之间的距离为“28.28”。实际距离为 7。

更新

我在我的吸引和排斥向量中发现了一个严重的错误,它加剧了这个问题。我的距离公式有点偏差,但由于我计算角度的错误,它被放大了。需要使用 atan2 而不是 atan 来计算角度。原因是如果不使用 atan2,角度将在不正确的象限中返回,这会扭曲排斥矢量的方向并导致重大错误。

您在计算节点的垂直中心点时错误地使用了 size.width。 (即:centerSourceNode.y += sourceNode.size.width/2 应为 sourceNode.size.height/2centerDestNode 相同)

在更高层次上,您是否看过 iOS 9 中的新 UIFieldBehavior 类型,以及用于寻路的 Gameplay Kit?

Larry 可能有所作为,因为您对 centerSourceNode.x += sourceNode.size.width/2 的使用表明您的节点的锚点为 (0,0) 而不是默认值 (0.5, 0.5)。

使用默认值,精灵的位置是它的中心在父节点中的位置,这意味着除了两个节点之外你不需要考虑任何东西职位。

正如我在评论中提到的,这也让我们找到了通过扩展 CGPoint 来解决它的相当巧妙的可能性:

extension CGPoint {
    func distanceFromCGPoint(point:CGPoint)->CGFloat{
        return sqrt(pow(self.x - point.x,2) + pow(self.y - point.y,2))
    }
}

然后,无论你在哪里需要这个,你都可以通过简单的调用来获得距离:

let distance = spriteA.position.distanceFromCGPoint(spriteB.position)