Navmesh::class:Component{// Спроецировать точку на этот навмэш.// triangle_hint — это подсказка для ускорения, которую можно повторно использовать для запросов поблизости.try_find_closest_point_on_navmesh::method(to_point:v2,result:refv2,triangle_hint:refs64)->bool;rebuild_immediately::method()->bool;mark_for_rebuild::method();}
Настройка в редакторе (создание навмэша)
Создайте сущность и добавьте Навмэш компонент.
Создайте дочерние сущности с Navmesh_Loop компонентами, чтобы определить проходимые полигоны.
Для каждого Navmesh_Loop:
Добавьте точки, чтобы определить форму цикла
Переключите Flip Inside Outside чтобы сделать дыру/препятствие вместо проходимого пространства
Используйте отладочные параметры навмэша в инспекторе, чтобы визуализировать треугольники.
По умолчанию Navmesh_Loop определяет проходимую область. Переверните его, чтобы «пробивать дыры» (непроходимые островки) в существующей навмэш-сетке.
Коллайдеры и циклы навмэша
Коллайдеры также могут вносить циклы через параметры инспектора коллайдера (например, «Make Navmesh Loop» / «Flip Navmesh Loop»).
Важные оговорки:
A Навмэш не не автоматически перестраивается только потому, что коллайдер изменился во время выполнения.
Если вы изменяете геометрию коллайдера и полагаетесь на циклы, зависящие от коллайдера, вручную перестройте навмэш (см. ниже).
Если вы хотите навмэш, который полностью игнорирует коллайдеры, используйте параметр инспектора навмэша для игнорирования коллайдеров.
Появление на навмэше (привязка к достижимой земле)
Используйте try_find_closest_point_on_navmesh чтобы спроецировать желаемую позицию на ближайшую допустимую точку навмэша:
Переиспользуйте triangle_hint для повторяющихся запросов в одной и той же области (спавнеры лута, волновые спавны и т. п.). Это может значительно ускорить проекцию.
Перестройка навмэшей (при изменении геометрии)
Используйте одно из:
mark_for_rebuild(): откладывает перестройку до начала следующего кадра (лучше всего для пакетной обработки)
rebuild_immediately(): перестраивает сейчас (используйте только если вам нужно запросить обновлённую сетку в том же кадре)
Если у вас есть родительский навмэш, который «сшивает» вместе несколько дочерних навмэшей, изменение дочернего навмэша не перестраивает родительский автоматически. Вызовите mark_for_rebuild() / rebuild_immediately() на родительском после изменений.
Столкновения (текущая поддержка CSL)
Коллайдеры существуют как компоненты (Box_Collider, Circle_Collider, Edge_Collider, Polygon_Collider), но скриптовые колбэки входа/выхода столкновений ещё не реализованы в CSL.
Пока колбэки столкновений не доступны в CSL, стандартный подход такой:
Запрашивайте ближайшие компоненты с помощью Scene.get_all_components_in_range / Scene.get_closest_component_in_range
Выполняйте простые проверки расстояния (in_range) чтобы определить «внутри», «подобрано», «попадание» и т. п.
Вспомогательные функции запросов сцены
Для игровых последствий (урон, подбор предметов, счёт) предпочитайте серверную сторону. Для косметической обратной связи используйте локальные проверки.
Зона триггера (вход / нахождение / выход)
Чтобы имитировать зону триггера, ведите список тех, кто был внутри на прошлом кадре, и сравнивайте его с результатами текущего кадра.
Это «по возможности» логика столкновений. Если что-то телепортировано/удалено между обновлениями, вы можете не получить корректный «выход», если не обработаете очистку.
Подбираемые предметы (ближайший в радиусе)
Быстрые «попадания» при движении (простая субдискретизация)
Если вы движетесь быстро (рывок, снаряд), при проверке только конечной позиции можно пропустить узкие цели. Простое решение — субдискретизация: взять несколько точек между старой и новой позицией и выполнить те же запросы по радиусу.
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, "Entered zone!");
}
else {
// on_stay
}
}
}
for id: last_inside {
if !contains_id(current_inside, id) {
// on_exit (возможно, вам потребуется собственный поиск 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); // ваша собственная логика (выдать + уничтожить)
}
}
}
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) {
// применить попадание один раз, запустить кулдаун и т. д.
}
}
}
}