Navmesh::clase: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::método(to_point:v2,result:refv2,triangle_hint:refs64)->bool;rebuild_immediately::método()->bool;mark_for_rebuild::método();}
Configuración del editor (construir un navmesh)
Crea una entidad y añade un Navmesh componente.
Crea entidades hijas con Navmesh_Loop componentes para definir polígonos transitables.
Para cada Navmesh_Loop:
Añade puntos para definir la forma del bucle
Alternar Invertir Interior Exterior para hacer un agujero/obstáculo en lugar de espacio transitable
Usa las opciones de depuración del navmesh en el inspector para visualizar los triángulos.
Por defecto, un Navmesh_Loop define un área transitable. Inviértelo para "hacer agujeros" (islas no transitables) en un navmesh existente.
Colliders y bucles de navmesh
Los colliders también pueden contribuir bucles mediante opciones del inspector del collider (por ejemplo "Make Navmesh Loop" / "Flip Navmesh Loop").
Advertencias importantes:
Un Navmesh hace no reconstrucción automática solo porque un collider cambie en tiempo de ejecución.
Si modificas la geometría del collider y dependes de bucles impulsados por colliders, reconstruye manualmente el navmesh (ver más abajo).
Si quieres un navmesh que ignore completamente los colliders, usa la opción del inspector del navmesh para ignorar colliders.
Generar sobre 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:
Reutiliza triangle_hint para consultas repetidas en la misma área (generadores de botín, oleadas, etc.). Puede acelerar significativamente la proyección.
Reconstrucción de navmeshes (cuando la geometría cambia)
Usa una de:
mark_for_rebuild(): difiere la reconstrucción hasta el inicio del siguiente fotograma (mejor para agrupar)
rebuild_immediately(): reconstruye ahora (úsalo solo si debes consultar la malla actualizada en el mismo fotograma)
Si tienes un navmesh padre que "une" múltiples navmeshes hijos, cambiar un navmesh hijo no reconstruye automáticamente el padre. Llama mark_for_rebuild() / rebuild_immediately() en el padre después de las ediciones.
Colisión (soporte actual de CSL)
Los colliders existen como componentes (Box_Collider, Circle_Collider, Edge_Collider, Polygon_Collider), pero las devoluciones de llamada de entrada/salida de colisión están aún no implementadas en CSL.
Hasta que las devoluciones de llamada de colisión estén expuestas en CSL, el enfoque estándar es:
Consultar componentes cercanos con Scene.get_all_components_in_range / Scene.get_closest_component_in_range
Haz comprobaciones de distancia simples (in_range) para decidir "dentro", "recogido", "golpeado", etc.
Helpers de consulta de escena
Prefiere el lado del servidor para consecuencias de jugabilidad (daño, recogidas, puntuación). Usa comprobaciones locales solo para retroalimentación cosmética.
Volumen de activación (entrar / permanecer / salir)
Para simular una zona de activación, mantiene una lista de quién estaba dentro el último fotograma y compárala con los resultados del fotograma actual.
Esta es una lógica de colisión de "mejor esfuerzo". Si algo se teletransporta/desaparece entre actualizaciones, puede que no obtengas un "salida" limpio a menos que manejes la limpieza.
Recogibles (el más cercano en rango)
Golpes 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 simple es el subpaso: muestrea algunos puntos entre la posición anterior y la nueva y ejecuta las mismas consultas de rango.
contains_id :: proc(list: []u64, id: u64) -> bool {
for x: list if x == id return true;
return false;
}
Trigger_Zone :: clase : Component {
radio: float @ao_serialize;
last_inside: [..]u64;
ao_update :: método(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, "¡Entró en la zona!");
}
else {
// on_stay
}
}
}
for id: last_inside {
if !contains_id(current_inside, id) {
// on_exit (puedes querer tu propia búsqueda id->player)
}
}
last_inside = current_inside;
}
}
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)
}
}
}
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) {
// aplicar golpe una vez, iniciar tiempo de reutilización, etc.
}
}
}
}