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 компонент.
Создавайте дочерние сущности с Navmesh_Loop компонентами, чтобы определить проходимые полигоны.
Для каждого Navmesh_Loop:
Добавьте точки, чтобы задать форму петли
Переключить Flip Inside Outside чтобы сделать отверстие/препятствие вместо проходимого пространства
Используйте параметры отладки навсетки в инспекторе, чтобы визуализировать треугольники.
По умолчанию, Navmesh_Loop определяет проходимую область. Переключите его, чтобы "пробивать отверстия" (непроходимые островки) в существующей навсетке.
Коллайдеры и петли навсетки
Коллайдеры также могут добавлять петли через опции инспектора коллайдера (например, "Make Navmesh Loop" / "Flip Navmesh Loop").
Важные оговорки:
A Navmesh делает не автоматически перестраивается просто потому, что коллайдер изменился во время выполнения.
Если вы изменяете геометрию коллайдера и полагаетесь на петли, управляемые коллайдерами, вручную перестройте навсетку (см. ниже).
Если вы хотите навсетку, полностью игнорирующую коллайдеры, используйте опцию инспектора навсетки, чтобы игнорировать коллайдеры.
Спавн на навсетке (ограничение до доступной земли)
Используйте 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) {
// применить попадание один раз, запустить перезарядку и т. п.
}
}
}
}