name: ss14-npc-system-core
description: Глубокий разбор NPC-системы в SS14/Sunrise: HTN-планирование, utility-выбор целей, steering/pathfinding, blackboard и контракты выполнения. Используй, когда нужно понять общую схему и логику NPC, создать или переработать прототипы поведения (htnCompound, rootTask, blackboard), либо написать свой код ИИ (операторы, предусловия, компоненты, системы) и безопасно встроить его в runtime.
NPC System: Архитектура, Прототипы и Кастомный ИИ
Используй этот skill как основной playbook по NPC в SS14/Sunrise.
Держи фокус на свежем коде и проверяй актуальность через git log/git blame (cutoff: 2024-02-20).
Что загружать в первую очередь
references/architecture-runtime.md— общая схема работы, слои и жизненный цикл NPC.references/behavior-prototypes.md— как проектировать и писать прототипы поведения (htnCompound,utilityQuery,HTNcomponent).references/custom-ai-code.md— как и где писать свой код ИИ (операторы, предусловия, системы, компоненты).references/debug-validation.md— отладка, проверка и защита от регрессий.
Общая схема работы NPC
NPCSystemобновляет только активных NPC (ActiveNPCComponent) и ограничивает бюджет апдейта (npc.max_updates).HTNSystemпланирует поведение через time-sliced job queue (HTNPlanJob) и исполняет текущий план.HTNPlanJobразворачивает деревоHTNCompoundTask->HTNPrimitiveTask, выбирая branch по preconditions.HTNOperator.Plan()оценивает валидность шага и может вернутьeffectsдля blackboard.HTNOperator.Startup()/Update()/TaskShutdown()выполняет runtime-логику.- Тяжелую/координируемую логику оператор обычно делегирует внешним системам через компоненты (steering/combat и т.п.).
NPCSteeringSystemведет перемещение, pathfinding и obstacle avoidance.NPCUtilitySystemвыбирает цели черезutilityQuery+ considerations/curves.NpcFactionSystemвлияет на выбор враждебных/дружественных целей.
Принципы и логика проектирования
- Держи
Plan()максимально чистым и предсказуемым: вычисляй, а не мутируй мир. - Выполняй side effects в
Startup()/Update(), а cleanup в shutdown-хуках. - Разделяй “выбрать цель”, “дойти до цели”, “сделать действие” на разные примитивы.
- Используй blackboard как единый контракт между прототипом и кодом.
- Проектируй behavior как композицию маленьких compounds, а не giant-tree.
- Всегда добавляй fallback-ветку (idle/noop), чтобы NPC не зависал без плана.
- Для движений и боя опирайся на существующие системы (
NPCSteeringSystem,NPCCombatSystem), а не на ad-hoc логику в операторе.
Паттерны
- Начинать root-behavior с приоритетной ветки (combat/goal), затем fallback (idle/follow).
- Добавлять
UtilityOperatorперед действием, если цель динамическая. - Обновлять цель сервисом (
services) внутри боевых примитивов. - Делать
MoveToOperatorотдельным шагом между выбором цели и действием. - Хранить диапазоны/флаги в blackboard (
VisionRadius,MeleeRange,NavInteract) для тонкой настройки без кода. - Возвращать
effectsизPlan()для переиспользования дорогих вычислений в execution. - Использовать
IHtnConditionalShutdown, когда нужен управляемый cleanup приTaskFinishedилиPlanFinished. - Писать предусловия как простые булевы gate-объекты без побочных эффектов.
- Отключать/включать HTN через
SetHTNEnabled/replan-паттерны вместо прямой мутации внутренних полей. - Валидировать дерево на рекурсивные ловушки отдельным тестом.
Анти-паттерны
- Писать монолитный
htnCompound, который сложно анализировать и тестировать. - Делать побочные эффекты в
Plan()(особенно сетевые/физические изменения). - Не чистить blackboard и runtime-компоненты при завершении задачи.
- Полагаться только на один target key без fallback-логики.
- Игнорировать
services, из-за чего NPC “залипает” на устаревшей цели. - Смешивать pathfinding/steering/combat вручную в одном операторе.
- Писать precondition, которая зависит от скрытого mutable-state вне blackboard/компонентов.
- Использовать невалидные типы в
blackboardYAML (отсутствие!typeдля сложных/явных типов). - Строить behavior без ветки idle/noop.
- Игнорировать budget/cooldown (
PlanCooldown,npc.max_updates) при диагностике “тупящих” NPC.
Мини-примеры
1) Root prototype с fallback
- type: htnCompound
id: MyCustomHostileCompound
branches:
- tasks:
- !type:HTNCompoundTask
task: MeleeCombatCompound
- tasks:
- !type:HTNCompoundTask
task: IdleCompound
2) Подключение HTN к сущности
- type: entity
id: MobMyNpc
components:
- type: HTN
rootTask:
task: MyCustomHostileCompound
blackboard:
VisionRadius: !type:Single
18
AggroVisionRadius: !type:Single
24
NavInteract: !type:Bool
true
3) Кастомный оператор
public sealed partial class MyOperator : HTNOperator
{
[Dependency] private readonly IEntityManager _entManager = default!;
public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
{
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
// Передать heavy/runtime-логику в отдельную систему или компонент.
return HTNOperatorStatus.Finished;
}
}
Правило расширения
- При добавлении новой цели/поведения сначала расширять
utilityQueryи прототипы tasks. - Переходить к новому C# коду только когда прототипов и текущих операторов уже недостаточно.
- Для новой механики сначала определить extension point (precondition/operator/system/component), затем писать код.
- После любого расширения прогонять debug-проверку домена и smoke-тест с реальным NPC.
Думай о NPC-системе как о конвейере планирование -> исполнение -> обслуживание состояния, а не как о случайном наборе операторов.