【问题标题】:Calculating which of the Collider line segments was hit in Unity? (Placing gameobjects around the perimeter of a collider)在 Unity 中计算哪些 Collider 线段被击中? (将游戏对象放置在对撞机的周边)
【发布时间】:2023-03-08 22:11:01
【问题描述】:

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

尝试使用光线投射执行此操作会引入很多边缘情况。为了消除这些,我想应用一种更一致的方法。

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

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

请看一下这个例子:

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

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

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

【问题讨论】:

    标签: unity3d


    【解决方案1】:

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

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

    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
    

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

    【讨论】:

    • 不错!我刚刚实现了其中的一些代码,它完成了工作!
    【解决方案2】:

    一种解决方案是搜索与碰撞点最接近的线对的点。所以基本上,我们希望多边形上的 CollisionEnter2D 函数看起来像这样:

    private void OnCollisionEnter2D(Collision2D collision)
    {
        Vector2 contactPoint = collision.GetContact(0).point;
        (Vector2, Vector2) closestLine = FindClosestLine(contactPoint);
    
        if (closestLine != null)
            print(closestLine);
    }
    

    我的解决方案可以用下图来描述:

    (请见谅,我是程序员)。

    作为一种伪算法:

    define point = collisionPoint;
    define pair;
    define minDistance;
    
    for pair := (p1, p2) in collider.points:
        if (dist := shortestDistance(point, pair) < minDistance):
            minDistance = dist
            pair = (p1, p2)
    

    在这个循环结束时,我们将得到我们正在寻找的两个点。这是我想出的一个粗略的实现:

    private (Vector2, Vector2)? FindClosestLine(Vector2 contactPoint)
    {
        var localScale = transform.localScale;
        var points = polygonCollider.points;
    
        (Vector2, Vector2) closestLine = (default, default);
        var shortestDistance = float.MaxValue;
    
        for (var i = 1; i < points.Length; i++)
        {
            // We multiply the points by localScale, because the collider 
            // scales them to 1 internally, regardless of our size.
            var line = (points[i - 1] * localScale, points[i] * localScale);
            var distance = MinDistPointToLine(contactPoint, line);
            if (distance < shortestDistance)
            {
                shortestDistance = distance;
                closestLine = line;
            }
        }
    
        if (shortestDistance < float.MaxValue)
            return closestLine;
        else return null;
    }
    

    为了算出最短距离,我们可以使用一些基本的三角学(再次请原谅画图。)

    代码如下所示:

    private static float MinDistPointToLine(Vector2 point, (Vector2, Vector2) line)
    {
        // Calculate the shortest distance between the line (p[n+1] - p[n]) and the given point.
    
        var (end, start) = line;
        var lineLength = (start - end).magnitude;
        var lineLengthSqr = lineLength * lineLength;
        var distToStartSqr = (point - end).sqrMagnitude;
        var distToEndSqr = (point - start).sqrMagnitude;
        
        // Equation found by algebra. 
        return distToStartSqr - (distToStartSqr - distToEndSqr - lineLengthSqr) / 2 * lineLength;
    }
    

    在这种情况下,我们只是打印两个点,但您显然会使用它们来实现您描述的算法。

    【讨论】:

    • 我已经采用了@derHugo 的另一种方法,但非常感谢您的贡献,看起来这也可行。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-12
    相关资源
    最近更新 更多