Система UI в SS14 прошла через несколько итераций, и многие части кодовой базы устарели по сравнению с текущими соглашениями по UI. При использовании существующих UI в качестве справки, пожалуйста, учитывайте возраст кода.Если вы найдёте код, который не соответствует текущим соглашениям, рефакторинг всегда приветствуется!
Хорошо, но как сделать его красивым?
FancyWindow
DefaultWindow не рекомендуется. Если вы не создаёте собственное окно, в любых обстоятельствах следует использовать FancyWindow. У него есть дополнительные свойства, которые интегрируются с SS14 лучше, чем DefaultWindow.
Свойство Stylesheet позволяет окну получать информацию о стилях из заданного stylesheet. Мы поговорим о stylesheets подробнее позже, но то, какой stylesheet использует данный UI, определяет, какой набор правил стиля применяется к нему. В настоящее время существуют следующие stylesheets:
Nanotrasen— stylesheet по умолчанию. Используется для любых стандартных UI, предназначенных для игроков.System— В основном используется для UI администраторов и sandbox (в настоящее время не реализован).
StyleClass
Классы стилей позволяют правилам стиля применять оформление к элементу. Вы можете назначить элементу классы стилей, установив свойство StyleClasses в XAML.
Content.Client/Stylesheets/StyleClass.cs — это статический класс для определения строк классов стилей, которые могут применяться к любому элементу UI. Это нужно для централизации расположения всех доступных классов стилей, для лёгкости доступа и предотвращения дублирования классов стилей.
Любые классы стилей, которые являются общими / могут использоваться для более чем одного элемента, определяются вверху. Например, класс стиля positive влияет на Button, Panel и Label.
Остальные классы стилей определяются для конкретных общих элементов UI. Некоторые распространённые классы стилей:
OpenLeft: Делает кнопку плоской с левой стороны.OpenRight: Делает кнопку плоской с правой стороны.OpenBoth: Делает кнопку плоской с обеих сторон; квадратной.LabelSubtext: Делает Label меньше и более приглушённого цвета.LabelKeyText: Делает Label жирным и цвета выделения.LabelWeak: Weak — противоположность strong; делает Label более приглушённого цвета.
Написание стилей
Этот раздел касается правил стиля. Для большинства UI их редактирование будет излишним, однако вы ВСЕГДА должны предпочитать использовать классы стилей вместо хардкодинга цветов или ресурсов, которые могут быть использованы повторно.Да здравствует могучий Sheetlet
Важно понимать, что stylesheet — это массивный список всех правил стиля. Вместо того чтобы составлять один гигантский список правил стиля (потому что это было бы нелепо… ха-ха… ха…), обязанность вносить вклад в этот список распределена между множеством Sheetlet. Каждый Sheetlet возвращает небольшой фрагмент правил стиля, который агломерируется в итоговый список в конце.
Раньше все правила стиля были в одном гигантском списке:
StyleNano.cs, 1600-строчная яма отчаяния, где мечты умирали. Он был настолько огромным, что ломал подсветку синтаксиса в IDE. НЕ допускайте повторения чего-либо подобного.- Общие Sheetlets (Generic Sheetlets): Они находятся в
Content.Client/Stylesheets/Sheetlets. Эти sheetlets касаются общих элементов UI, используемых во многих разных UI, и должны быть написаны обобщённо, чтобы работать с любым stylesheet. - Специфические Sheetlets (Specific Sheetlets): Они находятся вместе с файлами
*.xaml, с которыми они связаны. Эти sheetlets касаются элементов UI, специфичных для одного UI, и должны быть написаны для работы с конкретными sheetlets, с которыми они связаны.
[CommonSheetlet].
Правила стиля
Правила стиля применяют оформление к XML-элементам, не так уж отличаясь от CSS. Они состоят из селектора, который указывает, на какие элементы влияет это правило стиля, и набора свойств, определяющих оформление для этих элементов. Сначала давайте посмотрим на селекторы, которые фильтруют элементы по нескольким различным признакам:Type: Тип элемента, на который влияет это правило. Всё, что наследуется от этого типа, также будет подвержено влиянию этого правила.StyleClasses: Классы, которые должен иметь элемент, чтобы на него повлияло это правило. Элемент должен иметь все классы, указанные правилом, чтобы на него повлияло это правило. Это задаётся в XML с помощью свойстваStyleClasses.StyleIdentifier: Идентификатор элемента. Это уникальный идентификатор, который можно использовать для нацеливания на конкретный элемент. Он должен использоваться, когда существует только один экземпляр элемента, который нужно стилизовать очень специфическим образом. Элемент может иметь только один идентификатор, который задаётся в XML с помощью свойстваStyleIdentifier.PseudoClasses: Это специальные классы, которые можно использовать для нацеливания на элементы в определённом состоянии. Например, это используется для стилизации кнопок по-разному при наведении или нажатии. Они срабатывают автоматически при взаимодействии пользователя.- Элементы также могут быть стилизованы на основе их родительского элемента и всех их свойств стиля. В определении правила стиля это делается с помощью метода
.ParentOf(...), который принимает другой селектор, описывающий дочерний элемент, к которому будут применены стили.
Content.Client/Stylesheets/StylesheetHelpers. Чтобы увидеть это в действии, давайте рассмотрим несколько примеров правил стиля:
Смерть Хардкодингу!
Когда это возможно, избегайте хардкодинга в определениях правил стиля, чтобы сохранить их как можно более переиспользуемыми/широкими. Следующие системы предназначены для помощи в централизации всех определений, и ваши правила стиля должны быть паттернами, которые применяют эти определения.ColorPalette
На самом деле существует довольно надёжная (ха-ха) система цветовых палитр, чтобы, надеюсь, сделать хардкодинг цветов ненужным. В классе Palettes определён набор общих палитр, и каждый stylesheet использует их для следующих общих палитр, на которые ссылаются sheetlets:
PrimaryPalette: Используется для элементов переднего плана.SecondaryPalette: Используется для фоновых элементов.PositivePalette: Традиционно зелёная палитра, используемая для обозначения успеха / хорошо / полно.NegativePalette: Традиционно красная палитра, используемая для обозначения ошибок / плохо / пусто.HighlightPalette: Используется для выделения заголовков или важных элементов.
ColorPalette. От самого яркого до самого тёмного, свойства (на момент написания) расположены следующим образом (где меньшие числа темнее):
+0:TextBase-1:TextDark,Element-2:BackgroundLight,PressedElement-3:Background-4:BackgroundDark,DisabledElement
NanotrasenStylesheet:

