Unity: проблемы при использовании метода Raycast для определения позиции игрока - PullRequest
0 голосов
/ 07 ноября 2018

Я работаю над проектом в 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 голосов
/ 07 ноября 2018

Обязательно добавьте смещение к начальной позиции луча, чтобы избежать проблем, когда луч начинается изнутри искомого объекта (что может произойти, если, например, игрок упирается в вашу платформу, проникая в нее незначительным количеством).

Это добавляет смещение к Лучу, чтобы гарантировать обнаружение земли / платформ, даже если они слегка проникают:

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 гарантирует, что вы случайно не обнаружите проигрыватель или другие нежелательные объекты.

Не обнаружение платформы в самом ее краю - это совершенно другая проблема. В результате ваш Луч, начиная с центра Игрока, просто проходит мимо платформы, когда центр вашего Игрока больше не находится над платформой. Это можно исправить, отправив несколько лучей в разные позиции, например, по кругу вокруг центра игрока.

Используется несколько лучей для улучшения обнаружения земли / платформы в краевых случаях (буквально):

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

Выше кода предполагается, что у вас есть слои с именами "Ground" и "Platform" в дополнение к тегам. Вы можете изменить это по мере необходимости. Цель LayerMask - убедиться, что Raycast не рассматривает никакие объекты, кроме площадок и платформ. Вы можете разместить их в отдельных слоях или в каком-то отдельном слое "Мир" или в любом другом месте, если игрок не находится в том же слое, что и основания или платформы.

Редактировать: Возможны ситуации, когда иногда на земле (на краю) платформы обнаруживается Земля. Это может произойти, если для поля 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 работал надежно. Может все же иметь смысл использовать слои для фильтрации рассматриваемых объектов по соображениям производительности, если в вашей игре много коллайдеров в непосредственной близости от raycast.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...