3 - Movimentação Point and Click + Obstáculos


Link para o repositório no github!!

O processo de fazer a mecânica de desviar dos obstáculos seguiu os seguintes passos:

  1. verificar se há um obstáculo entre a posição do player e o destino;
  2. criar 2 pontos de desvio; verificar qual lado está livre e é o mais curto;
  3. mais uma verificação entre o desvio e o destino final;
  4. extra: cortando caminho;

1. Para verificar se há obstáculos foi usado um RaycastHit2D[ ] e uma layer chamada "Obstacle". Neste caso, a direction e a distance se baseiam entre na posição do player e o destino final. Após a verificação é usado um foreach, pois pode haver mais de um obstáculo pelo caminho:

obs: precisa colocar a layer Obstacle nos objetos que precisam ser desviados na cena; isMoving = true é colocado após a verificaçao do foreach;

[SerializeField] private LayerMask obstacleLayer;
. . .
if (hitGround.collider != null){
    destination = hitGround.point;
    RaycastHit2D[] obstaclesHit = Physics2D.RaycastAll(transform.position, destination - (Vector2)transform.position, Vector2.Distance(transform.position, destination), obstacleLayer);
    foreach (var item in obstaclesHit){
        if (item.collider != null) . . .
    }
    isMoving = true;
}


2. Para criar um ponto de desvio é necessário criar um bool para "caso estiver desviando"  e um Vector2 para o ponto:

private Vector2 detourPosition;
private bool isDetouring;
void Start(){
isMoving = false;
isDetouring = false;
}

No método OnClick, dentro do foreach, a lógica do cálculo para o desvio seria: 

  • usar o tamanho do collider do obstáculo para conseguir um float de distância razoável;
if (item.collider != null){
    Collider2D obsCollider = item.collider;
    Vector2 obsSize = obsCollider.bounds.size;
    float detourDistance = Mathf.Max(0.5f, Mathf.Min(obsSize.x, obsSize.y) / 2f);
  • conseguir o ponto perpendicular (90 graus);
    Vector2 direction = (destination - (Vector2)transform.position).normalized;
    Vector2 perpDirection = Vector2.Perpendicular(direction);
  •  fazer o cálculo verificando os dois lados do obstáculo; criar dois bool para verificar quais lados estão livres para passagem;

 ponto do collider que o RaycastHit bate   + ou -   o ponto perpendicular     X     float de distância do collider

    detourPos1 = item.point + perpDirection * detourDistance;
    detourPos2 = item.point - perpDirection * detourDistance;
    bool detour1Clear = !Physics2D.Raycast(playerPosition, detourPos1 - playerPosition, Vector2.Distance(playerPosition, detourPos1), obstacleLayer);
    bool detour2Clear = !Physics2D.Raycast(playerPosition, detourPos2 - playerPosition, Vector2.Distance(playerPosition, detourPos2), obstacleLayer);
  • verificar qual lado está livre e mais perto;
    if (detour1Clear && detour2Clear){
        detourPosition = Vector2.Distance(playerPosition, detourPos1) < Vector2.Distance(playerPosition, detourPos2) ? detourPos1 : detourPos2;
    }else if (detour1Clear){
        detourPosition = detourPos1;
    }else if (detour2Clear){
        detourPosition = detourPos2;
    }else{
        isMoving = false;
        return;
    }
    isDetouring = true;
}

No Update:

if (isMoving){
    Vector2 nextPosition = isDetouring ? detourPosition : destination;
    transform.position = Vector2.MoveTowards(transform.position, nextPosition, speed * Time.deltaTime);
    if ((Vector2)transform.position == destination){
        if (isDetouring)
            isDetouring = false;
        else
            isMoving = false;
    }
}


3. O script até esse ponto já é funcional, mas fiz mais uma verificação para evitar qualquer colisão restante. As verificações foram colocadas num método à parte, uma coroutine, para fazer os cálculos até ter o ponto de desvio.

private void CheckObstacles(){
    RaycastHit2D[] obstaclesHit = Physics2D.RaycastAll(transform.position, destination - (Vector2)transform.position, Vector2.Distance(transform.position, destination), obstacleLayer);
    foreach (var item in obstaclesHit){
        if (item.collider != null){
            Collider2D obsCollider = item.collider;
            Vector2 obsSize = obsCollider.bounds.size;
            float detourDistance = Mathf.Max(0.5f, Mathf.Min(obsSize.x, obsSize.y) / 2f);
            Vector2 direction = (destination - (Vector2)transform.position).normalized;
            Vector2 perpDirection = Vector2.Perpendicular(direction);
            StartCoroutine(PathOptionsCheck(item.point, perpDirection, detourDistance));
            isDetouring = true;
        }
    }
    isMoving = true;
}

Na IEnumerator PathOptionsCheck foi colocado, além das verificações já existentes, mais uma verificação contendo 2 bool. Caso os 2 lados não estejam livre é recomeçado a coroutine mas com uma variação no float detourDistance:

IEnumerator PathOptionsCheck(Vector2 itemPoint, Vector2 perpDirection, float detourDistance){
    detourPos1 = itemPoint + perpDirection * detourDistance;
    detourPos2 = itemPoint - perpDirection * detourDistance;
    detour1Clear = RaycastVerification(transform.position, detourPos1, Color.red);
    detour2Clear = RaycastVerification(transform.position, detourPos2, Color.blue);
    end1Clear = RaycastVerification(detourPos1, destination, Color.red);
    end2Clear = RaycastVerification(detourPos2, destination, Color.blue);
//escolher o desvio
    if (detour1Clear && detour2Clear){
        if (end1Clear && end2Clear)
            detourPosition = Vector2.Distance(transform.position, detourPos1) < Vector2.Distance(transform.position, detourPos2) ? detourPos1 : detourPos2;
        else if (end1Clear)
            detourPosition = detourPos1;
        else if (end2Clear)
            detourPosition = detourPos2;
    }
    else if (detour1Clear && end1Clear)
        detourPosition = detourPos1;
    else if (detour2Clear && end2Clear)
        detourPosition = detourPos2;
    else    // quando os 2 lados não dão certo
    {
        isMoving = false;
        StartCoroutine(PathOptionsCheck(itemPoint, perpDirection, detourDistance + 0.5f));
    }
    yield break;
}

O método RaycastVerification é usado para retornar se o lado verificado está livre ou não. Há um Debug.DrawLine para poder ver os caminhos criados quando for testar na unity:

private bool RaycastVerification(Vector2 origin, Vector2 direction, Color color){
    bool pathClear = !Physics2D.Raycast(origin, direction - origin, Vector2.Distance(origin, direction), obstacleLayer);
    Debug.DrawLine(origin, direction, color, 2f);
    if (pathClear)        
        return true;       
    else       
        return false;              
}


4. No Update é possivel fazer a movimentação cortar caminho: enquanto vai até o desvio é feito um Raycast para verificar se isso é possivel.

if (isMoving){
    Vector2 nextPosition = isDetouring ? detourPosition : destination;
    transform.position = Vector2.MoveTowards(transform.position, nextPosition, speed * Time.deltaTime);
    if (isDetouring && !Physics2D.Raycast(transform.position, destination - (Vector2)transform.position, Vector2.Distance(transform.position, destination), obstacleLayer)){
        nextPosition = destination;
        isDetouring = false;
    }              
    if ((Vector2)transform.position == nextPosition){
        if (isDetouring)
            isDetouring = false;
        else
            isMoving = false;
    }         
}

Leave a comment

Log in with itch.io to leave a comment.