Imported from ZhukovskyGamesStudio/takeRoot (
.claude/skills/takeroot-ui-mvp/SKILL.md). Install upstream withnpx skills add ZhukovskyGamesStudio/takeRoot --skill takeroot-ui-mvp. Copyright stays with the author.
Take Root — UI (uGUI + MVP)
Сначала
takeroot-project-context. Стек — uGUI + TextMeshPro только; UI Toolkit в геймплее нет. ⚠️UIElementsSchemaв корне репо — это редакторные XSD, игнорируй.
MVP везде
- View = пассивный MonoBehaviour:
[SerializeField]-виджеты + методыSetData/Init(тупой рендер). - Presenter = plain-C# класс, создаётся в
CoreEntryPoint.InitPresenters()(точка регистрации), держит сервисы черезServiceLocator.Single<T>(), подписывается на реактивное состояние, частоIUpdatable+IDisposable.
Рецепт: добавить панель
- Добавь значение в
enum PanelType. - В сцене: GameObject панели →
CoreCanvasUi.PanelsView.Panels;Toggle→PanelTogglesView.Toggles; соответствующую записьToggleData(требования по Research/Race). - Создай
View(SetData/Init) + plain-C#Presenter. - Сошлись на
ViewизCoreCanvasUiилиCoreEntryPoint. - Создай
PresenterвCoreEntryPoint.InitPresenters, прокинувSingle<IService>()-зависимости. - Расово-вариативный визуал — через
IHasRaceVariant, не ветвлением по расе.
Конвенции
- Открытие/закрытие:
SerializedDictionary<enum, Toggle/GameObject>+Toggle.onValueChanged+GameObject.SetActive. - Панели взаимоисключающи: открытие панели снимает выделение, выделение закрывает панели.
- Видимость toggle гейтится
ToggleData(ResearchRequirement/RaceRequirement) черезUpdatePanels.
Реактивщина
- UniRx
ReactivePropertyдля выделения / pending-команды / готовности времени / ingame-времени. - Plain
Action-события для сигналов (OnResearchFinished,NewNotification,OnJobChanged). - Пер-кадровый рефреш —
IUpdatable+IUpdateServiceилиUpdate/FixedUpdateво View. ⚠️ Следи за пер-кадровым LINQ/аллокациями (горячая точка —TimeStatusUIcolony stress).
Сетевой сплит UI
- Клиент-локальные: панели, выделение, инфо, камера, нотификации, оверлеи, ingame-часы.
- Сетевые:
- Скорость/пауза —
GameSpeedView→ServerRpc→NetworkDataHolder.SetGameSpeedClientRpc, выставляет глобальныйTime.timeScale(побеждает минимум из двух игроков; пауза гейтится заряженнойTimeMachine). - Исследования —
NetworkDataHolder.ResearchNetworkData.
- Скорость/пауза —
- ⚠️
NetworkDataHolder.Instanceдолжен быть заспавнен до вызовов времени/исследований (иначе NRE). См.takeroot-netcode.
Время
Time.timeScale — единственные синхронизируемые часы (глобально через ClientRpc). ⚠️ UI, который должен работать на паузе, нужен на unscaledDeltaTime (по большей части не сделано). IngameTimeService — по-клиентный (не сетевой), масштабируется с timeScale.
Выделение → инфо
ISelectionService.SelectedReactive— единый источник истины.SelectionServicePresentertype-switch'итSettlerSelectablevsCommandTargetSelectable. ⚠️ Безусловный каст кInfoDataCombinedможет бросить.SettlerInfoPanelсвапает render-слой + портрет-камеру.- Выделение выключено, пока висит pending
JobType-команда.
Известные опасные места
⚠️ требует проверки у Гоши.
- ⚠️
Dispose()презентеров часто пуст/не вызывается → подписки живут весь lifetime сцены, утекают при перезагрузке. - ⚠️ Смешан legacy
Inputи новый Input System (камера);EventSystem.currentразыменовывается без null-чека. OverlayService— заглушка (см.takeroot-grid-pathfinding).CoreCanvasUi.OpenInfoPanel—[Obsolete](используйSelectionServicePresenter).- Строки RU/EN захардкожены, локализации нет.
- Кнопки меню провязаны через инспектор
onClick— открой префаб, чтобы найти вызовы.MenuEntryPoint.Play— host-only.