Показываю, как писать интерфейсы Unity на SUS: реактивный счётчик, форму с вычисляемыми значениями, переходы между экранами и сохранение их состояния. Разметка, C# и стили живут в одном файле .sharq. Ядро и роутер теперь доступны на GitHub с открытым исходным кодом. Их также можно установить бесплатным набором Core из Unity Asset Store. Там же есть Kit — набор с готовыми компонентами интерфейса. Читать далее
Уровень сложностиСредний
Время на прочтение8 мин
Охват и читатели7.7K
Обзор

В первой статье я рассказывал, как устроена наша надстройка над UI Toolkit: формат .sharq, генерация C# и темы оформления. Но почти не показал, как с её помощью писать интерфейс. Как обновить текст после нажатия кнопки? Где хранить состояние формы? Как переключать экраны и возвращаться к тому месту, на котором остановился игрок?
За это время SUS вырос из внутреннего инструмента. Ядро и роутер теперь доступны на GitHub с открытым исходным кодом. Их также можно установить бесплатным набором Core из Unity Asset Store. Там же есть Kit — набор с готовыми компонентами интерфейса. В этой статье разберу несколько примеров: от счётчика до навигации между экранами.
Что теперь можно попробоватьОснова SUS — два пакета. sus-core отвечает за компоненты, реактивные данные, генерацию кода и темы. sus-router добавляет переходы между экранами, историю навигации и модальные окна. Оба пакета можно взять на GitHub под MIT.
Если удобнее устанавливать через Asset Store, там уже есть два набора:
Sharq UI System Core — бесплатный набор с ядром и роутером.
Sharq UI System Kit — ядро, роутер и готовые компоненты: кнопки, поля ввода, таблицы, диалоги и другие элементы интерфейса.
Для пакетов с GitHub действует MIT, для наборов из Asset Store — лицензия Asset Store. Примеры ниже используют ядро, роутер и стандартные элементы UI Toolkit.
На сайте собраны документация и каталог компонентов. Там же можно открыть демо в браузере и посмотреть, как они ведут себя при взаимодействии.

Каталог компонентов SUS
Часть компонентов из Storybook — среды, где мы смотрим их состояния и проверяем поведение.
Счётчик: разметка, логика и стили рядомНачну с небольшого примера. В .sharq разметка, C# и стили находятся в одном файле. Если вы работали с Vue, структура будет знакомой.
Я выбрал её в том числе потому, что так мне проще верстать компоненты: дерево элементов видно в разметке, а стили и логика находятся рядом. Когда нужно переделать уже готовый компонент, можно поменять расположение элементов, добавить новый блок и сразу описать его оформление и поведение в том же файле.
Вот как выглядит обычный счётчик:
<!-- Counter.sharq -->
<template>
<ui:VisualElement $MainElement class="counter">
<ui:Label :text="Count" class="counter__value" />
<ui:Button text="+1" @click="OnInc" />
</ui:VisualElement>
</template>
<script>
public Prop<int> Count = new(0);
private void OnInc() => Count.Value++;
</script>
<style>
.counter { flex-direction: row; align-items: center; }
.counter__value { font-size: 24px; margin-right: 12px; }
</style>
Prop<int> хранит число и сообщает об изменении. Запись :text="Count" связывает его с текстом Label. Кнопка вызывает OnInc(), число увеличивается, подпись обновляется. В обработчике не нужно искать лейбл и присваивать ему новый текст.
Чтобы показать компонент в UIDocument, используем SusApp:
SusApp.Create(uiDocument)
.UseTheme(SusTheme.Dark)
.Mount<Counter>();
Здесь uiDocument — ссылка на UIDocument в сцене, а Counter — класс, который редактор сгенерировал из нашего файла.

Реактивный счётчик
Счётчик в демо: кнопка меняет данные, связанные с ними подписи обновляются автоматически.
Сам .sharq нужен на этапе разработки. В редакторе Unity он превращается в C# и USS. В сгенерированном коде есть обычные вызовы, которые создают элементы и подключают биндинги. При запуске игры разбирать разметку заново не требуется.

