Фреймворк для игр на Unity. Он задаёт форму приложения: точку входа, порядок инициализации, жизненный цикл компонентов, модель данных и способ, которым игра расширяет его поведение.
Что это значит на практике:
- игра запускается из bootstrap-сцены, а порядок инициализации задают
MethodHookAttributeи этапы, а не вашStart; - компоненты наследуют
PRMonoBehaviourи получаютPRUpdate, паузу, игровое время и готовность черезReadySignal; обычныйMonoBehaviourвсего этого не получает; - поведение расширяется подключением к ядру, а не правкой ядра: хуки урона, провайдеры с автопоиском по атрибутам, фабрики окон, трекеры сущностей;
- данные игры описаны его моделью: каталоги, определения предметов, награды, проекты.
Название историческое: строка PRUnitySDK зашита в пути Resources, пункты меню
и сериализованные ассеты, поэтому переименование требует отдельной миграции.
Warning
Проект находится в активной разработке. Публичные API, структура каталогов и процесс инициализации могут изменяться. Перед обновлением фиксируйте используемый commit.
| Система | Назначение |
|---|---|
PRUnitySDK |
Центральная точка доступа к настройкам, менеджерам, трекерам и сервисам |
Bootstrap |
Единая точка запуска и последовательная инициализация фреймворка |
PRMonoBehaviour |
Базовый Unity-компонент с PR lifecycle, поддержкой паузы и физическими callback'ами |
EventBus |
Глобальная типизированная шина событий с защитой от повторной подписки |
PauseManager |
Раздельное управление общей, логической, музыкальной и другими видами паузы |
PRTime / PRTimeScale |
Игровое и реальное время с учётом логической паузы и пользовательских time scale |
PRCoroutineBase |
Обёртки над Unity-корутинами с единым запуском и остановкой |
BackgroundTasks |
Работа по расписанию вне сцены и наблюдение за значениями, которые сами о себе не сообщают |
HookSystem |
Последовательная обработка изменяемых и отменяемых событий |
FlagsSystem |
Совместное управление состояниями объекта из нескольких источников |
Entity / Items / Wallet / Reward |
Базовые модели сущностей, предметов, ресурсов и наград |
GameRules |
Глобальные ограничения характеристик поверх персональных модификаторов |
Conditions |
Настраиваемые правила «когда это разрешено»: ассетом или прямо в инспекторе |
GameDataStorage |
Сохранение прогресса и состояния объектов сцены между запусками |
| State / Damage | Переиспользуемые игровые модули |
| Quality / Localization / Logging | Качество предметов, переводы и структурированное логирование |
Как части связаны между собой. Стрелка — «зависит от».
graph TD
Boot["Bootstrap<br/>единственная точка входа"]
Facade["PRUnitySDK<br/>facade, ServiceResolver"]
Hooks["HookSystem<br/>MethodHook и стадии"]
Boot --> Facade
Facade --> Hooks
subgraph BASE["Базис"]
Models["Models<br/>Enumeration, ProjectData"]
Utils["#Utils, #Extensions, Debug"]
Attrs["@Attributes"]
end
subgraph LIFE["Жизненный цикл и время"]
Mono["PRMonoBehaviour"]
Pause["PauseSystem"]
Time["PRTime, PRTimeScale"]
Coro["Coroutines, Yields"]
Bg["BackgroundTasks"]
end
subgraph DATA["Данные"]
Storage["GameDataStorage<br/>ProjectData, ObjectState"]
Db["#Database<br/>PRSDKDatabase, PRSDKSettings"]
Paths["ResourcePaths"]
end
subgraph WORLD["Модель мира"]
Entity["@Entity<br/>EntityBase, Metadata,<br/>Stats, Player"]
Items["Items"]
Wallet["Wallet"]
Reward["Reward"]
Rules["GameRules"]
Props["PropertyContainer"]
Flags["FlagsSystem"]
Cond["@Conditions<br/>ICondition, ConditionBase"]
end
subgraph RUNTIME["Рантайм-инфраструктура"]
Managers["@Managers<br/>PRManagerContainer"]
Trackers["Trackers<br/>Entities, Players, Cameras, Windows"]
Factories["Factories, ObjectPool"]
Bus["@Events<br/>EventBus"]
Bots["Bots"]
Input["Input<br/>InputTranslator, PlayerInputState"]
end
subgraph UIL["Интерфейс"]
Wnd["#UI<br/>MonoWindow, Notifiers,<br/>PRWindowsContainer"]
end
subgraph MODS["Модули"]
Damage["@DamageSystem"]
HitBox["HitBox"]
Cam["Camera"]
Tr["Translate"]
Tween["DOTweenEffects"]
end
Editor["Core/Editor<br/>окна Database и PRUnitySDKDebug"]
Mono --> Pause
Mono --> Time
Mono --> Bus
Bg --> Mono
Entity --> Models
Entity --> Mono
Entity --> Factories
Entity --> Rules
Entity --> Input
Items --> Models
Items --> Tr
Props --> Models
Flags --> Models
Rules --> Models
Reward --> Items
Wallet --> Models
Cond --> Items
Cond --> Wallet
Managers --> Storage
Managers --> Items
Trackers --> Entity
Trackers --> Wnd
Bus --> Items
Bots --> Bus
Wnd --> Mono
Wnd --> Pause
Cam --> Trackers
Damage --> Entity
Damage --> Hooks
Damage --> Items
HitBox --> Damage
Tween --> Time
Editor --> Entity
Editor --> Db
Editor --> Wnd
Editor --> Bg
Игра к фреймворку не подмешивается: зависимость всегда направлена от игры к нему. Как именно к нему подключаться — в разделе «Какой механизм расширения выбрать».
| Папка | Что в ней |
|---|---|
Assets/PRUnitySDK |
сам фреймворк: общее для любой игры |
Assets/PRUnitySDKPrivate |
проектный слой: механики конкретной игры, надстроенные над фреймворком |
Assets/PRUnityData |
данные игры: каталоги, настройки, проекты |
Данные вынесены из фреймворка намеренно: его обновление не должно задевать содержимое
игры, а один и тот же фреймворк обслуживает несколько игр. Какая из них собирается
сейчас, указывает PRSDKProject — окно PRUnitySDK/Windows/Project. Подробности —
в PRUnityData/README.md.
Опыт и уровни теперь тоже в проектном слое: XPManager, фоновое начисление и бустеры
опыта живут в PRUnitySDKPrivate/Modules/@ProgressionModule.
- Unity 2022.3 LTS или новее;
- Newtonsoft Json for Unity;
- DOTween для tween-расширений и модуля
DOTweenEffects.
Некоторые интеграции имеют дополнительные зависимости:
YG2.Integrationтребует YG2 Plugin и соответствующие модули YG2.
Репозиторий пока не оформлен как UPM-пакет: в корне отсутствует package.json.
Размещайте содержимое репозитория внутри Assets/PRUnitySDK.
Через Git submodule:
git submodule add https://github.com/prethink/PRUnitySDK.git Assets/PRUnitySDK
git submodule update --init --recursiveЛибо скачайте репозиторий и скопируйте его содержимое в:
Assets/PRUnitySDK
После импорта убедитесь, что Unity завершил компиляцию без ошибок и установлены перечисленные выше зависимости.
- Создайте отдельную сцену и добавьте её первой в
File → Build Settings. - Поместите в неё GameObject с компонентом
Bootstrap. - Добавьте основную игровую сцену следующей, с build index
1.
В текущей реализации после завершения инициализации Bootstrap вызывает переход на
сцену с индексом 1. В Unity Editor класс PlayFromBootstrap автоматически назначает
сцену с индексом 0 стартовой при входе в Play Mode.
Important
Если проект использует другую схему загрузки сцен, измените обработчик
Bootstrap.OnInitialized() или переопределите bootstrap-процесс через
OverrideBootstrapAttribute.
using UnityEngine;
public class RotatingObject : PRMonoBehaviour
{
[SerializeField] private float speed = 90f;
protected override void PRUpdate()
{
transform.Rotate(Vector3.up, speed * PRTime.Instance.GameDeltaTime);
}
}PRUpdate, PRLateUpdate и PRFixedUpdate не выполняются во время логической паузы.
Для временных расчётов используйте PRTime, если система должна следовать правилам
паузы фреймворка.
Физические callback'и объявлены в базовом классе, поэтому Unity вызывает их у любого наследника. Ненужные отключаются на уровне типа:
[DisableMethods("OnTriggerStay", "OnCollisionStay")]
public class Pickup : PRMonoBehaviour { }Атрибут действует только на физические callback'и и OnPauseStateChanged, имена
задаются строками, а у наследника список заменяется целиком, а не дополняется —
подробности в PRMonoBehaviour.
Там же чек-лист «метод не вызывается — что проверить».
Событие описывается интерфейсом:
public interface ICoinsChanged : IGlobalSubscriber
{
void OnCoinsChanged(int value);
}Подписчик реализует интерфейс и регистрируется в EventBus:
public class CoinsView : MonoBehaviour, ICoinsChanged
{
private void OnEnable() => EventBus.Subscribe(this);
private void OnDisable() => EventBus.Unsubscribe(this);
public void OnCoinsChanged(int value)
{
Debug.Log($"Coins: {value}");
}
}Вызов события:
EventBus.RaiseEvent<ICoinsChanged>(listener => listener.OnCoinsChanged(10));Компоненты, наследуемые от PRMonoBehaviour, автоматически регистрируются в
EventBus при стандартной инициализации и снимаются с регистрации при уничтожении.
PRUnitySDK.PauseManager.SetLogicPaused(true, this);
PRUnitySDK.PauseManager.SetLogicPaused(false, this);Состояние доступно через:
bool isPaused = PRUnitySDK.PauseManager.IsLogicPaused;var delay = new WaitGameSecondsCoroutine(
callback: () => Debug.Log("Completed"),
duration: 2f,
instance: this);
delay.Execute();Если MonoBehaviour не передан, корутина запускается на глобальном
PRMonoBehaviourHost. Для повторного запуска одного объекта корутины используйте
StopAndExecute(), а для остановки — Stop().
Основная последовательность запуска:
Bootstrap.Awake()вызываетPRUnitySDK.InitializeSDK().- Инициализируются правила, конвертеры, singleton-сервисы и фабрики.
- Через method hooks подключаются дополнительные модули.
- Инициализируются контейнеры менеджеров и окон.
- Устанавливается
PRUnitySDK.IsInitialized. - Публикуется
ISDKEvents.OnInitialized()и устанавливаетсяReadySignal.
Повторный параллельный запуск блокируется флагом PRUnitySDK.IsStartInitialize.
Готовность можно проверить через PRUnitySDK.IsInitialized или
PRUnitySDK.ReadySignal.
Во фреймворке шесть способов вклиниться в чужое поведение. Они не взаимозаменяемы: каждый решает свою задачу, и выбор определяется одним вопросом — что вам нужно сделать с чужим кодом.
| Нужно | Механизм | Где описан |
|---|---|---|
| Выполнить свой код в определённый момент | MethodHookAttribute |
Attributes |
| Узнать, что что-то уже произошло | EventBus |
EventBus |
| Изменить или запретить действие до того, как оно случилось | HookSystem |
HookSystem |
| Согласовать решение, на которое влияют несколько компонентов | FlagsSystem |
FlagsSystem |
| Собрать данные из всех модулей в один список | InvokePartialAttribute |
Attributes |
| Подменить стандартную реализацию сервиса | OverridePropertyAttribute |
Attributes |
| Выполнять работу по расписанию вне сцены | BackgroundTask |
BackgroundTasks |
| Узнать об изменении значения, которое само о себе не сообщает | WatcherTask<T> |
BackgroundTasks |
MethodHook — «выполни это на такой-то стадии». Инициализация модуля, регистрация
фабрик, клонирование данных. Порядок задаётся числом, результат не собирается.
[MethodHook(MethodHookStage.SDK, order: 20)]
private static void InitializeInventory() { }EventBus — «сообщи, что уже случилось». Отправитель не ждёт ответа и не знает
подписчиков. Обновить UI, проиграть звук, записать метрику.
public class CoinsView : MonoBehaviour, IResourceValueChangedEvent { }HookSystem — «дай вмешаться до того, как случится». В отличие от EventBus,
обработчик получает изменяемый контекст: может уменьшить урон, заменить награду или
отменить действие. Обработчики идут цепочкой в порядке Order.
public class DamageResistanceComponent : PRMonoBehaviour, IHookListener<DamageHookEvent> { }FlagsSystem — «можно ли сейчас?». Когда на один вопрос («можно ли прыгать»)
влияют несколько независимых источников — оглушение, катсцена, туториал. Каждый
добавляет своё влияние, система сводит их к ответу.
flagResolver.Add(PlayerFlags.CanJump, this, false);InvokePartial — «соберите со всех». Каждый модуль возвращает свой кусок, вызывающий
получает объединённый результат.
IEnumerable<Modifier> modifiers = this.CollectPartialResult<Modifier>(context);OverrideProperty — «замени реализацию». Интеграция подставляет свой сервис вместо
стандартного до применения fallback: серверное время платформы вместо локального,
облачное хранилище вместо PlayerPrefs.
[OverrideProperty(typeof(IServerTime), order: -100)]
private static void UsePlatformServerTime() => ServerTime = new PlatformServerTime();BackgroundTask — «делай это раз в N секунд». Работа, у которой нет владельца на
сцене и которая должна пережить её смену: автосохранение, офлайн-доход, отложенная
аналитика. Задача сама переживает паузу так, как ей нужно, и умеет пропускать запуск,
пока условия не готовы.
[AutoBackgroundTask]
public class PlaytimeTrackerTask : BackgroundTask
{
public override Enumeration Key => BackgroundTaskKeyEnumerationProvider.PlaytimeTracker;
public override float RepeatSeconds => 60f;
protected override void OnExecute() { }
}WatcherTask<T> — «сообщи, когда значение изменится». Отличие от EventBus в том,
что источник ничего не отправляет: смену суток, состояние сети или изменённый на сервере
баланс можно узнать только опросом. Наблюдатель опрашивает по расписанию и поднимает
событие только в момент изменения.
EventBusтам, где нуженHookSystem. Если обработчику надо повлиять на результат, событие не подойдёт: оно уведомляет уже после того, как решение принято.HookSystemтам, где хватило быEventBus. Перехват дороже и делает поток неочевидным: обработчик может незаметно отменить действие.- Свой флаг вместо
FlagsSystem. Булево поле «нельзя прыгать» на компоненте ломается, когда источников запрета становится двое: кто снял — тот и разрешил, хотя второй ещё против. MethodHookдля реакции на игровое событие. Хуки стадий вызываются там, где код явно их запускает; для игровых событий естьEventBus.partial voidкак точка расширения. У partial-метода ровно одна реализация на класс: вторая часть, которой понадобится та же точка, получит ошибку компиляцииCS0757. Если участников может быть несколько — этоMethodHookилиInvokePartial.- Своя корутина вместо
BackgroundTask. Для периодической работы без владельца на сцене корутина требует объекта-хозяина, теряется при смене сцены и не даёт ни счётчиков, ни защиты от череды ошибок. - Опрос в
UpdateвместоWatcherTask<T>. Проверять раз в кадр то, что меняется раз в минуту, — лишняя работа; наблюдатель делает это по расписанию и сообщает только об изменении.
Отдельно стоят механизмы, которые не перехватывают чужое поведение, а дополняют данные. Им не нужен ни один из шести способов выше:
| Что добавить | Как |
|---|---|
| Поле в сохранение | partial class ProjectData + [MethodHook(Cloning)] |
| Новое окно | partial class MonoWindowKeyEnumerationProvider + partial class PRWindowsContainer |
| Фоновая задача | наследник BackgroundTask + [AutoBackgroundTask] + ключ partial-частью BackgroundTaskKeyEnumerationProvider |
| Путь к ресурсам модуля | partial class ResourcePaths |
| Новый ключ, тип, слой | наследник EnumerationProviderBase или partial-часть существующего |
Правило простое: общие файлы фреймворка править не нужно — почти всё расширяется partial-частью
рядом с модулем.
PRUnitySDK/
├── Core/ # Ядро, сервисы, базовые модели и инструменты
├── Modules/ # Опциональные игровые модули
├── Examples/ # Примеры использования
├── Resources/ # Runtime-ресурсы фреймворка (данные игры — в PRUnityData)
├── Utils/ # Дополнительные утилиты
└── YG2.Integration/ # Интеграция с YG2
- SDK — facade, инициализация и service resolver
- ResourcePaths — канонические пути к runtime-ресурсам и правила расширения
- Окна Database и Settings — секции с описанием и сбросом, поиск, заполнение каталогов, валидация definitions и наборы состава базы для разных игр
- Attributes — method hooks, переопределение сервисов и расширение Inspector
- Actions — переиспользуемые действия с единым контрактом проверки и выполнения
- Conditions — переиспользуемые правила «когда это разрешено»; рядом с действиями, но отдельно от них: действие совершает поступок, условие только спрашивает. Оттуда же берут подпись для игрока: чего не хватает, чтобы условие выполнилось
- EventBus — типизированная шина уведомлений о произошедшем
- HookSystem — перехват действий с возможностью изменить или запретить их
- FlagsSystem — согласование независимых решений компонентов без прямых зависимостей
- PRMonoBehaviour — базовый компонент с lifecycle-хуками, учитывающими логическую паузу
- Coroutines — обёртки над корутинами с запуском, перезапуском и остановкой
- Yields —
CustomYieldInstruction, останавливающие корутины на логической паузе - PauseSystem — раздельные причины паузы и мониторы аниматоров и физических тел
- PRTime — источник времени: реальное, игровое и учёт паузы
- PRTimeScale — слои скорости времени, модификаторы с владельцами и драйверы для анимации и физики
- BackgroundTasks — фоновые задачи по расписанию и наблюдение за значениями
- Обзор менеджеров — доступ, жизненный цикл, порядок и расширение контейнера
- GameManager — загрузка и сохранение
ProjectData/GameSettings, autosave и готовность данных - ProjectPropertiesManager — свойства
long,float,DateTime,stringиbool - ResourceManager — игровые ресурсы, баланс, списание и события изменений
- OpenedItemsManager — открытые предметы и количество в
ProjectData - SelectedItemsManager — что из имеющегося надето у каждого локального игрока
- ReservedItemsManager — предметы, которые выдаются не покупкой: награды, подарки, кейсы
- PRManagerContainer — hook-порядок создания runtime-менеджеров
- SoundManager — музыка, UI-звуки, позиционные эффекты и наборы
AudioSet - CursorManager — запросы Show/Hide курсора с приоритетом последнего обращения
- Entity — сущности игрового мира: идентификаторы, описание, реестр, время жизни и пул
- EntityStats — характеристики сущности: базовые значения, персональные модификаторы и расчёт итоговых
- Ввод — состояние ввода игрока или бота и его маршрутизация по
InputGuid - Фабрики MonoBehaviour — обычные prefab, singleton-компоненты, MonoWindow и Notifier
- Trackers — игроки, сущности, камеры и UI-реестры
- MonoWindow — модальные runtime-окна, фабрики и параметры открытия
- Reward — модели наград, экземплярный сервис выдачи и проектные обработчики
- Wallet — баланс, начисление и списание валюты поверх
ResourceManager - Enumeration — расширяемый строковый идентификатор вместо
enum - Services —
NameServiceи сервис имени текущего игрока - Локализация — переводы в базе, ассетах и на префабах, живой перевод чисел, сбор и обмен через CSV
- Utils — мелкие помощники: логирование, ресурсы, работа с материалами
- GameDataStorage — storage-контракты и универсальный
ProjectDataMap - Состояние объекта сцены —
SaveableObjectState: объект появляется таким, каким его оставили - Extensions — extension-методы общего назначения для Unity и типов SDK
- Quality — качество предметов: уровни редкости и их отображение
- Utils — вспомогательные классы: время, отложенные вызовы, имена
- Proxies — переадресация Unity-callback'ов с дочерних объектов родительским компонентам
- Property modifiers — динамические характеристики, персональные модификаторы и
GameRules - GameRules — глобальные границы характеристик, применяемые последними
- Modules — опциональные игровые модули:
StateManager - DamageSystem — создание, модификация и применение урона через хуки; правила урона игры (
DamageRules) из настроек проекта, со сцены и из кода - GameSessions — сессия карты и раунды с режимами и правилами; одиночная игра и локальный мультиплеер
- HitBox — связь физических коллайдеров с
DamageSystem - DOTweenEffects — связь DOTween с логической паузой и
PRTimeScale - YG2 Integration — облачные сохранения, реклама и платформенные возможности Яндекс Игр
- Фреймворк распространяется как Unity Assets, а не как UPM-пакет.
- Автоматический installer пока не создаёт настройки, слои, теги и prefab'ы.
- Bootstrap по умолчанию предполагает сцены с индексами
0и1. - Часть каталогов и API всё ещё находится в процессе переноса и рефакторинга.
- Не все модули имеют отдельную документацию и тестовое покрытие.
- Версии нет: понять, на какой сборке фреймворка сделана игра, пока негде.