> For the complete documentation index, see [llms.txt](https://docs.allout.game/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.allout.game/all-out-docs/docs-es/conceptos-basicos-del-motor/navmesh-and-collision.md).

# Navmesh y colisión

Los navmeshes definen las **áreas transitables** en tu mundo. Úsalos para:

* Ajustar los objetos generados al suelo alcanzable
* Restringir a los jugadores/PNJ a las regiones transitables

Los colliders también se pueden usar para recortar navmeshes en el editor.

### Componentes (resumen rápido)

* **`Navmesh`**: la malla de navegación horneada que consultas y reconstruyes
* **`Navmesh_Loop`**: bucles de polígonos que definen los límites transitables (y los huecos)
* **Colliders**: `Box_Collider`, `Circle_Collider`, `Edge_Collider`, `Polygon_Collider`

{% hint style="info" %}
Para movimiento/búsqueda de rutas (`Movement_Agent`) consulta [Agentes de movimiento/NPCs](/all-out-docs/docs-es/conceptos-basicos-del-motor/movement-agents-npcs.md).
{% endhint %}

### Referencia de la API de Navmesh

```go
Navmesh :: class : Component {
    // Proyecta un punto sobre este navmesh.
    // triangle_hint es una pista de aceleración que puedes reutilizar para consultas cercanas.
    try_find_closest_point_on_navmesh :: method(
        to_point: v2,
        result: ref v2,
        triangle_hint: ref s64
    ) -> bool;

    // Lanza un rayo a través del navmesh. Devuelve el extremo y si la consulta tuvo éxito.
    try_raycast :: method(position: v2, direction: v2) -> v2, bool;

    // Encola una reconstrucción para la actualización normal del navmesh.
    mark_for_rebuild :: method();

    // Reconstruye ahora cuando una consulta en el mismo fotograma necesita la nueva geometría.
    rebuild_immediately :: method() -> bool;

    // Incluye solo los colliders de esta entidad y sus descendientes.
    child_colliders_only: bool;

    // Detecta cambios de entrada y reconstruye automáticamente.
    enable_automatic_rebuilds: bool;
}
```

### Configuración del editor (construcción de un navmesh)

1. Crea una entidad y añade un `Navmesh` componente.
2. Crea entidades hijas con `Navmesh_Loop` componentes para definir polígonos transitables.
3. Para cada `Navmesh_Loop`:
   * Añade puntos para definir la forma del bucle
   * Ordena los puntos del contorno exterior en sentido antihorario
   * Activa **Invertir interior y exterior** para convertirlo en un hueco/obstáculo en lugar de un espacio transitable
4. Usa las opciones de depuración del navmesh en el inspector para visualizar los triángulos.

{% hint style="info" %}
De forma predeterminada, un `Navmesh_Loop` define un área transitable. Inviértelo para «perforar huecos» (islas no transitables) en un navmesh existente.
{% endhint %}

{% hint style="warning" %}
El sentido del bucle determina qué lado de cada arista se trata como el interior. El sentido antihorario es el habitual para un contorno exterior transitable. El sentido horario invierte ese resultado. Usa **Invertir interior y exterior** de forma deliberada para los huecos en lugar de depender de un orden de puntos invertido por accidente.
{% endhint %}

### Colliders y bucles de navmesh

Los colliders también pueden aportar bucles mediante las opciones del inspector del collider (por ejemplo, «Make Navmesh Loop» / «Flip Navmesh Loop»).

Advertencias importantes:

* Con **Activar reconstrucciones automáticas**, los navmeshes detectan cambios de entrada en bucles, colliders, tilemaps y navmeshes hijos, y encolan una reconstrucción.
* Activa **Solo colliders hijos** para ignorar colliders no relacionados en otras partes de la escena.
* Con las reconstrucciones automáticas desactivadas, llama a `mark_for_rebuild()` después de cambiar una entrada.
* Llama a `rebuild_immediately()` solo antes de una consulta en el mismo fotograma que requiera la geometría modificada.

### Generación sobre el navmesh (ajustar al suelo alcanzable)

Usa `try_find_closest_point_on_navmesh` para proyectar una posición deseada sobre el punto válido más cercano en un navmesh:

```go
spawn_on_navmesh :: proc(navmesh: Navmesh, desired_position: v2) -> Entity {
    spawn_pos: v2;
    triangle_hint: s64; // 0 = aún no establecido

    if navmesh.try_find_closest_point_on_navmesh(desired_position, ref spawn_pos, ref triangle_hint) {
        e := Scene.create_entity();
        e.set_local_position(spawn_pos);
        return e;
    }

    return null;
}
```

{% hint style="info" %}
Reutiliza `triangle_hint` para consultas repetidas en la misma zona (generadores de botín, apariciones por oleadas, etc.). Puede acelerar significativamente la proyección.
{% endhint %}

### Reconstrucción inmediata de navmeshes

La mayoría de los cambios de geometría se detectan automáticamente. Úsalo solo cuando necesites forzar el momento:

* **`rebuild_immediately()`**: reconstruye ahora (úsalo solo si debes consultar la malla actualizada en el mismo fotograma)

```go
ok := navmesh.rebuild_immediately();
if !ok {
    log_info("Falló la reconstrucción del navmesh", {});
}
```

{% hint style="info" %}
Los navmeshes padre se actualizan después de los navmeshes hijo, así que las mallas padre ensambladas incorporan automáticamente los cambios de entrada de las mallas hijas.
{% endhint %}

### Lanzamiento de rayos a través de un navmesh

`try_raycast` sigue `la dirección` hasta el límite del navmesh. El segundo resultado informa de si la consulta tuvo éxito.

```go
endpoint, ok := navmesh.try_raycast(start, direction);
if ok {
    // el endpoint está en el límite del navmesh.
}
```

### Disparadores de collider

Un collider con `is_trigger == true` puede notificar cuando otro collider entra, permanece dentro o sale. Las devoluciones de llamada de disparador funcionan con colliders estáticos y en movimiento; un `Movement_Agent` no es necesario.

{% hint style="warning" %}
**Usa colliders con moderación.** Añádelos solo a superficies críticas para la jugabilidad: límites del mapa, plataformas sobre las que el jugador debe caer, obstáculos clave y volúmenes de disparo deliberados. No **no** añadas colliders a entidades decorativas (árboles, arbustos, elementos de fondo, etc.). La mayoría de las entidades de una escena no deberían tener collider.
{% endhint %}

```go
Trigger_Listener :: class : Component {
    ao_start :: method() {
        trigger_collider := entity.get_component(Circle_Collider);
        trigger_collider.is_trigger = true;

        trigger_collider.on_trigger_start = proc(self: Collider, other: Collider) {
            log("INICIO DE SOLAPAMIENTO: %", {other.entity.get_name()});
        };

        trigger_collider.on_trigger_stay = proc(self: Collider, other: Collider) {
            log("SOLAPAMIENTO CONTINÚA: %", {other.entity.get_name()});
        };

        trigger_collider.on_trigger_end = proc(self: Collider, other: Collider) {
            log("FIN DEL SOLAPAMIENTO: %", {other.entity.get_name()});
        };
    }
}
```

### Comprobaciones de proximidad y lógica de impacto personalizada

Para zonas de disparo que usan consultas de rango en lugar de devoluciones de llamada de colisionador, recogidas, comprobaciones de proyectiles u otra lógica de impacto personalizada, usa:

* `Scene.get_all_components_in_range` / `Scene.get_closest_component_in_range`
* Comprobaciones simples de distancia (`in_range`) para decidir «dentro», «recogido», «impactado», etc.

#### Ayudas de consulta de la escena

```go
Scene :: struct {
    get_all_components_in_range :: proc(position: v2, range: float, results: ref [..]$T);
    get_closest_component_in_range :: proc(position: v2, range: float, $T: typeid) -> T, bool;
}
```

{% hint style="info" %}
Ejecuta el daño, las recogidas y la puntuación en la ruta compartida normal de la jugabilidad. No protejas la jugabilidad predicha con `Game.is_server()`.
{% endhint %}

#### Volumen de disparo (entrar / permanecer / salir)

Para simular una zona de disparo, conserva una lista de quién estaba dentro en el fotograma anterior y compárala con los resultados del fotograma actual.

```go
contains_id :: proc(list: []u64, id: u64) -> bool {
    for x: list if x == id return true;
    return false;
}

Trigger_Zone :: class : Component {
    radius: float @ao_serialize;
    last_inside: [..]u64;

    ao_update :: method(dt: float) {
        center := entity.world_position;

        players: [..]Player;
        Scene.get_all_components_in_range(center, radius, ref players);

        current_inside: [..]u64;

        for p: players {
            if in_range(p.entity.world_position, center, radius) {
                current_inside.append(p.entity.id);

                if !contains_id(last_inside, p.entity.id) {
                    // on_enter
                    Notifier.notify(p, "¡Has entrado en la zona!");
                }
                else {
                    // on_stay
                }
            }
        }

        for id: last_inside {
            if !contains_id(current_inside, id) {
                // on_exit (quizá quieras tu propia búsqueda id.player)
            }
        }

        last_inside.clear();
        for id: current_inside {
            last_inside.append(id);
        }
    }
}
```

{% hint style="warning" %}
Este patrón manual de disparo sigue siendo útil cuando no quieres añadir un volumen de disparo con collider. Si algo se teletransporta o desaparece entre actualizaciones, puede que no obtengas una «salida» limpia a menos que gestiones la limpieza.
{% endhint %}

#### Recogibles (el más cercano dentro del rango)

```go
try_pickup_near_player :: proc(player: Player) {
    pos := player.entity.world_position;

    pickup, ok := Scene.get_closest_component_in_range(pos, 1.5, Pickup);
    if ok && pickup != null {
        if in_range(pickup.entity.world_position, pos, 1.5) {
            pickup.claim(player); // tu propia lógica (otorgar + destruir)
        }
    }
}
```

#### Impactos por movimiento rápido (subpasos simples)

Si te mueves rápido (embestida, proyectil), puedes fallar objetivos estrechos al comprobar solo la posición final. Una solución sencilla es usar subpasos: muestrea algunos puntos entre la posición anterior y la nueva y ejecuta las mismas consultas de rango.

```go
hit_check_move :: proc(last_pos: v2, new_pos: v2) {
    steps := 4;
    for i := 1; i <= steps; i += 1 {
        t := (i.(float)) / (steps.(float));
        p := lerp(last_pos, new_pos, t);

        enemies: [..]Enemy;
        Scene.get_all_components_in_range(p, 1.0, ref enemies);
        for e: enemies {
            if in_range(e.entity.world_position, p, 1.0) {
                // aplica el impacto una vez, inicia el tiempo de recuperación, etc.
            }
        }
    }
}
```
