首页 > 解决方案 > 在 Unity 中计算哪些 Collider 线段被击中?(将游戏对象放置在对撞机的周边)

问题描述

对于我正在开发的游戏,我正在尝试编写一些将 GameObjects 放置在 PolygonCollider2D 周边的代码。在我的游戏中,一个物体可以接触一个平台,然后开始在该平台周围散布物质。我希望内容在程序上散布在平台上,每个x单元放置游戏对象。有关我的意思的示例,请查看此 .gif,其中我对 RayCast 做了同样的事情。

在此处输入图像描述

尝试使用光线投射来做到这一点会引入很多边缘情况。为了消除这些,我想应用一种更一致的方法。

在 Unity 中,对撞机包含一个数组,Collider.points其中包含组成对撞机的点的坐标。理论上,如果您开始将游戏对象放置在 处point[0],查看 的方向point[1],然后开始沿该方向放置对象,直到到达point[1]处,查看方向point[2]并重复,您应该能够将对象整齐地放置在所述对撞机的周边周围。

我的问题是我不知道我的初始吊具对象是否必须在point[0]andpoint[1]point[n]and之间启动这个对象放置算法point[n+1]

请看一下这个例子:

在此处输入图像描述

如果我的碰撞发生在红色标记上,我需要以某种方式确定碰撞发生在 和 之间的线段 E 上point[4]point[5]这样我就可以知道沿周边的“起始位置”并开始编写将对象放入的代码两个方向同时沿周边。

我的第一个想法是找到碰撞的世界位置,并找到距离points[]该碰撞点最近的两个点的世界位置。但在上面的例子中,这行不通——它会找到位置24(甚至不是段),即使碰撞接触到45(段E)之间的线段。

有人对如何做到这一点有任何建议吗?

标签: unity3d

解决方案


如果您已经有了接触点,您可以遍历所有顶点 ( point) 并检查接触点最接近哪条线。

以下两种方法取自HandleUtility(参见源代码),但它仅存在于编辑器中,因此由于您想在运行时使用它,只需将其复制到自定义运行时类

public static class VectorUtils
{
    // Project /point/ onto a line.
    public static Vector3 ProjectPointLine(Vector3 point, Vector3 lineStart, Vector3 lineEnd)
    {
        Vector3 relativePoint = point - lineStart;
        Vector3 lineDirection = lineEnd - lineStart;
        float length = lineDirection.magnitude;
        Vector3 normalizedLineDirection = lineDirection;
        if (length > .000001f)
            normalizedLineDirection /= length;

        float dot = Vector3.Dot(normalizedLineDirection, relativePoint);
        dot = Mathf.Clamp(dot, 0.0F, length);

        return lineStart + normalizedLineDirection * dot;
    }

    // Calculate distance between a point and a line.
    public static float DistancePointLine(Vector3 point, Vector3 lineStart, Vector3 lineEnd)
    {
        return Vector3.Magnitude(ProjectPointLine(point, lineStart, lineEnd) - point);
    }
}

现在假设您的积分都是连续的,您可以使用它,例如

// Allows to do some iteration queries on IEnumerable collections
using System.Linq;

...

public static void GetTouchSegmentEndpoints(
    // The Collider.points
    PolygonCollider2D collider, 
    // Your given collision point
    Vector3 touchPoint, 
    // After this method call these two will be filled with the information
    out Vector3 resultA, out Vector3 resultB)
{
    // Assign default values
    resultA = Vector3.zero;
    resultB = Vector3.zero;

    var localPoints = collider.points;

    // First of all the PolygonCollider2D.points are in LOCAL SPACE
    // so firs we need to convert them to worldSpace 
    // using Linq we can do this in a single line
    var worldPoints = collider.points.Select(p => collider.transform.TransformPoint(p)).ToArray();  
    // This basically equals doing something like
    //var worldPoints = new Vector3 [localPoints.Length];
    //for(var i = 0; i < localPoints.Length; i++)
    //{ 
    //    worldPoints[i] = collider.transform.TransformPoint(localPoints[i]);
    //} 

    // for comparing the distance to the current line
    var minDistance = float.PositiveInfinity;

    // Go through the world space points
    for(var i = 0; i < worldPoints.Length; i++)
    {
        // Get the next i with wrap around at the end
        var nextI = i == (worldPoints.Length - 1) ? 0 : i + 1;

        // Get the two corner points for the current line
        var pointA = worldPoints[i];
        var pointB = worldPoints[nextI];

        // get the distance between that line and the given touch point
        var distance = VectorUtils.DistancePointLine(touchPoint, pointA, pointB);

        // if it is smaller than the current minDistance 
        if(distance < minDistance)
        {
            // replace the results 
            resultA = pointA;
            resultB = pointB;

            minDistance = distance;
        }
    }
}

最后你会简单地称它为例如

PolygonCollider2D yourCollider;
Vector3 yourWorldTouchPoint;

VectorUtils.GetTouchSegmentEndpoints(yourCollider, yourWorldTouchPoint, out var lineA, out var lineB);

// Do something with lineA and lineB

如果这是我不知道的最有效的方法^^


推荐阅读