Подробные изменения: - Добавлены сцены Bootstrap, MainMenu и Game с текущей вертикальной 9:16 структурой проекта. - Добавлены runtime-скрипты gameplay-архитектуры: DI через Extenject, спавнер платформ, прогрессия этапов, one-way платформы, игрок, камера и оборудование JumpPad. - Добавлены ScriptableObject-этапы сложности, weighted-настройки платформ, оборудования, противников и collectibles. - Добавлены UI Toolkit ресурсы главного меню, preview-материалы, шрифты, спрайты, анимации и игровые prefab-ассеты. - Перенесена и оформлена новая COM-система для работы с аппаратными портами и StreamingAssets. - Обновлены проектные гайды, AGENTS.md, правила Git/Git LFS, .gitignore, Unity Packages и ProjectSettings. - Удалён локальный skill start-required-mcp-servers из репозитория.
16 KiB
Собственные надстройки и абстракции над Unity-классами
Зачем это нужно
Unity даёт много удобных API, но если использовать их напрямую везде, проект быстро получает:
- жёсткую привязку к
MonoBehaviour - сложный unit testing
- размытые границы между логикой и инфраструктурой
- трудности с заменой реализации
Поэтому для прикладного проекта полезно строить собственные надстройки над Unity API:
- обёртки
- фасады
- адаптеры
- базовые интерфейсы
- прикладные сервисы
Для Danro Jump это особенно важно из-за сочетания:
- gameplay-систем
- UI
- сцен
COM-оборудования- призовой логики
- сервисного режима
Legacy как источник абстракций
Assets/Temp/Legacy содержит много старых MonoBehaviour, managers, UI-контроллеров и editor extensions. Их стоит рассматривать как материал для выделения новых абстракций, а не как готовый код для прямого переноса.
Особенно полезные источники:
Assets/Temp/Legacy/Scripts/Controls/Inputs/ComSystem— аппаратный протокол, handshake, роли устройств, витринаAssets/Temp/Legacy/Scripts/Managers/PaymentManager.cs— кредиты, free play, старт игрыAssets/Temp/Legacy/Scripts/Managers/PrizeSystemManager.cs— prize orchestrationAssets/Temp/Legacy/Scripts/Settings— сервисные настройкиAssets/Temp/Legacy/Scripts/UI/Modal— модальные окна призов и настроекAssets/Temp/Legacy/Scripts/Editor— editor tooling для настроек и иерархииAssets/Temp/Legacy/Scripts/Utilities— логирование,QR, телефонные форматы
Перед переносом старого класса нужно ответить на вопрос: какую стабильную обязанность он выполняет в новом проекте?
Что считать надстройкой
Под надстройкой над Unity-классами в этом гайде понимаются не только editor extensions, но и прикладные архитектурные слои над Unity API.
Чаще всего это:
- интерфейс над Unity API
- сервис над статическим классом
- базовый runtime-компонент
- конфигурационный слой на
ScriptableObject - editor tooling для удобства настройки
Когда надстройка действительно нужна
Надстройка оправдана, если:
- Unity API мешает тестировать код
- доступ к системе идёт из слишком многих мест
- нужно заменить поведение в будущем
- есть разница между доменной логикой и конкретной Unity-реализацией
Надстройка не нужна, если:
- это одноразовый локальный вызов
- абстракция ничего не упрощает
- она только дублирует Unity API без пользы
Хорошие кандидаты на абстракции в Unity
SceneManager
Вместо прямого доступа:
SceneManager.LoadSceneAsync("Gameplay");
лучше:
public interface ISceneFlowService
{
UniTask OpenGameplayAsync();
}
Time
Вместо прямого Time.deltaTime везде можно иметь:
public interface ITimeProvider
{
float DeltaTime { get; }
float UnscaledDeltaTime { get; }
}
Это упрощает тестирование и управление скоростью.
Random
Вместо прямого UnityEngine.Random:
public interface IRandomService
{
int Range(int minInclusive, int maxExclusive);
float Value();
}
PlayerPrefs
Для project-scale логики лучше не разбрасывать вызовы PlayerPrefs напрямую.
Лучше:
public interface ILocalSettingsStorage
{
void SetInt(string key, int value);
int GetInt(string key, int defaultValue = 0);
}
COM-порт и внешние устройства
Это один из самых сильных кандидатов на абстракцию в Danro Jump.
Не стоит смешивать:
- порт
- протокол
- игровую логику
- сервисную диагностику
Лучше разделять:
IComPortGatewayIDeviceProtocolIPrizeShowcaseGatewayIHardwareDiagnosticsService
Legacy reference:
ComNativeWrapperпоказывает границу с nativeComNativeComHubиComDeviceClientпоказывают модель нескольких устройствPrizeStandControllerпоказывает прикладной протокол витриныComInputToInputSystemAdapterпоказывает, как аппаратный ввод можно отдавать в Unity Input System
Базовые Unity-типы, над которыми чаще всего строят свои слои
MonoBehaviour
MonoBehaviour хорош как Unity lifecycle entry point, но плох как контейнер всей бизнес-логики.
Практика:
MonoBehaviourлучше использовать как view, adapter или composition point- доменную и прикладную логику лучше выносить в обычные C# классы
Хорошая схема:
public sealed class PrizeScreenView : MonoBehaviour
{
[SerializeField] private Button _claimButton;
public event Action ClaimClicked;
private void Awake()
{
_claimButton.onClick.AddListener(() => ClaimClicked?.Invoke());
}
}
А логика живёт отдельно:
public sealed class PrizeScreenPresenter
{
private readonly PrizeScreenView _view;
private readonly IPrizeService _prizeService;
}
ScriptableObject
ScriptableObject хорошо подходит для:
- конфигурации
- таблиц баланса
- статических наборов данных
- editor-friendly настроек
Для Danro Jump через ScriptableObject удобно хранить:
- стоимость игры
- настройки сложности
- параметры призов
- таблицы выдачи
- конфигурацию аппаратных режимов
Практика:
ScriptableObjectхранит данные- поведение и orchestration живут в сервисах
Legacy reference:
Assets/Temp/Legacy/Scripts/Settings/MainSettingsDatabase.assetAssets/Temp/Legacy/Scripts/Settings/Core/SettingsDatabase.csAssets/Temp/Legacy/Scripts/ScriptableObjects/MainSettings.asset
При переносе этих материалов важно не тащить старую базу настроек как единственный источник истины без ревизии. Нужно выделить новые группы настроек: оплата, сложность, призы, QR, витрина, диагностика.
EditorWindow / CustomEditor / PropertyDrawer
Если под "надстройками" понимать editor-расширения, то это тоже важный слой.
Они полезны, когда нужно:
- удобнее настраивать сервисные параметры
- валидировать конфигурацию
- упростить работу дизайнеру или оператору
- сделать собственные окна диагностики
Типичные инструменты:
CustomEditorPropertyDrawerEditorWindowMenuItem
Legacy reference:
Assets/Temp/Legacy/Scripts/Editor/SettingsDatabaseEditor.csAssets/Temp/Legacy/Scripts/Editor/SettingEntryDrawer.csAssets/Temp/Legacy/Scripts/Editor/HierarchyBulkRenamerWindow.csAssets/Temp/Legacy/Scripts/Editor/MouseWorldPositionOverlay.cs
Эти инструменты можно использовать как идеи для нового tooling, но лучше переподключать их к новой модели настроек и новым namespace.
Полезные паттерны над Unity API
Adapter
Используется, когда нужно скрыть конкретный Unity API за интерфейсом.
Пример:
SceneManager->ISceneFlowServicePlayerPrefs->ILocalSettingsStorageAudioSource->IAudioPlaybackService
Facade
Используется, когда несколько Unity-подсистем должны выглядеть как одна прикладная операция.
Пример:
- показать экран награды
- проиграть анимацию
- активировать витрину
- ожидать подтверждение выдачи
Вместо разброса логики по сцене можно иметь:
public interface IRewardPresentationFacade
{
UniTask PresentRewardAsync(RewardResult rewardResult);
}
Wrapper around static API
Подходит для:
TimeRandomPlayerPrefsSceneManagerApplication
Presenter / Controller layer
Подходит для UI:
- view знает только про кнопки и поля
- presenter знает про бизнес-реакции
Legacy UI-классы из Assets/Temp/Legacy/Scripts/UI чаще всего нужно переносить именно через этот паттерн:
- старый
MonoBehaviourстановитсяView - бизнес-решения уходят в
Presenterили application service - прямые обращения к singleton-manager'ам заменяются интерфейсами
- переходы сцен уходят в
ISceneFlowService
Например, QRCodeModal и PrizeStandModal стоит рассматривать как reference для будущих QrRewardView и PrizeShowcaseView, а не как финальные controllers.
Что лучше не делать
Плохо:
- наследоваться от
MonoBehaviourдля всего подряд - хранить бизнес-логику прямо в
Update - вызывать
SceneManager,PlayerPrefs,Random,Timeнапрямую в доменных классах - делать толстые
Manager-классы, которые знают про всё
Хорошо:
- вводить точечные интерфейсы
- отделять runtime view от логики
- держать конфигурацию отдельно от поведения
- строить прикладные сервисы поверх Unity API
Практические абстракции, которые особенно полезны для Danro Jump
1. Аппаратный слой
public interface IPrizeShowcaseGateway
{
UniTask OpenAsync(int prizeSlotId);
}
Legacy mapping:
PrizeStandController->IPrizeShowcaseGatewayComSystemManager->IHardwareSessionServiceComNativeWrapper->IComNativePortApi
2. QR-выдача
public interface IQrRewardService
{
UniTask<QrRewardResult> CreateRewardCodeAsync(RewardRequest request);
}
Legacy mapping:
QRCodeUtils->IQrTextureFactoryQRCodeInfo-> новаяQrRewardConfigQRCodePrize->QrPrizeExecutor
3. Баланс игры
public interface IGameBalanceProvider
{
int GetSessionPrice();
float GetDifficultyMultiplier();
}
4. Сервисные настройки
public interface IServiceSettingsService
{
void SetSessionPrice(int value);
void SetDifficulty(float value);
}
Legacy mapping:
SettingsManager->IServiceSettingsServiceSettingsDatabase->ISettingsDefinitionRepositorySettingsUIGenerator-> новый service mode presenter/view generator
5. Время и рандом
public interface ITimeProvider
{
float DeltaTime { get; }
}
public interface IRandomService
{
int Range(int minInclusive, int maxExclusive);
}
Когда делать editor-надстройки
Editor-расширения стоит делать, когда:
- ручная настройка через стандартный inspector становится слишком тяжёлой
- нужно валидировать таблицы призов
- нужно дать персоналу или дизайнеру безопасный интерфейс настройки
- нужно сделать диагностику подключённого оборудования
Хорошие кандидаты для editor tooling в этом проекте:
- окно сервисной конфигурации
- custom inspector для таблиц призов
- property drawer для настроек весов и диапазонов
- diagnostic window для
COM-связи
Когда остановиться и не переусложнять
Надстройки полезны, пока они:
- уменьшают связанность
- улучшают тестируемость
- делают архитектуру понятнее
Если абстракция:
- не скрывает ничего важного
- не даёт тестовой выгоды
- не помогает сопровождению
то она, скорее всего, лишняя.
Рекомендуемый путь внедрения
- Сначала выбрать legacy-подсистему из
Assets/Temp/Legacy. - Выписать её реальные обязанности.
- Вынести эти обязанности за новые интерфейсы.
- Перенести только нужные модели данных.
- Подключить новую реализацию через
Extenject. - После этого при необходимости делать editor tooling.
Для Danro Jump разумный старт:
SceneManagerPlayerPrefsTimeиRandom- аппаратные шлюзы
- editor tooling для сервисных настроек
Для миграции legacy-кода разумный старт:
COMи витрина- призовой контур
- сервисные настройки
QR-выдача- UI-модалки и admin screens
Официальные и полезные источники
- https://docs.unity3d.com/6000.1/Documentation/Manual/class-ScriptableObject.html
- https://docs.unity3d.com/ja/2023.2/ScriptReference/ScriptableObject.html
- https://docs.unity3d.com/es/current/ScriptReference/CustomEditor.html
- https://docs.unity3d.com/ja/current/ScriptReference/PropertyDrawer.html
- https://docs.unity3d.com/es/current/ScriptReference/EditorWindow.html
- https://docs.unity3d.com/cn/2022.3/ScriptReference/MenuItem.html