Компиляция .sharq в C# и USS
Разметка и логика проходят через генератор в редакторе. В сборку попадает результат.
Форма: когда одно значение зависит от другихСо счётчиком всё просто. В форме обычно больше связей: подсказка зависит от введённых данных, а кнопка должна стать доступной только после проверки полей.
Для таких значений есть Computed<T>. Он запоминает результат и пересчитывает его при следующем чтении после изменения зависимостей. Зависимости определяются по обращениям к .Value внутри вычисления.
Ниже учебная форма регистрации. Проверка email намеренно упрощена; для настоящей регистрации понадобятся серверная проверка и обработка ошибок.
<!-- SignupForm.sharq -->
<template>
<ui:VisualElement $MainElement class="form">
<ui:TextField name="email" label="Email" @change="OnEmail" />
<ui:TextField name="password" label="Password" @change="OnPassword" />
<ui:Label :text="Hint" />
<ui:Button name="submit" text="Sign up" @click="OnSubmit" />
</ui:VisualElement>
</template>
<script>
public Prop<string> Email = new("");
public Prop<string> Password = new("");
public Computed<bool> CanSubmit;
public Computed<string> Hint;
protected override void Created()
{
CanSubmit = C(() =>
Email.Value.Contains("@") && Password.Value.Length >= 8);
Hint = C(() => CanSubmit.Value
? "Можно отправлять"
: "Укажите email и пароль от 8 символов");
}
protected override void Mounted()
{
this.Q<TextField>("password").isPasswordField = true;
var submit = this.Q<Button>("submit");
WatchEffect(() => submit.SetEnabled(CanSubmit.Value));
}
void OnEmail() => Email.Value = this.Q<TextField>("email").value;
void OnPassword() => Password.Value = this.Q<TextField>("password").value;
void OnSubmit()
{
if (!CanSubmit.Value) return;
// Здесь можно вызвать сервис регистрации.
}
</script>
В Created() вычисляемые значения создаются один раз, до построения разметки. В Mounted() дочерние элементы уже доступны: здесь включаем маску пароля и связываем доступность кнопки с CanSubmit.
@change вызывает метод без аргумента события, поэтому значение поля в этом примере читается через Q<TextField>(). Подсказку обновляет биндинг в шаблоне, а кнопку — WatchEffect: он повторяет действие, когда меняются прочитанные внутри него реактивные значения.
На этой форме уже видно, ради чего всё затевалось. Условия проверки находятся рядом с данными. При изменении поля не приходится вручную обновлять и подсказку, и кнопку в нескольких обработчиках.
Vue Router для UI ToolkitСледующая задача — переключение экранов. У небольшого меню быстро появляются настройки, профиль, лобби, окно подтверждения. Нужно помнить, откуда пришёл пользователь, иногда запрещать переход, иногда сохранять состояние экрана.
В sus-router экрану назначается путь, например /settings или /user/42. Это адрес внутри интерфейса, он не требует веб-сервера. Push добавляет переход в историю, Replace заменяет текущую запись, Back возвращает назад.
Маршруты можно описать при запуске приложения:
using Sharq.Core;
using Sharq.Router;
SusApp.Create(GetComponent<UIDocument>())
.UseTheme(SusTheme.Dark)
.UseRouter(new SusRouter(), routes => routes
.Route("/", typeof(HomeScreen)).Name("home")
.Route("/user/:id", typeof(UserScreen)).KeepAlive()
.Route("/settings", typeof(SettingsLayout)).Children(c => c
.Route("profile", typeof(ProfileScreen))),
initialPath: "/")
.Run();
HomeScreen, UserScreen и остальные типы здесь — компоненты вашего проекта. У настроек есть вложенный экран profile, а :id обозначает параметр маршрута.

Переходы между экранами
Пример навигации из пакета: вкладки и переходы через Push/Replace.
Проверка перед переходомДопустим, часть экранов доступна только после входа. Общую проверку можно подключить через BeforeEach. Фрагмент из примера Guards:
_router.BeforeEach((from, to) =>
{
if (to.FullPath == "/home") return true;
if (!_isLoggedIn)
{
_statusChip.text = "Login required";
return false;
}
return true;
});
Возвращаем false — переход отменяется. Аналогично можно проверить готовность игровой сессии перед входом в матч. Проверку для отдельного маршрута задаёт ISusRouteGuard; например, она может запретить уход с формы, где остались несохранённые изменения.
Для одних экранов удобнее каждый раз создавать новый экземпляр. Для других хочется сохранить введённый текст или выбранную вкладку. За второй вариант отвечает KeepAlive:
_router.Register("/counter", typeof(CounterScreen),
new SusRouteConfig { KeepAlive = true });
_router.Register("/form", typeof(FormScreen),
new SusRouteConfig { KeepAlive = true });
_router.Register("/settings", typeof(SettingsScreen));
При уходе с /counter или /form роутер убирает экран из визуального дерева, но сохраняет его экземпляр в кэше. При возврате используется тот же экран с прежним состоянием. /settings в этом примере создаётся заново.
Модальные окна открываются поверх текущего экрана через OverlayHost. Для диалога подтверждения не нужно прятать основной экран и затем восстанавливать его вручную.