ISheetletConfig
ISheetletConfig предназначен для сокращения повторяющегося кода путём предоставления общей функциональности и определений между stylesheets. Любой Sheetlet, который требует значения из некоторого экземпляра ISheetletConfig, должен иметь ограничение обобщённого типа, требующее интерфейс ISheetletConfig.
ISheetletConfig также служит проверкой зависимостей. Когда stylesheets собирают все sheetlets, имеющие [CommonSheetlet], они сначала проверяют, удовлетворяют ли они ограничению типа, прежде чем добавлять правила в stylesheet.
Если sheetlet не удовлетворяет ограничениям типа какого-либо stylesheet, игра выведет ошибку в лог. Если ваши стили не отображаются, возможно, это причина.
Доступ к ресурсам
Ресурсы в sheetlets запрашиваются иначе, чем в других частях кодовой базы. Каждый stylesheet предоставляет список директорий (корней) для использования при запросе ресурса (например, кореньTextureResource в NanotrasenStylesheet — /Textures/Interface/Nano). Это означает, что любой текстура, запрошенная с помощью GetTexture, будет искаться относительно этой директории.
Общие Sheetlets
Общие sheetlets используются для общих элементов UI, которые используются во многих разных UI. Они сгруппированы вContent.Client/Stylesheets/Sheetlets. Вот некоторые соглашения, которым следует руководствоваться при написании общих sheetlets:
- Вы всегда должны выбирать элементы с помощью
.Class, а не.Identifier. - При доступе к ресурсам используйте метод
GetTextureOr, чтобы получить текстуру и указать запасной корень, который будет использован, если текстура не найдена в корнях stylesheet. - Избегайте ручного хардкодинга классов. При ссылке на классы вы должны использовать только классы, определённые на стилизуемом элементе (в свойствах
StyleClass*), или определять свои собственные вStyleClass.cs. - Если вам нужно получить доступ к ресурсу, который ещё не предоставлен, вы должны добавить путь к соответствующему
ISheetletConfigили создать новый.
Пример кода (нажмите, чтобы развернуть)
Пример кода (нажмите, чтобы развернуть)
Специфические Sheetlets
Специфические sheetlets используются совместно с элементами UI, которые используются лишь несколько раз, чаще всего все в одном UI. Эти sheetlets находятся в той же директории, что и файл*.xaml, с которым они связаны.
В целом, эти sheetlets следуют немного другим соглашениям по сравнению с общими sheetlets:
- Вы должны предпочитать выбирать элементы с помощью
.Identifier, а не.Class. - Любые стили, которые МОГУТ быть использованы другим UI, должны быть перемещены в общий sheetlet.
- Хардкодинг допускается более свободно; вы всё равно должны стараться избегать его, когда это возможно, но хардкодинг
StyleIdentifier-ов, вероятно, нормален. - Если вам ДЕЙСТВИТЕЛЬНО нужен специфический ресурс, и нет смысла добавлять его в
ISheetletConfig, вы можете получить к нему доступ черезResCacheкак обычно. - Вам не нужно делать ограничение обобщённого типа, так как sheetlet должен быть специфичен для одного UI, а значит, и для одного stylesheet.
Пример кода (нажмите, чтобы развернуть)
Пример кода (нажмите, чтобы развернуть)
Создание собственного Stylesheet
Несколько stylesheets стали возможны только недавно с внедрением системы Sheetlet, поэтому полный спектр возможностей, которые это открывает, ещё не исследован. Если stylesheets будут использоваться интересными способами, помимо смены палитр, пожалуйста, обновите этот раздел!
SystemStylesheet.
Цвета для stylesheets определяются с использованием цветового пространства OKLAB, перцептивно равномерного цветового пространства. Когда вы выбираете новые цвета для своего stylesheet, может быть полезно использовать OKLCH Color Picker и изменить существующий цвет.
Написание C# для UI
TODO: Я недостаточно уверен в своих знаниях, чтобы подробно описать, что делать, а чего не делать. Это всего лишь общий обзор на данный момент, и его следует обновить. Также это, вероятно, должно быть отдельной страницей.Лучший способ научиться писать код UI — смотреть на существующий код. Некоторые UI, безусловно, делают ужасные вещи, которые никогда не стоит повторять, но в SS14 горы ужасного кода, так что это не является чем-то необычным. Я не могу научить знакомству с внутренностями этой игры, но я могу дать общий обзор. Код, на который вы можете ссылаться:
- Robotics Console
- Reagent Dispenser
- BatteryMenu
MyThing, для которой мы хотим показать UI. Вот, в общем, как будет выглядеть структура:
Bound User Interfaces
TODO: кто-то более знакомый с BUI, чем я, должен написать о том, как писать хорошие BUI. Я просто вставлю заметки из#codebase-changesот Bard в дискорде:Predicted BUIs are in:Для передачи данных по сети: Вариант 1 (Предпочтительный). Переместите состояние BUI в component states. Используйте существующую клиентскую систему / создайте новую для обработки обновления BUI при обновлении состояния (используйте TryGetOpenUi) и при вызове Open в BoundUserInterface. См. JukeboxSystem в качестве примера, напримерВариант 2. Сделайте элемент управления BUI заглушкой до получения состояния. Для UI: вызывайте TryOpenUi в shared, где это возможно, и клиент должен просто обработать это. Вызов с сервера также будет работать, как и раньше. Для сообщений: используйте SendPredictedMessage, где это возможно, в BUI. В какой-то момент это, вероятно, станет поведением по умолчанию вместо SendMessage. В целом: предпочитайте использовать перегрузки, принимающие EntityUid вместо ICommonSession — это упростит программирование NPC, которые смогут взаимодействовать с UI в будущем.Некоторые заметки на будущее:
- Существует вспомогательный метод
this.CreateWindow<TWindow>()для BUI, который обрабатывает удаление + открытие + подписку на закрытие за вас.- Существует метод OnProtoReload, который вызывается на BUI, так что вы можете переопределить его и обработать без необходимости вручную подписываться на другой системе.
- Я добавил поддержку перезагрузки прототипов для некоторых вещей.
- Я почистил много кода BUI. Windows теперь просто вызывают события, а сам BUI обрабатывает отправку сообщений.
- Вы должны создавать / удалять control entities внутри EnteredTree и ExitedTree, а не внутри Dispose.
- Элементы управления должны иметь возможность быть созданными с пустым конструктором и не должны вызывать методы BUI напрямую. Это значительно упрощает повторное использование.
- Все новые элементы управления должны обрабатывать перезагрузку прототипов, если это применимо.
- Все новые элементы управления должны предпочитать использование component states, а не BUI states, где это возможно. Они лучше работают с prediction и проще в использовании.
- Элементы управления должны уметь обрабатывать исчезновение компонентов и не полагаться на
GetComponent<T>везде, так как нет гарантии, что компонент существует.