Барьеры — план реализации (Стена и Купол)
2026-07-26. Дизайн принят и зафиксирован — Combat - Barriers (две формы, решение Макса, не развилка). Здесь — инженерная часть: швы, гочи, порядок, тесты. Код ещё не написан.
Порядок: заход идёт после позиционики. Причина дизайнерская (обходить барьер должно быть кому), но она же и инженерная: скоринг «линия огня перекрыта» встраивается в тот же Filter / Score / Override, который позиционика и приносит.
Почему это дёшево
Симуляция уже даёт всё, на чём такие барьеры строятся, и ни один из пунктов не надо добывать:
- Своя тиковая сим без Unity-физики (
CombatSimulation, фикс.TickDelta). Барьер — ещё одна геометрическая сущность в тике, а не коллайдер и неPhysics2D-запрос. - Снаряды летят по настоящей траектории и уже проверяются swept circle-segment
(
ProjectileSystem.SweptCircleHit). «Поймать снаряд» = пересечь тот же отрезок с геометрией барьера. - АОЕ уже описан геометрией —
AreaShape.Circle/Line,AreaHit,AbilitySystem.ApplyCircle. Значит «купол закрыл от взрыва» — фильтр по геометрии, а не переписывание способностей. - Единая точка мутаций —
ICombatContext. Всё, что барьер обязан перехватывать, проходит через один интерфейс. - Shapes уже в проекте (
Guildmaster.Presentation) → векторная отрисовка без VFX Graph.
Модель данных
Combat/Barriers/Barrier.cs — данные одного барьера
Combat/Barriers/BarrierShape.cs — enum { Arc, Dome }
Combat/Barriers/BarrierField.cs — запросы к набору барьеров (чистые функции)
Combat/Systems/BarrierSystem.cs — жизненный цикл в тике
Barrier: Id, Team, Shape, CurrentHP, ExpiresAtTick, Anchor (носитель или null для
статичной), геометрия (Center + Radius для купола; Center + Normal + HalfLength для
стены), флаги источника.
BarrierField — два запроса, оба чистые и детерминированные:
bool TryBlockSegment(Vector2 from, Vector2 to, int sourceTeam, out Barrier hit, out Vector2 point)— для снарядов и линейных зон;bool IsShieldedFrom(Vector2 targetPos, Vector2 originPos, int sourceTeam, out Barrier hit)— для круговых АОЕ: цель внутри купола, центр эффекта — снаружи.
Порядок обхода — по индексу списка, тай-брейк по Id. Никакого Random, иначе повтор боя
разъедется (и это же требование к реплеям и к хост-авторитетной сети).
Швы
Шов 1 — ProjectileSystem. Перед проверкой юнитов прогнать отрезок [PreviousPosition, newPos]
через BarrierField. Пересёк вражеский барьер — урон уходит в барьер, снаряд гаснет там же.
Гоча, и главная.
AdvanceTracking(ProjectileSystem.cs) для самонаводящегося снаряда телепортирует его в цель, не проверяя ничего по пути: приdist <= travel + radiusпозиция просто присваивается позиции цели. Барьер такой снаряд не поймает вообще — а трекинг у нас основной способ доставки. Ветку надо переучить на ту же сегментную проверку, что иAdvanceFreeFlying. Без этого фича выглядит сломанной ровно там, где её будут смотреть первым делом.
Шов 2 — применение площадного урона. Фильтр ставится в точках нанесения, а не внутри
QueryUnitsInRadius / QueryUnitsInLine: те же запросы используются для поиска целей, условий
каста и союзных аур — отфильтруешь там, сломаешь выбор цели и хилы. Точки:
AbilitySystem.ApplyCircle— круговой АОЕ →IsShieldedFrom;- линейная авто-атака (
AutoAttackSystem, «Размашистый выпад») →TryBlockSegment, полоса обрезается по стене; ApplyAllWithTag— не фильтруется: масс-каст по тегу геометрии не имеет (§9.10), барьер по дизайну его не ловит.
Шов 3 — жизненный цикл. BarrierSystem.Tick: истечение срока, подтяжка позиции к носителю,
гашение при смерти носителя и при CurrentHP <= 0, удаление из списка (тот же паттерн, что
ProjectileSystem.RemoveDead).
Шов 4 — контекст. В ICombatContext: SpawnBarrier(in BarrierSpawn spawn) и
fire-and-forget ReportBarrierHit(...) для презентации (по образцу ReportAreaHit — не мутирует
сим, детерминизма не касается).
Шов 5 — данные. BarrierComponent : IRuntimeEffectComponent — эффект ставит барьер, прочность
через ScalableValue (скейлится статами источника, как ShieldComponent). Никакой новой ветки
авторинга: барьер приезжает обычным EffectData.
Шов 6 — AI. Блок скоринга «линия огня перекрыта» + опция «бить сам барьер» + предпочтение цели под куполом у мили. Едет вместе с позиционикой, в её же Filter / Score / Override. Это половина всей работы и главный риск фичи — см. требования в дизайне.
Шов 7 — презентация. BarrierView на Shapes: геометрия, полоска прочности, рябь в точке
попадания по ReportBarrierHit, заметное гашение при разрушении.
Что НЕ трогаем
- Навигация. Тела ходят сквозь барьеры,
SeparationSystemиMovementSystemо них не знают. Зона контроля отклонена, барьеры её не возвращают. DamagePipeline. Барьер перехватывает до пайплайна: брони, школы и сродства он не касается, поглощает урон после эффективности источника. Пайплайн остаётся чистым.SpatialHash. Барьеров на арене единицы; линейный обход дешевле индекса и детерминирован без оговорок. Если когда-нибудь станет десятки — тогда и подумаем.
Тесты (EditMode, рядом с ProjectileTests)
- Свободнолетящий снаряд гаснет о вражескую стену, урон ушёл в стену, цель цела.
- Самонаводящийся снаряд гаснет о стену (регресс-тест на гочу трекинга).
- Снаряд команды-владельца проходит сквозь свой барьер.
- Выстрел изнутри купола наружу проходит.
- Круговой АОЕ с центром снаружи не задевает цель под куполом; с центром внутри — задевает.
- Стена не блокирует круговой АОЕ.
- Хил-снаряд проходит сквозь любой барьер в обе стороны.
- Источник со свойством «игнорирует барьеры» проходит насквозь.
- Разрушение пула: снаряд, добивший барьер, гаснет; следующий летит свободно.
- Истечение срока и смерть носителя гасят купол.
- Два барьера на траектории: гасится о первый по ходу движения; при совпадении — тай-брейк по
Id. - Детерминизм: два прогона одного боя с барьерами дают одинаковую чек-сумму.
Оценка
| Кусок | Усилия |
|---|---|
Ядро: данные, BarrierField, BarrierSystem, швы 1–5, тесты 1–12 | ~1 заход |
| AI-осведомлённость (шов 6) | вместе с позиционикой, отдельно не считается |
| Презентация (шов 7) | ~0.5 захода |
| Баланс: прочность, радиусы, число носителей в ростере | по петле баланса, не разово |