1. GameFacade показывает доступные возможности
Service выполняет одну понятную задачу. Facade даёт типизированный доступ к важным сервисам и операциям текущего окружения. Он скрывает сложность доступа, но не забирает себе игровую логику.
В проекте могут быть отдельные фасады для Menu и Play. В обоих случаях точка входа называется Game., но IDE показывает возможности именно этой части игры. В исходных примерах это вызовы:
Game.UI.Settings.Open();
Game.Flow.PlayMap(_mapDocks, _gameMode_Roam);
Первый открывает настройки, второй сокращает последовательность перехода к карте. Такой shortcut допустим, пока фасад не становится владельцем игровых решений. Расчёт урона и разрешение боя должны оставаться в соответствующих системах или сервисах.
Основные обязательные сервисы стоит делать видимыми в типизированном API фасада. GetService<T> — редкий и объяснимый выход для возможностей, которые не нужно включать в основной интерфейс. Необязательная зависимость должна быть явно необязательной: для неё нужен TryGetService или отдельный optional API.
Свойство должно оставаться дешёвым кешированным чтением, без выделения памяти и скрытых действий — тогда его можно использовать в Update. Запросы и изменения состояния выражаются методами или командами.
2. Интерфейс меняет форму зависимости, данные — её границу
Flexy сознательно использует явный layered Service Locator. Это выбор способа организации окружения. DI тоже не уничтожает зависимости: он может перенаправлять их через контейнер. Выбор инструмента не отменяет необходимость понимать связи и время жизни объектов.
Если объект по смыслу существует только в CoregameContext, зависимость от этого окружения может честно отражать устройство игры. Прятать её за интерфейсом только ради формальной независимости не всегда полезно.
Предположим, мобу нужен бонус скорости. Передача IPerkProvider оставляет зависимость от поставщика perks. Передача готового значения SpeedBoost делает границу уже: объекту нужен результат, а не чужая подсистема. Но если по design моб действительно живёт в coregame и использует perks игрока, прямой доступ через Game.Player.Perks.SpeedBoost может быть проще и честнее.
Выбирайте форму по реальной ответственности. Интерфейс полезен, когда выражает нужную границу; дополнительная абстракция без задачи только усложняет путь к данным.
Границы и применение Facade
Facade - это typed access surface к important services и operations.
Meaning
- Facade упрощает доступ к context.
- Facade не должен владеть gameplay logic.
- Shortcut methods допустимы, если они не превращаются в domain behavior.
What It Prevents
- Прямой доступ к service container из любого места.
- Новый
GameManagerпод красивым именем. - Hidden dependency resolution через
Resolve<Anything>().
Critique Notes
Facade, Context и explicit Service Locator
- В Flexy
ContextиGameFacadeсознательно являются explicit layeredService Locator. - Это не ошибка, а design choice.
- DI в Unity часто на практике является тем же
Service Locator, только скрытым за container, bindings, installers и framework ceremony. - DI не убирает dependencies, а перенаправляет их через container.
- Способность DI создавать objects на ходу в gameplay runtime может быть вредной, потому что усложняет lifetime и делает связи менее явными.
- Flexy предпочитает открытые и прямые связи скрытым связям.
- Если entity не может жить вне
CoregameContext, dependency наCoregameContextне является проблемой. Это production fact. CoregameContextсодержит конкретные обязательные services, без которых core game не живет.- Optional services должны доставаться явно через
TryGetService, например camera-like services. GetService<T>допустим как rare escape hatch для services, которые редко используются и не должны засорять основной facade surface.GetService<T>не должен быть основной формой доступа, если service является важной частью игры.- Для записи/изменений используется
MsgBus.Sendили explicit service methods. - Public API конкретного service - ответственность самого service, а не глобальной architecture.
- Interface не делает object независимым. Он только меняет форму dependency.
- Если в production существует одна реализация interface, interface часто является ceremony и лишним indirection.
- Настоящая независимость появляется, когда object получает data/value, а не provider/interface.
- Например, чтобы mob не зависел от perks, ему нужно передать
SpeedBoostкак value, а неIPerkProvider. - Но если mob по design живет внутри coregame и зависит от player perks, прямой доступ через
Game.Player.Perks.SpeedBoostможет быть проще и честнее. - В hot path не стоит добавлять interface/virtual indirection без реальной пользы.
- Execution order в играх не является smell.
- Hidden execution order является smell.
- Games are frame pipelines.
- Systems и groups должны делать frame pipeline явным.
ScriptExecutionOrderплох не сам по себе, а когда порядок размазан, не документирован и не выражает gameplay pipeline.- Доступ через facade в
Updateдопустим, если он cached, predictable, allocation-free и не скрывает mutation или object creation. - Field/property/method naming должен не врать о cost и side effects.
- Properties должны быть cheap cached reads.
- Methods могут быть queries/actions.
- Commands должны явно менять state.
Риски, которые остаются:
- обязательные и optional services смешиваются;
GetService<T>становится основным способом доступа;- interfaces используются как fake decoupling;
- systems order существует, но не описан явно;
- service API плохо назван и скрывает cost/mutation.
Формулировки, которые можно перенести почти напрямую:
DI does not remove dependencies. It redirects them through a container.
Interface does not make an object independent. Data input does.
If a game object cannot exist outside
CoregameContext, dependency onCoregameContextis not a problem. It is a fact.Flexy does not hide
Service Locator. Flexy makes service location explicit, layered and visible.Execution order is not a smell in games. Hidden execution order is.
Games are frame pipelines. Systems and groups should make that pipeline explicit.
Service location is not the enemy. Uncontrolled hidden access is the enemy.
Facade naming confusion
GameFacade- корректное название, если сразу объяснить его Flexy-смысл.GameFacadeдействительно является facade: он скрывает dynamic context/service structure за simple typed API.GameFacadeскрывает access complexity, а не должен владеть всей domain/game logic.GameFacadeотвечает за:- доступ к services текущего context/package;
- кеширование access;
- удобный
Game.entry point; - IDE/IntelliSense visibility;
- readable service surface.
GameFacadeпоказывает, что доступно из конкретной части игры: menu, play, package, etc.- Logic в основном остается в services, systems, states, bricks/toys.
- Methods в facade допустимы, если это small shortcut methods.
- Small shortcut method может делать несколько вызовов к flow/state/service, чтобы дать простой API.
- Пример:
Game.Flow.PlayMap(...)может скрывать несколько вызовов для перехода из menu в play. - Это нормально, если method не содержит business logic и не становится owner gameplay decision.
- Facade method плох, если начинает содержать domain/game logic.
- Граница:
Game.Flow.PlayMap(...)- ok, если это transition shortcut;Game.UI.Settings.Open()- ok;Game.Audio.PlayClick()- ok;Game.CalculateDamage()- suspicious;Game.ResolveCombat()- suspicious;Game.ApplyEconomyReward()- suspicious, если это настоящая domain logic.
- Правило не "никаких methods в facade".
- Правило: facade can simplify access, but must not own gameplay logic.
Формулировки, которые можно перенести почти напрямую:
GameFacadehides dynamicContext/Servicestructure behind simple typed API.Facade is allowed to simplify access. Facade is not allowed to own gameplay logic.
Small shortcut methods are fine. Hidden domain logic is not.
GameFacadeturns service location into readable code.