Skip to content

A lightweight framework for annual architecture. Created specifically for AbyssMoth.

License

MIT, Unknown licenses found

Licenses found

MIT
LICENSE
Unknown
LICENSE.meta
Notifications You must be signed in to change notification settings

RimuruDev/AbyssMothNodeFramework

Repository files navigation

AbyssMothNodeFramework

Dependencies: NaugntyAttribute -> assetstore

Logo

AbyssMoth NodeFramework — Cookbook (v2.2.4)

0) Старт игры: кто кого запускает

Boot цепочка:

  1. AppEntry создаёт GlobalRoot и вешает SceneOrchestrator (DontDestroyOnLoad)
  2. SceneOrchestrator поднимает ProjectRootConnector (из Resources, если надо)
  3. ProjectRootConnector.Awake() создаёт ProjectContext и вызывает Execute(ProjectContext, sender: this)
  4. На каждый sceneLoaded SceneOrchestrator вызывает SceneConnector.Execute(projectRoot.ProjectContext) для сцены

1) Контейнеры: где какие сервисы живут

ProjectContext (глобальный, живёт всегда)

  • Хост: ProjectRootConnector.ProjectContext
  • Доступ откуда угодно:
    • ProjectRootRegistry.GetContext()
    • ProjectServices.Context
    • ProjectServices.Get<T>() / TryGet<T>() / Add<T>()

SceneContext (на сцену, родитель = ProjectContext)

  • Хост: SceneConnector.SceneContext
  • Создаётся внутри SceneConnector.Execute(projectContext) как new ServiceRegistry(parentContainer: projectContext)
  • Внутри сцены любой нод получает ServiceRegistry registry в Bind/Construct/... — это и есть SceneContext

Важно

  • У ServiceRegistry нет публичного доступа к parent — и не надо: TryGet/Get автоматически поднимаются вверх.
  • Если ты в SceneContext вызываешь registry.Get<SomeGlobalService>(), он спокойно найдёт его в ProjectContext.

2) ServiceRegistry: как регать/получать сервисы

Внутри нода (у тебя есть registry)

public sealed class AudioBootstrapNode : ConnectorNode
{
    public override void Bind(ServiceRegistry registry)
    {
        registry.Add(new AudioService());
    }

    protected override void DisposeInternal()
    {
        // если надо гарантированно убрать именно свой инстанс:
        // registry.RemoveIfSame(expected: audioService);
    }
}

Глобально (ProjectContext) из любого места

var lifecycle = ProjectServices.Get<AppLifecycleService>();
ProjectServices.Add(new AnalyticsService());
var analytics = ProjectServices.Get<AnalyticsService>();

Когда нужен TryGet, а когда Get

  • TryGet — если сервис опциональный / модульный
  • Get — если сервис “обязан существовать” (например аналитика в сборке с аналитикой)

3) Жизненный цикл нода (ConnectorNode)

Порядок вызовов при Execute(LocalConnector)

  1. Bind(registry)
  2. Construct(registry)
  3. BeforeInit()
  4. Init()
  5. AfterInit()
  6. затем циклы: Tick / FixedTick / LateTick

Где что делать

  • Bind — сохранить ссылки на registry, добавить/получить сервисы, базовые подписки
  • Construct — создать “тяжёлые” штуки, которые не зависят от других нодов
  • BeforeInit/Init/AfterInit — логика инициализации в 3 фазы (аналог Awake/Start/после прогрева)
  • DisposeInternal — отписки и чистка (всё что подписывал — отписать)

Шаблон нода

public sealed class MyNode : ConnectorNode
{
    private ServiceRegistry registry;

    public override void Bind(ServiceRegistry registry)
    {
        this.registry = registry;
    }

    public override void Construct(ServiceRegistry registry) { }

    public override void BeforeInit() { }
    public override void Init() { }
    public override void AfterInit() { }

