Unity:使用Raycast方法检测玩家位置时出现的问题

时间:2018-11-07 11:44:46

标签: c# unity3d

我正在Unity中的一个项目中工作,我需要知道玩家的位置,无论玩家是站在平台上还是地面上。播放器可以在这两者之间传送。我编写的这段代码大多数时候都可以工作,除了在某些时候,它返回错误的响应。当我要么从地面传送到平台,反之亦然,或者我几乎站在平台的边缘时,就会发生这种情况。有人可以建议如何处理吗?

private float distance = 0.5f;
void Update(){
        RaycastHit hit;

        Ray footstepRay = new Ray (transform.position, Vector3.down);
        if(Physics.Raycast(footstepRay, out hit, distance)){
            if(hit.collider.tag == "Ground"){
                Debug.Log ("Player is standing on the ground");
            }
            else if(hit.collider.tag == "Platform"){
                Debug.Log ("Player is standing on the platform");
            }

        }
    } 

1 个答案:

答案 0 :(得分:2)

请确保为Ray的起始位置添加偏移量,以防止Ray从搜索对象内部开始时出现问题(例如,如果Player停在您的平台上,并穿透了很小的一部分,则可能会发生这种情况)。 / p>

这会增加射线的偏移量,以确保即使地面/平台略微穿透也能检测到:

private float distance = 0.5f;
private float offset = 0.5f;

void Update()
{
    RaycastHit hit;

    Ray footstepRay = new Ray(transform.position + (Vector3.up * offset), Vector3.down); // FIX: added an offset

    if(Physics.Raycast(footstepRay, out hit, distance + offset, LayerMask.GetMask("Ground", "Platform"))) // FIX: added a LayerMask
    {
        if(hit.collider.tag == "Ground")
        {
            Debug.Log ("Player is standing on the ground");
        }
        else if(hit.collider.tag == "Platform")
        {
            Debug.Log ("Player is standing on the platform");
        }
    }
}

当从搜索到的对象内部意外启动射线时,偏移量可防止错过检测。

使用显式的LayerMask可确保您不会意外检测到Player或其他不需要的对象。

完全站在另一个边缘时,没有检测到平台是完全不同的问题。发生的情况是,一旦您的播放器的中心不再位于平台上方,您的Ray就将从播放器的中心开始经过平台。您可以通过在不同位置(例如,围绕播放器中心的圆上)发送多条光线来解决此问题。

这在边缘情况下(字面上)使用了多条射线来改善地面/平台检测:

private float distance = 0.5f;
private float yOffset = 0.5f;
private float playerRadius = 0.3f;

void Update()
{
    string hitTag = DetectGround(Vector3.zero);
    if (hitTag != null)
    {
        OnFound(hitTag);
        return;
    }

    const int rays = 10;
    for (int i = 0; i < rays; ++i)
    {
        float angle = (360.0f / rays) * i;
        Vector3 posOffset = Quaternion.AngleAxis(angle, Vector3.up) * (Vector3.forward * playerRadius);

        hitTag = DetectGround(posOffset);
        if (hitTag != null)
        {
            OnFound(hitTag);
            return;
        }
    }
}

void OnFound(string tag)
{
    if(tag == "Ground")
    {
        Debug.Log ("Player is standing on the ground");
    }
    else if(tag == "Platform")
    {
        Debug.Log ("Player is standing on the platform");
    }
}

string DetectGround(Vector3 posOffset)
{
    RaycastHit hit;
    Ray footstepRay = new Ray(transform.position + posOffset + (Vector3.up * yOffset), Vector3.down); // FIX: added an offset

    if(Physics.Raycast(footstepRay, out hit, distance + yOffset, LayerMask.GetMask("Ground", "Platform"))) // FIX: added a LayerMask
    {
        return hit.collider.tag;
    }
    return null;
}

以上代码假定除标签外,您还具有称为“地面”和“平台”的图层。您可以根据需要进行修改。 LayerMask的目的是确保Raycast不考虑除地面和平台以外的任何对象。您可以将它们放置在单独的图层中,也可以放置在某种类型的单个“世界”图层中,也可以放置在您选择的任何内容中,只要Player与地面或平台不在同一图层中即可。

编辑:在某些情况下,站立在平台(边缘)上时有时会检测到地面。如果将distance字段设置为大于最小地面到平台距离的值,则会发生这种情况。如果该距离恒定,则可以通过相应地调整distance字段来解决此问题。但是,如果平台在移动,则该方法可能行不通。在这种情况下,使用距离玩家最近的物体会产生更好的效果。

此示例收集所有光线的所有命中,并按距离对它们进行排序。最接近的匹配被认为是理想的结果:

using System.Linq;

private float distance = 0.5f;
private float yOffset = 0.5f;
private float playerRadius = 0.3f;

void Update()
{
    List<RaycastHit> allHits = new List<RaycastHit>();
    DetectGround(allHits, Vector3.zero);

    const int rays = 10;
    for (int i = 0; i < rays; ++i)
    {
        float angle = (360.0f / rays) * i;
        Vector3 posOffset = Quaternion.AngleAxis(angle, Vector3.up) * (Vector3.forward * playerRadius);

        DetectGround(allHits, posOffset);
    }

    if (allHits.Any())
    {
        RaycastHit closestHit = allHits.OrderBy(hit => hit.distance).First();
        OnFound(closestHit.collider.tag);
    }
}

void OnFound(string tag)
{
    if(tag == "Ground")
    {
        Debug.Log ("Player is standing on the ground");
    }
    else if(tag == "Platform")
    {
        Debug.Log ("Player is standing on the platform");
    }
}

void DetectGround(List<RaycastHit> hits, Vector3 posOffset)
{
    Ray footstepRay = new Ray(transform.position + posOffset + (Vector3.up * yOffset), Vector3.down); // FIX: added an offset

    Debug.DrawLine(footstepRay.origin, footstepRay.origin + (footstepRay.direction * (distance + yOffset)), Color.red);
    hits.AddRange(Physics.RaycastAll(footstepRay, distance + yOffset, LayerMask.GetMask("Ground", "Platform")));
}

注意:此示例不需要LayerMask才能可靠地工作。如果您的游戏在光线投射附近有许多对撞机,则出于性能原因,使用“图层”来过滤考虑的对象可能仍然有意义。