Модальное окно поверх экрана
Пример модального окна из пакета роутера.
Примеры роутера используют обычные Button, Label и TextField из UI Toolkit. Их можно разобрать отдельно, а затем подключить свои компоненты или Kit.
В примере запуска уже был вызов UseTheme(SusTheme.Dark). SusApp подготавливает окружение интерфейса, подключает тему и создаёт слои для экранов и оверлеев. Для нового проекта есть мастер Setup Project в меню Window → SUS: он помогает создать конфигурацию и начальные файлы.

Токены тёмной темы
Пример цветов и других параметров тёмной темы.
Поверх ядра можно писать собственные компоненты. Если нужны готовые элементы, есть Kit: формы, таблицы, меню, диалоги. Я предлагаю их и как основу для своего интерфейса — своего рода скелеты компонентов с уже заданными стилями. Можно взять подходящий, изменить разметку и оформление, добавить нужные элементы и логику. Например, дополнить диалог своим блоком или перестроить форму под конкретный экран, сохранив ту часть, которая уже подходит.
Сейчас я также готовлю к публикации игровой пакет Game. В нём уже созданы более сложные составные компоненты: HUD, инвентарь, магазин и другие игровые интерфейсы. Их внешний вид и поведение можно посмотреть в каталоге на сайте. Идея та же: дать готовую основу, которую можно переделать и дополнить под свою игру.
Набор Sharq UI System Complete Bundle, в который входят Core, Kit и игровой пакет, готовится к повторной подаче в Asset Store. Ссылки на опубликованный Complete пока нет; доступные сейчас Core и Kit указаны в начале статьи.
Что видят инструменты отладкиЕщё одна полезная для нас возможность — получить состояние интерфейса в виде данных. В редакторе и development-сборках доступен SusUiProbe: он возвращает дерево элементов, значения свойств компонента и диагностику в JSON.
using Sharq.Core.Diagnostics;
var root = susApp.ScreenHost;
string tree = SusUiProbe.GetTreeJson(root);
string health = SusUiProbe.GetHealthJson(root);
string props = SusUiProbe.GetPropsJson(root, "HomeScreen");
В редакторе эти данные доступны и через MCP-инструменты. Так агент может прочитать структуру открытого экрана и проверить свойства элементов. Скриншот всё равно нужен, чтобы оценить внешний вид, но для вопроса «что сейчас находится в дереве?» уже есть более точный ответ.
Про устройство этой проверки и работу с агентами стоит рассказать отдельно: здесь я хотел показать сам интерфейс и код, которым он описывается.
ОграниченияТекущая версия рассчитана на Unity 6000.3 и новее.
SUS работает поверх UI Toolkit. Компоненты для uGUI/Canvas придётся писать отдельно.
.sharq компилируется в редакторе. Загрузить новый такой файл в готовую игру и исполнить его нельзя.
Возможности зависят от пакета. Например, мост к Unity Localization есть в Kit, а поддержка геймпада пока охватывает только часть компонентов. Перед выбором для проекта стоит проверить нужные элементы и сценарии.
Роутер управляет интерфейсом. Загрузка игровых сцен, данных и ресурсов остаётся задачей приложения.
Для первого знакомства достаточно бесплатного Core: он включает и роутер. Установите набор, откройте примеры и попробуйте собрать один небольшой экран своего проекта.
GitHub: sus-core · sus-router.
Первая статья — про формат .sharq и устройство генератора.
Буду рад вопросам и примерам задач, на которых вы попробуете SUS. Особенно интересно, где привычный подход из веб-разработки помогает в Unity, а где приходится делать иначе.
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | [Перевод] Понятие о конечных автоматах: руководство разработчика по предсказуемой логике приложений | 0 | 9.68 | 29-05-2026 |
| 2 | Недельный геймдев: #297 — 18 октября, 2026 | 0 | 13.29 | 19-10-2026 |
| 3 | USB без магии: устройство протокола | 0 | 7 | 23-06-2026 |
| 4 | Теплые и влажные Плитки для документов на C#: не проводник | 0 | 9.76 | 30-06-2026 |
| 5 | Ошибки в UX у Хабр | 0 | 7.84 | 17-07-2026 |
| 6 | Техники рендеринга воксельного мира на мобильном устройстве в Unity | 0 | 7.44 | 29-07-2026 |
| 7 | Книга: «Head First. Изучаем C# и .NET. 5-е изд.» | 0 | 10.4 | 20-05-2026 |
| 8 | Проектирование меню в стесненных условиях | 0 | 9.07 | 26-07-2026 |
| 9 | MCP как интерфейс продукта: почему я думаю, что GUI может стать не главным | 0 | 8.55 | 24-07-2026 |