    public override void Tick(float deltaTime) { }
    public override void FixedTick(float fixedDeltaTime) { }
    public override void LateTick(float deltaTime) { }

    protected override void DisposeInternal()
    {
        // отписки
    }
}

Про Unity callbacks

В нодах держи логику в Bind/Init/Tick.... (В дебаге у тебя есть валидация, которая ругается на Awake/Start/Update у нодов.)


4) Порядок выполнения (Order)

Ноды внутри LocalConnector

  • Сортировка: Order (IOrder), затем по имени типа

  • Где задавать:

    • в инспекторе у нода (ConnectorNode уже имеет поле Order)
    • или в OnValidate конкретного нода (Editor-only)

Коннекторы в SceneConnector

  • Сортировка: Order, затем по имени объекта

5) LocalConnector: Tick/Pause/Resume/Dispose

Пауза тиков у коннектора (и рассылка в IPausable ноды)

connector.OnPauseRequest(sender: this);   // выключит EnabledTicks и вызовет OnPauseRequest у нодов
connector.OnResumeRequest(sender: this);  // включит обратно и вызовет OnResumeRequest

RunWhenDisabled

  • Если нод отключён (isActiveAndEnabled == false), он всё равно может тикать, если это ConnectorNode и у него включён RunWhenDisabled.

Dispose

  • LocalConnector.Dispose():

    • отписывается от сцены
    • вызывает Dispose() у нодов с IDispose
    • чистит кэши tick интерфейсов

6) SceneConnectorRegistry: как получить SceneContext извне нодов

using UnityEngine.SceneManagement;

var scene = SceneManager.GetActiveScene();

if (SceneConnectorRegistry.TryGet(scene, out var sceneConnector))
{
    var sceneContext = sceneConnector.SceneContext;
    var sceneIndex = sceneContext.Get<SceneEntityIndex>();
}

7) SceneEntityIndex: поиск по id / tag / типу нода

Как объект попадает в индекс

  • SceneConnector при Execute регает все статические коннекторы
  • Динамические коннекторы регаются через LocalConnector.OnEnable() если сцена уже инициализирована
  • Для id/tag нужен EntityKeyBehaviour на том же объекте, что и LocalConnector

Поиск коннектора по Id

var sceneIndex = registry.Get<SceneEntityIndex>();

if (sceneIndex.TryGetById(42, out var connector))
{
    // ок
}

var mustExist = sceneIndex.GetByIdOrThrow(42);

Поиск по Tag

if (sceneIndex.TryGetFirstByTag("Chest", out var chest))
{
    // первый попавшийся
}

var all = sceneIndex.GetAllByTag("Chest"); // IReadOnlyList<LocalConnector>
var mustExist = sceneIndex.GetFirstByTagOrThrow("Chest");

Поиск нода по типу (в любой LocalConnector сцены)

if (sceneIndex.TryGetFirstNode<MyNode>(out var node, includeDerived: true))
{
    // найден
}

var mustExist = sceneIndex.GetFirstNodeOrThrow<MyNode>(includeDerived: true);

Получить все ноды типа

var buffer = new List<MyNode>(64);
var count = sceneIndex.GetNodes(buffer, includeDerived: true);

Найти нод внутри конкретного коннектора

if (sceneIndex.TryGetNodeInConnector<MyNode>(connector, out var node))
{
}

Найти нод в первом коннекторе по тегу

if (sceneIndex.TryGetNodeInFirstByTag<MyNode>("Chest", out var node))
{
}

8) EntityKeyBehaviour: Id/Tag

  • Поля:

    • Id (int)
    • Tag (string)
    • AutoAssignId (bool)
  • Editor: кнопка Assign Unique Id (для статических id в сценах)

  • Runtime: если Id <= 0 и AutoAssignId == true, индекс может назначить id при регистрации


9) Динамические объекты (spawn/disable/pool)

