task/0023-0028-navmesh #7
@@ -0,0 +1,219 @@
|
||||
# MVP World, Authority And Runtime NavMesh
|
||||
|
||||
## Status
|
||||
|
||||
Этот документ считается каноническим для решений по детерминированному миру, authority model и runtime NavMesh, пока его явно не заменят более новым архитектурным решением.
|
||||
|
||||
## Purpose
|
||||
|
||||
Зафиксировать долгосрочные решения для MVP, чтобы downstream-задачи по FishNet, worldgen, AI и persistence не уехали в разные стороны.
|
||||
|
||||
## Scope
|
||||
|
||||
- deterministic voxel world generation
|
||||
- authority model для session gameplay
|
||||
- runtime NavMesh в procedural world
|
||||
- риски WebGL-host режима
|
||||
|
||||
## Fixed Decisions
|
||||
|
||||
### 1. Базовый мир генерируется детерминированно и локально на каждом peer
|
||||
|
||||
Решение:
|
||||
- базовая геометрия мира не стримится от хоста по сети
|
||||
- каждый peer генерирует чанк локально из одинакового `seed`, одинакового `VoxelWorldConfig` и одинаковой версии world rules
|
||||
|
||||
Почему выбрано:
|
||||
- для WebGL и peer-host модели это минимизирует сетевой трафик
|
||||
- убирает постоянную сетевую репликацию геометрии чанков
|
||||
- снимает с хоста роль единственной точки генерации базового мира
|
||||
- хорошо сочетается с уже существующим `VoxelWorldGenerator`, который строит чанк из deterministic inputs
|
||||
|
||||
Почему не выбран host-generated world streaming:
|
||||
- хост получал бы лишнюю CPU-нагрузку на генерацию и лишнюю сетевую нагрузку на раздачу чанков
|
||||
- late join и догрузка дальних областей становились бы тяжелее по сети
|
||||
- это хуже укладывается в бюджет WebGL-host
|
||||
|
||||
Последствия:
|
||||
- `seed`, world config и их версия становятся частью session handshake
|
||||
- любое расхождение по config/version между peers недопустимо и должно считаться protocol drift
|
||||
|
||||
### 2. Host остается authoritative для NPC, AI и другого gameplay state
|
||||
|
||||
Решение:
|
||||
- NPC симулируются на хосте
|
||||
- pathfinding NPC, агро, боевые решения и каноническое положение NPC принадлежат хосту
|
||||
- клиенты получают состояние NPC по сети и могут делать только визуальное сглаживание
|
||||
|
||||
Почему выбрано:
|
||||
- NPC влияют на бой, урон, столкновения и progression, значит их нельзя отдавать в authority случайному клиенту
|
||||
- это радикально снижает риск читов и эксплуатационных багов
|
||||
- упрощает late join, reconnect и дебаг сетевой симуляции
|
||||
|
||||
Почему не выбран client-owned NPC:
|
||||
- ownership у первого встретившего игрока нестабилен при совместной игре
|
||||
- миграция owner во время боя ломает воспроизводимость path state, aggro state и hit timing
|
||||
- возрастает риск desync и эксплойтов
|
||||
- резко усложняется отладка и сопровождение
|
||||
|
||||
Последствия:
|
||||
- `client-authority` допустим только для ввода игрока и только при отдельной валидации на сервере
|
||||
- для NPC authority migration в MVP не используется
|
||||
|
||||
### 3. У чанков нет owner и нет chunk ownership migration
|
||||
|
||||
Решение:
|
||||
- чанк не закрепляется за конкретным игроком как за владельцем
|
||||
- базовый чанк является общей детерминированной сущностью мира, а не network-owned объектом
|
||||
|
||||
Почему выбрано:
|
||||
- при deterministic world generation ownership чанка не дает полезного выигрыша
|
||||
- chunk ownership добавляет coordination cost, миграцию ответственности и новые классы сетевых гонок без пользы для MVP
|
||||
- это плохо совместимо с late join и с будущими world deltas
|
||||
|
||||
Почему не выбран owner-per-chunk:
|
||||
- первый увидевший чанк игрок не является надежным authority source
|
||||
- потребуется сложная логика передачи владения при сближении игроков и при disconnect
|
||||
- любые расхождения по владельцу чанка приводят к hidden state drift
|
||||
|
||||
Последствия:
|
||||
- изменения чанка в будущем пойдут не через owner migration, а через authoritative world deltas от хоста
|
||||
|
||||
### 4. Runtime NavMesh строится локально на каждом peer по фактической локальной геометрии чанка
|
||||
|
||||
Решение:
|
||||
- NavMesh не реплицируется по сети как data blob
|
||||
- каждый peer строит NavMesh у себя локально из актуальной локальной геометрии мира
|
||||
- NavMesh всегда считается производным кэшем от world state, а не каноническим состоянием сессии
|
||||
|
||||
Почему выбрано:
|
||||
- NavMesh data тяжелая и плохо подходит для сетевой репликации в peer-host модели
|
||||
- при deterministic base world и одинаковых world deltas peers могут независимо прийти к одинаковой walkable topology
|
||||
- это сохраняет сеть для gameplay state, а не для производных навигационных артефактов
|
||||
|
||||
Почему не выбран network-streamed NavMesh:
|
||||
- лишний трафик и высокая сложность синхронизации
|
||||
- плохая масштабируемость для догрузки чанков и late join
|
||||
- NavMesh все равно пришлось бы пересобирать при локальных изменениях геометрии
|
||||
|
||||
Последствия:
|
||||
- каноничность gameplay не должна зависеть от клиентского NavMesh
|
||||
- client NavMesh используется для локальных потребностей, но authoritative decisions по NPC остаются у хоста
|
||||
|
||||
### 5. Будущие изменения проходимости мира передаются как authoritative world deltas
|
||||
|
||||
Решение:
|
||||
- базовый мир идет из deterministic generation
|
||||
- любые будущие баррикады, спеллы, разрушаемость, carve и другие изменения мира передаются как authoritative deltas от хоста
|
||||
- после применения delta каждый peer локально перестраивает затронутые nav regions
|
||||
|
||||
Почему выбрано:
|
||||
- это отделяет immutable base generation от mutable session state
|
||||
- обеспечивает late join: новому игроку можно отдать base seed/config и журнал world deltas
|
||||
- не требует вводить ownership migration для чанков
|
||||
|
||||
Почему не выбран fully local mutable world:
|
||||
- local-first изменения мира не могут быть каноническими в кооперативной сетевой игре
|
||||
- конфликтуют с античитом, late join и persistence
|
||||
|
||||
Последствия:
|
||||
- NavMesh pipeline обязан уметь маркировать локальные nav regions как dirty после world delta
|
||||
|
||||
### 6. NavMesh pipeline должен работать в single-thread budget; многопоточность в WebGL считается только опциональным ускорением
|
||||
|
||||
Решение:
|
||||
- архитектура runtime NavMesh не должна зависеть от наличия потоков
|
||||
- базовый режим должен укладываться в бюджет кадра на одном потоке
|
||||
- если deployment позже подтвердит поддержку `SharedArrayBuffer` и `COOP/COEP`, можно добавить threaded optimization, но не делать ее обязательной
|
||||
|
||||
Почему выбрано:
|
||||
- WebGL-host остается одной из целевых платформ
|
||||
- WebGL multithreading требует специальных заголовков и эксплуатационной дисциплины на стороне хостинга
|
||||
- завязка critical gameplay pipeline на эту инфраструктуру слишком рискованна для MVP
|
||||
|
||||
Почему не выбран threaded-only pipeline:
|
||||
- он может работать в editor/desktop и развалиться в реальном WebGL deployment
|
||||
- создаст ложное ощущение приемлемого бюджета, которого не будет на production-hosting
|
||||
|
||||
Последствия:
|
||||
- rebuild должен быть incremental, throttled и bounded
|
||||
- полносценовый bake вокруг камеры не подходит как каноническая модель
|
||||
|
||||
### 7. Первая итерация NavMesh покрывает область вокруг player actor, но долгосрочный контракт расширяется до players + active NPC
|
||||
|
||||
Решение:
|
||||
- для первой проверки гипотезы build priority привязывается к player actor
|
||||
- целевой контракт для multiplayer host: nav coverage должна учитывать игроков и активных NPC
|
||||
|
||||
Почему выбрано:
|
||||
- это минимальный объем для MVP-проверки без ранней переплаты за сложную interest model
|
||||
- при этом заранее фиксируется, что player-only coverage не является конечной архитектурой
|
||||
|
||||
Почему не выбран camera-driven center:
|
||||
- камера не является каноническим gameplay actor
|
||||
- в multiplayer и especially on host камера может не совпадать с зоной активной симуляции
|
||||
- привязка к `Camera.main` ломает переносимость решения из test scene в сетевую сессию
|
||||
|
||||
Последствия:
|
||||
- в коде нельзя оставлять `Camera.main` как канонический источник world/nav interest
|
||||
- target должен представлять actor-level interest, а не presentation-level camera
|
||||
|
||||
### 8. Для MVP поддерживается один тип NavMesh agent
|
||||
|
||||
Решение:
|
||||
- сейчас поддерживается только один `agentTypeID`
|
||||
|
||||
Почему выбрано:
|
||||
- проект на стадии hypothesis/MVP
|
||||
- это уменьшает стоимость runtime bake и настройки AI Navigation
|
||||
- не раздувает матрицу тестирования до появления реальной необходимости
|
||||
|
||||
Почему не выбран multi-agent bake сразу:
|
||||
- рост CPU и memory costs
|
||||
- усложнение отладки при почти нулевой текущей пользе
|
||||
|
||||
Последствия:
|
||||
- при появлении разных классов существ нужно отдельно пересмотреть agent taxonomy
|
||||
|
||||
### 9. В этой фазе решение остается scene-local и не привязывается к VContainer или Addressables
|
||||
|
||||
Решение:
|
||||
- runtime NavMesh реализуется как часть текущего scene-local world runtime
|
||||
- VContainer и Addressables в этой задаче не вводятся
|
||||
|
||||
Почему выбрано:
|
||||
- в проекте пока нет production-ready composition root поверх gameplay world
|
||||
- принудительное добавление DI boundary сейчас даст больше шума, чем пользы
|
||||
- Addressables не подключены и не требуются для гипотезы NavMesh generation
|
||||
|
||||
Почему не выбран ранний DI/bootstrap refactor:
|
||||
- это отвлекает от основной гипотезы по производительности и корректности NavMesh
|
||||
- возникает преждевременная архитектурная сложность при еще нестабильных правилах мира
|
||||
|
||||
Последствия:
|
||||
- код должен оставаться достаточно изолированным, чтобы позже его можно было вынести в runtime service
|
||||
|
||||
## Long-Term Risks
|
||||
|
||||
### Critical
|
||||
|
||||
- WebGL-host может не выдержать одновременно world streaming, runtime NavMesh rebuild и server-authoritative NPC AI.
|
||||
- Любой drift по `seed`, `VoxelWorldConfig` или world rules между peers приведет к расхождению геометрии и локального NavMesh.
|
||||
|
||||
### High
|
||||
|
||||
- Цель в `50` активных NPC может упереться не в один subsystem, а в суммарный CPU budget хоста.
|
||||
- Будущие изменения геометрии потребуют точной invalidation strategy по nav regions; без нее rebuild cost быстро выйдет из-под контроля.
|
||||
- Если client movement в будущем начнет опираться на локальный NavMesh как на authority source, появятся расхождения с host simulation.
|
||||
|
||||
### Medium
|
||||
|
||||
- Late join требует не только `seed/config`, но и корректного воспроизведения authoritative world deltas.
|
||||
- Если region size выбрать слишком крупным, rebuild будет дорогим; если слишком мелким, возрастет число build operations и seam-risk на границах.
|
||||
|
||||
## Downstream Implications
|
||||
|
||||
- `TASK-0001`: этот документ закрывает часть канонических MVP-решений по world/authority/navmesh.
|
||||
- `TASK-0002`: session handshake должен включать world seed, config/version и protocol compatibility checks.
|
||||
- `TASK-0012`: enemy AI проектируется только как host-authoritative.
|
||||
- `TASK-0023`: runtime NavMesh обязан быть local-build, throttled и без camera-driven assumptions.
|
||||
@@ -0,0 +1,229 @@
|
||||
# TASK-0023 Runtime NavMesh Implementation Plan
|
||||
|
||||
## Goal
|
||||
|
||||
Реализовать runtime NavMesh для procedural voxel world без фризов и без camera-driven assumptions, с архитектурой, совместимой с будущей peer-host multiplayer моделью.
|
||||
|
||||
## Inputs And Assumptions
|
||||
|
||||
- текущая test scene: `Assets/Features/VoxelWorld/Scenes/VoxelWorldTestScene.unity`
|
||||
- основной runtime: `Assets/Features/VoxelWorld/Runtime/VoxelWorldGenerator.cs`
|
||||
- текущий world config asset:
|
||||
- `chunkSize = 16`
|
||||
- `generationRadius = 3`
|
||||
- `maxMountainHeight = 6`
|
||||
- `renderRegionSizeInChunks = 4`
|
||||
- `maxAsyncChunkJobs = 2`
|
||||
- `maxChunkBuildsPerFrame = 1`
|
||||
- `maxChunkMeshBuildsPerFrame = 1`
|
||||
- `maxColliderAppliesPerFrame = 1`
|
||||
- первая итерация учитывает область вокруг player actor
|
||||
- долгосрочный контракт остается `players + active NPC`
|
||||
- один тип агента
|
||||
- динамические изменения мира пока не реализуются, но точки расширения под них должны быть предусмотрены
|
||||
|
||||
## Chosen Technical Direction
|
||||
|
||||
### 1. Не использовать `NavMeshSurfaceVolumeUpdater` как основу решения
|
||||
|
||||
Причина:
|
||||
- пример из samples двигает один build volume за tracked agent и не подходит как production-модель для chunk streaming
|
||||
- он делает слишком coarse-grained rebuild и оставляет мало контроля над budget, dirty queue и multi-region state
|
||||
|
||||
### 2. Использовать ручной runtime pipeline через `NavMeshBuilder.UpdateNavMeshDataAsync`
|
||||
|
||||
Причина:
|
||||
- дает прямой контроль над build sources, bounds, lifecycle `NavMeshData` и количеством одновременных rebuild
|
||||
- позволяет отказаться от scene-wide source collection и собирать только известные chunk sources
|
||||
- лучше подходит для throttling под WebGL-host
|
||||
|
||||
### 3. Строить NavMesh не per-chunk, а по небольшим nav regions
|
||||
|
||||
Выбор:
|
||||
- отдельный `NavMeshData` на nav region
|
||||
- стартовый размер region рекомендуется сделать `2x2` чанка, configurable отдельно от render regions
|
||||
|
||||
Почему выбран region-based подход:
|
||||
- per-chunk rebuild создает слишком много мелких операций и лишние seam-риски на границах
|
||||
- один большой sliding volume вокруг interest target слишком дорог для WebGL-host
|
||||
- небольшой region дает контролируемый компромисс между стоимостью rebuild и связностью навигации
|
||||
|
||||
### 4. Источник build sources брать из runtime collider-геометрии чанков
|
||||
|
||||
Выбор:
|
||||
- `GroundCollider` каждого чанка дает box source
|
||||
- `MountainCollider.sharedMesh` дает mesh source
|
||||
|
||||
Почему так:
|
||||
- не нужно сканировать всю сцену
|
||||
- не нужно строить отдельную nav-only геометрию на первом этапе
|
||||
- collider topology уже является ближайшим к gameplay физическим представлением поверхности
|
||||
|
||||
### 5. Rebuild делать через dirty queue и budgeted scheduler
|
||||
|
||||
Выбор:
|
||||
- region помечается dirty при `ApplyColliderMesh` и при unload чанка
|
||||
- scheduler сортирует dirty regions по расстоянию до interest actor
|
||||
- одновременно идет максимум один nav rebuild
|
||||
- если region снова стал dirty во время build, версия region увеличивается и после завершения запускается новый rebuild только для актуальной версии
|
||||
|
||||
Почему так:
|
||||
- это bounded и предсказуемо для WebGL-host
|
||||
- исключает лавинообразные rebuild при быстром перемещении игрока
|
||||
|
||||
## Proposed Runtime Structure
|
||||
|
||||
### File Placement
|
||||
|
||||
- расширить `VoxelWorldGenerator` новыми partial-файлами, а не вводить отдельный service layer на этой стадии
|
||||
- рекомендуемые файлы:
|
||||
- `Assets/Features/VoxelWorld/Runtime/VoxelWorldGenerator.NavMesh.cs`
|
||||
- `Assets/Features/VoxelWorld/Runtime/VoxelWorldGenerator.NavMesh.Types.cs`
|
||||
|
||||
Причина:
|
||||
- nav lifecycle напрямую зависит от chunk lifecycle, которым уже владеет `VoxelWorldGenerator`
|
||||
- это минимальное изменение без раннего DI/refactor
|
||||
|
||||
### New Runtime Data
|
||||
|
||||
- `NavRegionRuntime`
|
||||
- `NavMeshData NavMeshData`
|
||||
- `NavMeshDataInstance Instance`
|
||||
- `AsyncOperation ActiveBuild`
|
||||
- `int Version`
|
||||
- `bool IsDirty`
|
||||
- `bool BuildRequestedWhileRunning`
|
||||
- `Bounds BuildBounds`
|
||||
- `List<NavMeshBuildSource>` reusable sources buffer
|
||||
- `Queue<Vector2Int> dirtyNavRegions`
|
||||
- `HashSet<Vector2Int> queuedNavRegions`
|
||||
- `Dictionary<Vector2Int, NavRegionRuntime> navRegions`
|
||||
|
||||
### New Config Settings
|
||||
|
||||
Добавить в `VoxelWorldConfig` отдельную секцию `NavMesh`:
|
||||
- `int navRegionSizeInChunks = 2`
|
||||
- `int maxNavMeshBuildsPerFrame = 1`
|
||||
- `int maxConcurrentNavMeshBuilds = 1`
|
||||
- `float navBoundsVerticalPadding`
|
||||
- `float navBoundsHorizontalPadding`
|
||||
- `int navWarmupRadiusInRegions`
|
||||
|
||||
Примечание:
|
||||
- `maxConcurrentNavMeshBuilds` для первой итерации должен остаться `1`
|
||||
- horizontal padding нужен для корректной стыковки границ region data
|
||||
|
||||
## Integration With World Lifecycle
|
||||
|
||||
### Chunk load / update flow
|
||||
|
||||
1. `GenerateChunkData` завершает данные чанка.
|
||||
2. `RenderChunk` собирает render snapshot и collider mesh.
|
||||
3. После фактического применения collider mesh chunk помечает свой nav region dirty.
|
||||
4. Если чанк лежит у границы nav region, дополнительно dirty-mark соседний region, который делит с ним границу.
|
||||
5. Scheduler позже запускает rebuild региона по budget.
|
||||
|
||||
### Chunk unload flow
|
||||
|
||||
1. Перед `runtime.Dispose()` определить nav region чанка.
|
||||
2. Пометить соответствующий region dirty.
|
||||
3. Если region стал пустым и вышел из active nav range, удалить его `NavMeshDataInstance`.
|
||||
|
||||
### Interest target flow
|
||||
|
||||
1. Убрать каноническую зависимость от `Camera.main` как источника стриминга/nav interest.
|
||||
2. Ввести actor-level target semantics.
|
||||
3. Для сохранения сцены использовать rename с `FormerlySerializedAs`, если будет меняться имя поля.
|
||||
4. Для первой итерации target задается явно со сцены или от будущего player actor bootstrap.
|
||||
|
||||
## Region Build Flow
|
||||
|
||||
1. Определить `regionCoord` по координате чанка.
|
||||
2. Вычислить `Bounds` региона с padding по XZ и по высоте.
|
||||
3. Собрать build sources только из чанков, попадающих в region и в соседний margin вокруг него.
|
||||
4. Для каждого активного чанка добавить:
|
||||
- `NavMeshBuildSourceShape.Box` из `GroundCollider`
|
||||
- `NavMeshBuildSourceShape.Mesh` из `MountainCollider.sharedMesh`, если mesh не пустой
|
||||
5. Запустить `NavMeshBuilder.UpdateNavMeshDataAsync` для region-local `NavMeshData`.
|
||||
6. При завершении проверить актуальность версии и либо оставить data, либо сразу перезапустить rebuild актуальной версии.
|
||||
|
||||
## Region Granularity And Boundary Rules
|
||||
|
||||
### Start choice
|
||||
|
||||
- `navRegionSizeInChunks = 2`
|
||||
|
||||
Почему не `1`:
|
||||
- слишком много мелких `NavMeshData`
|
||||
- больше seam pressure на стыках
|
||||
- выше scheduler overhead
|
||||
|
||||
Почему не `4`:
|
||||
- rebuild слишком дорогой для частого runtime update на WebGL-host
|
||||
- это уже заметный кусок от всего active world при `generationRadius = 3`
|
||||
|
||||
### Boundary handling
|
||||
|
||||
- build bounds должны быть больше чистого region rectangle
|
||||
- source collection должна захватывать соседние чанки на один region-margin
|
||||
- region dirty-mark должен учитывать chunk changes на границах
|
||||
|
||||
Причина:
|
||||
- без overlap на границах легко получить cracks и непредсказуемую связность между соседними `NavMeshData`
|
||||
|
||||
## Multiplayer And Authority Contract For This Task
|
||||
|
||||
- базовый voxel world генерируется локально у каждого peer из одинакового deterministic input
|
||||
- NavMesh строится локально у каждого peer и не реплицируется по сети
|
||||
- authoritative gameplay использует host-side NPC simulation
|
||||
- текущая итерация NavMesh coverage вокруг player actor считается временным MVP simplification
|
||||
- при переходе к реальной multiplayer-сцене host должен строить priority coverage вокруг `players + active NPC`
|
||||
- будущие world changes должны приходить как authoritative deltas и маркировать nav regions dirty локально на каждом peer
|
||||
|
||||
## Performance Rules
|
||||
|
||||
- не делать full-scene bake
|
||||
- не пересобирать NavMesh синхронно через `BuildNavMesh()` на gameplay path
|
||||
- не сканировать произвольные scene objects через generic collection APIs, если можно собрать sources из известных chunk runtimes
|
||||
- держать максимум один активный build
|
||||
- переиспользовать buffers, где это возможно
|
||||
- rebuild запускать только после фактического применения collider mesh
|
||||
- unload и load чанков должны только маркировать region dirty, а не запускать немедленный build вне scheduler
|
||||
|
||||
## Verification Plan
|
||||
|
||||
### Manual verification
|
||||
|
||||
1. Запустить `VoxelWorldTestScene`.
|
||||
2. Использовать debug `NavMeshAgent` из AI Navigation samples.
|
||||
3. Проверить, что агент строит путь по поверхности уже загруженных чанков.
|
||||
4. Быстро перемещать actor target по миру и отслеживать отсутствие заметных фризов.
|
||||
5. Проверить unload чанков: после ухода области старый NavMesh не должен оставлять висячие walkable islands в уже удаленных регионах.
|
||||
|
||||
### Debug instrumentation
|
||||
|
||||
- gizmos для region bounds и состояния region build
|
||||
- лог счетчиков:
|
||||
- active nav regions
|
||||
- dirty nav regions
|
||||
- builds started/completed/cancelled as stale
|
||||
|
||||
## Explicit Non-Goals For This Iteration
|
||||
|
||||
- NavMeshObstacle carving
|
||||
- multi-agent bake
|
||||
- DI integration через VContainer
|
||||
- Addressables integration
|
||||
- ownership migration для чанков или NPC
|
||||
- финальная multiplayer interest model вокруг всех actors
|
||||
|
||||
## Execution Order
|
||||
|
||||
1. Добавить nav settings в `VoxelWorldConfig` и resolved settings.
|
||||
2. Добавить runtime структуры nav regions и dirty scheduler в `VoxelWorldGenerator`.
|
||||
3. Привязать dirty-marking к chunk collider apply и unload.
|
||||
4. Реализовать source collection из chunk colliders.
|
||||
5. Реализовать region-local `NavMeshData` lifecycle и async rebuild.
|
||||
6. Убрать camera-driven fallback из world/nav interest path.
|
||||
7. Добавить debug visualization и ручную проверку через sample agent.
|
||||
8. Задокументировать фактические perf observations после первой проверки гипотезы.
|
||||
@@ -6,12 +6,14 @@ priority: Highest
|
||||
area: ai
|
||||
owner: abysscion
|
||||
created: 2026-03-31
|
||||
updated: 2026-04-07
|
||||
updated: 2026-04-08
|
||||
execution_time: 2d
|
||||
depends_on:
|
||||
- TASK-0003
|
||||
canonical_docs:
|
||||
- docs/tasks/Index.md
|
||||
- docs/architecture/mvp-world-authority-navmesh.md
|
||||
- docs/plans/TASK-0023-runtime-navmesh-implementation-plan.md
|
||||
related_files:
|
||||
- Assets/Features/VoxelWorld/Runtime/VoxelWorldGenerator.cs
|
||||
---
|
||||
@@ -94,4 +96,4 @@ AI врагов (`TASK-0012`) опирается на NavMesh. Воксельн
|
||||
|
||||
## Handoff Notes
|
||||
|
||||
Если в проекте нет пакета NavMeshComponents, возможно придется добавить его или реализовать минимальный runtime builder.
|
||||
Если в проекте нет пакета NavMeshComponents, возможно придется добавить его или реализовать минимальный runtime builder.
|
||||
|
||||
Reference in New Issue
Block a user