Что происходит автоматически

  • Если ты заспавнил объект с LocalConnector:

    • при OnEnable() он попробует зарегаться в SceneConnector и выполниться (если сцена уже initialized)
  • При OnDisable() — разрегистрируется

  • Execute() у LocalConnector вызывается один раз (есть флаг executed)

Если нужен “полный снос” объекта с корректным Dispose

Используй утилиту:

ConnectorDestroyUtils.DisposeAndDestroy(gameObject);

10) Переходы между сценами через EmptySceneTransition + cleanup

В проекте есть SceneTransitionService:

  • Go(targetSceneName, doCleanup)

  • грузит transitionSceneName

  • опционально делает:

    • Resources.UnloadUnusedAssets()
    • GC.Collect() + WaitForPendingFinalizers() + GC.Collect()
  • потом грузит целевую сцену

Как зарегистрировать SceneTransitionService один раз (в ProjectContext)

Сделай нод на ProjectRootConnector (он DontDestroy) и зарегай сервис в Bind:

public sealed class SceneTransitionsBootstrapNode : ConnectorNode
{
    [SerializeField] private string transitionSceneName = "EmptySceneTransition";

    public override void Bind(ServiceRegistry registry)
    {
        // runner = этот нод (MonoBehaviour), он живёт вместе с ProjectRootConnector
        ProjectServices.Add(new SceneTransitionService(runner: this, transitionSceneName: transitionSceneName));
    }
}

Как вызывать переход из любого места

ProjectServices.Get<SceneTransitionService>().Go("Level_2", doCleanup: true);

Примечание про SceneConnector в transition-сцене

  • В EmptySceneTransition можно не иметь SceneConnector.
  • В Editor у тебя может быть warn, если SceneConnector не найден — либо игнорируй, либо добавь пустой SceneConnector в transition-сцену.

11) AppLifecycleService: фокус/пауза/выход

SceneOrchestrator гарантирует наличие AppLifecycleService в ProjectContext.

Подписка из нода

public sealed class LifecycleListenerNode : ConnectorNode
{
    private AppLifecycleService lifecycle;

    public override void Bind(ServiceRegistry registry)
    {
        lifecycle = ProjectServices.Get<AppLifecycleService>();
        lifecycle.FocusChanged += OnFocusChanged;
        lifecycle.PauseChanged += OnPauseChanged;
        lifecycle.Quit += OnQuit;
    }

    protected override void DisposeInternal()
    {
        if (lifecycle == null)
            return;

        lifecycle.FocusChanged -= OnFocusChanged;
        lifecycle.PauseChanged -= OnPauseChanged;
        lifecycle.Quit -= OnQuit;
    }

    private void OnFocusChanged(bool hasFocus, Object sender) { }
    private void OnPauseChanged(bool paused, Object sender) { }
    private void OnQuit(Object sender) { }
}

12) Быстрые рецепты (1 строка)

  • Получить ProjectContext:

    • var project = ProjectServices.Context;
  • Получить SceneContext из MonoBehaviour:

    • SceneConnectorRegistry.TryGet(gameObject.scene, out var sc); var ctx = sc.SceneContext;
  • Получить SceneEntityIndex из SceneContext:

    • var index = ctx.Get<SceneEntityIndex>();
  • Найти объект по тегу:

    • var door = index.GetFirstByTagOrThrow("Door");
  • Найти нод по типу:

    • var ui = index.GetFirstNodeOrThrow<MyUiNode>();
  • Усыпить тики у конкретного LocalConnector:

    • connector.OnPauseRequest(sender: this);
  • Удалить объект “правильно”:

    • ConnectorDestroyUtils.DisposeAndDestroy(go);

About

A lightweight framework for annual architecture. Created specifically for AbyssMoth.

Topics

Resources

License

MIT, Unknown licenses found

Licenses found

MIT
LICENSE
Unknown
LICENSE.meta

Stars

Watchers

Forks

Packages

No packages published

Languages