Перейти к основному содержимому

Содержание

Введение

World Graph Editor — это инструмент, предоставляющий удобный редактор на основе узлов (нод), который позволяет легко определять связи между сценами, упрощая создание проектов с большим количеством сцен. Помимо этого, инструмент содержит встроенные механизмы осуществления переходов с возможностью ожидания выполнения асинхронных задач.

Материалы

Основные возможности

  • Редактор на основе нод, позволяющий определить, как соединяются ваши сцены и как игрок перемещается между ними.
  • Возможность задать один из трех типов связей между сценами: (ненаправленные, односторонние и шорткаты).
  • Визуальное выделение и подписи для GameObject-ов, представляющих порты на графе.
  • Автоматическое добавление сцен в Build Settings .
  • Проверка корректности данных при входе в Play Mode или сборке проекта.
  • Превью сцен на нодах в графе для более быстрой навигации в больших проектах.
  • Совместимость с 2D и 3D проектами.
  • Поддержка отмены/повтора действий (Undo/Redo).
  • Поддержка Unity 6.
  • Проверка графа на достижимость сцен.

Дополнительные возможности

  • Префаб TransitionManager для управления переходами между сценами во время игры.
  • Возможность автоматически создавать объект игрока при загрузке новой сцены.
  • Поддержка выполнения асинхронного кода во время переходов между сценами.
  • Оверлеи Scene View: краткий статус контейнера и сцены, создание превью для нод.
  • Расширения главного тулбара Unity: быстрый доступ к Transition Manager, переключение сцен по графу / Build Settings / полный список.
  • Окно настроек Edit → Project Settings → World Graph Editor (граф, подсветка портов, тулбар, кастомные тесты).
  • Демо проект с несколькими сценами и кастомными компонентами.

Технические детали

Поддерживаемые операционные системы:
Windows, macOS, Linux

Инструмент работает внутри редактора Unity и был протестирован на Windows и macOS. Совместимость с Linux ожидается, но не тестировалась должным образом.

Поддерживаемые платформы:
Windows, macOS, WebGL

Никаких ограничений, связанных с другими платформами, обнаружено не было, однако на них не проводилось отдельного тестирования.

Обновление с версии 1.0

Если вы обновляетесь с версии 1.0, внимательно ознакомьтесь с этим разделом. Он описывает перенос данных из 1.0 в 1.1 и новее (в том числе в актуальную 1.2).

Новая система сохранений

Начиная с версии 1.1 используется новая система сохранений, несовместимая с форматом версии 1.0. Чтобы конвертировать ваши данные:

  1. Откройте проект и убедитесь что все сцены, используемые в графе находятся в Build Settings. Для этого откройте в графе ваш контейнер и нажмите кнопку "Save".
  2. Найдите и выберите нужный контейнер в Project-окне. В окне Inspector нажмите на три вертикальные точки (⋮) и выберите пункт To Json. При успешном экспорте рядом с контейнером появился .json-файл с таким же именем.
  3. Удалите текущую версию World Graph Editor и установите новую.
  4. После установки создайте новый контейнер или выберите существующий. Важно: его имя должно совпадать с именем .json-файла, и он должен находиться в той же папке.
  5. Выберите контейнер в окне Inspector, нажмите и выберите Load From Json. Если все сделано правильно, контейнер автоматически заполнится данными, а так же появится вложенный ассет с именем Editor Only Data.
  6. Откройте граф с этим контейнером и нажмите "Save".

Видео: Ссылка

Обновленная регистрация асинхронных методов

Начиная с версии 1.1 методы RegisterAsyncHandler и UnregisterAsyncHandler теперь принимают делегаты типа Func<CancellationToken, Task> вместо Func<Task>. Старые версии этих методов помечены как [Obsolete] и вызовут ошибку компиляции.

Чтобы обновить код, добавьте параметр CancellationToken в асинхронный метод:

Было:

TransitionManager.RegisterAsyncHandler(eventType, DoSomething, 0);

private async Task DoSomething()
{
await Awaitable.WaitForSecondsAsync(1f);
}

TransitionManager.UnregisterAsyncHandler(_eventType, DoSomething);

Стало:

TransitionManager.RegisterAsyncHandler(eventType, DoSomething, 0);

private async Task DoSomething(CancellationToken token)
{
await Awaitable.WaitForSecondsAsync(1f, token);
}

TransitionManager.UnregisterAsyncHandler(_eventType, DoSomething);

Обновление с версии 1.1

Если вы обновляетесь с версии 1.1 на 1.2, учтите следующие изменения в API и поведении редактора.

Интерфейс ITransitionComponent

  • Метод в редакторе: void Refresh(RefreshContext context) (раньше в примерах мог встречаться вариант с TransitionManager — используйте RefreshContext).
  • Чтобы заполнить PortsDropdown портами текущей активной сцены, вызывайте context.FillPortsDropdownData(dropdown).
  • Чтобы заполнить список всеми портами графа, вызывайте context.FillAllPortsDropdownData(dropdown).

При необходимости ручного доступа к данным используйте context.Manager и WorldGraphContainer.EditorData.

ContainerEditorData и пути сцен

  • Рекомендуемый метод: GetPortsDropdownData(string scenePath) — путь к .unity файлу сцены (как у Scene.path / SceneManager.GetActiveScene().path).
  • Перегрузка GetPortsDropdownData(int buildIndex) помечена как [Obsolete] и сохранена для обратной совместимости; в новом коде лучше перейти на вариант с путём сцены.

TransitionManager.RefreshPorts()

  • Метод TransitionManager.RefreshPorts() помечен как [Obsolete] и не выполняет обновление. В редакторе списки портов и компоненты с ITransitionComponent обновляются автоматически при смене сцены, иерархии и ряде других действий. Не вызывайте этот метод из OnValidate и пользовательского кода — полагайтесь на автоматическое обновление.

Демо проект

Инструмент не зависит от выбранного рендер-пайплайна, однако демонстрационные сцены используют URP и старую систему ввода (Input Manager).

Как запустить

  1. Импортируйте World Graph Editor в проект
  2. Перейдите по пути Assets/WorldGraphEditor/Resources/TransitionManager.prefab и нажмите кнопку Refresh Build Settings (обновить список сцен) под полем Container. Это добавит демо сцены в настройки сборки
  3. Откройте сцену Example 1 по пути: Assets/WorldGraphEditor/Examples/Scenes/Example 1.unity
  4. Запустите игру

Возможные проблемы

После импорта демонстрационного проекта может не работать прыжок у персонажа. Это связано с отсутствием корректного слоя для определения земли.

Как исправить

  1. Создайте новый физический слой, например MyGround
  2. Откройте префаб игрока по пути: Assets/WorldGraphEditor/Examples/Prefabs/Player.prefab
    и в поле LayerMask установите ваш новый слой
  3. В каждой демонстрационной сцене (Assets/WorldGraphEditor/Examples/Scenes)
    задайте этот слой (MyGround) всем дочерним объектам объекта Ground

После этого прыжки игрока должны заработать корректно.

Использование

С чего начать

Настройки проекта

Откройте Edit → Project Settings → World Graph Editor. Здесь можно задать:

  • Прозрачность фона нод в окне графа;
  • Режим подсветки портов в Hierarchy (имена и цвета, только имена или без подсветки);
  • Расширения главного тулбара Unity: кнопку быстрого открытия префаба Transition Manager (подпись TM), выпадающий список сцен с режимами Neighbours (соседи по графу для активной сцены), Build Settings и All Scenes;
  • Массив кастомных тестов валидации сцен.

Создание WorldGraphContainer

  1. В папке проекта создайте WorldGraphContainer:
    • ПКМ → Create → World Graph Editor → World Graph Container
  2. Дважды нажмите ЛКМ по созданному объекту, чтобы открыть окно редактора

Добавление сцен

Перетащите нужные сцены в открывшееся окно для создания нод. Каждая нода представляет собой сцену, а каждый порт на ноде обозначает переход на другую сцену.

[!WARNING]
Избегайте дубликатов сцен, так как это приведёт к ошибкам. Дубликаты будут подсвечены красным.

Виды портов

При создании портов с помощью кнопки "+" на ноде открывается контекстное меню, позволяющее создать один из следующих портов:

  • Left Passage, Right Passage, Top Passage, Bottom Passage — связующие порты (проходы), для создания связей, которые используются для соединения нод
  • Additional Port — порт, не предназначенный для связи, используется в случаях, требующих, например, реализации системы быстрого перемещения

Создание портов и связей

  1. Нажмите на "+" на выбранной ноде и создайте связующий порт
  2. Перетащите связь из порта первой ноды к середине второй ноды
  3. Если все сделано правильно, между нодами появится связь

Таким образом, вы указали какими проходами сцены будут связаны между собой. Дополнительная настройка портов доступна в окне Inspector. Выберите нужную ноду, чтобы изменить её параметры.

[!WARNING]
Каждый порт на ноде должен иметь уникальное название, а также оно не может быть пустым или состоять только из пробелов. В противном случае это приведет к ошибкам.

Типы возможных связей между нодами

  • Undirected — Связь по умолчанию. Перемещение между проходами возможно в любом направлении
  • Shortcut — Указывает, что проход изначально доступен только с одной стороны (например, запертая дверь, открывающаяся с другой стороны). Может учитываться в собственной реализации портов и блокировать переход в зависимости от условий
  • One-Way — Связь, позволяющая проходить исключительно в заданном направлении

Тип связи настраивается в контекстном меню, которое открывается с помощью клика ПКМ по уже существующей связи.

Сохранение и настройка

Сохранение графа

Когда закончите работу с графом, нажмите кнопку "Save" чтобы сохранить его.

[!NOTE]
При сохранении, все несвязанные порты, кроме дополнительных (Additional Port), будут удалены.

[!NOTE]
При сохранении будет предложено добавить все использованные сцены в Build Settings, так как это необходимо для дальнейшей работы.

Настройка TransitionManager

  1. Убедитесь, что менеджер существует по пути: Assets/WorldGraphEditor/Resources/TransitionManager. Если нет,
    создайте его с помощью пункта меню:
    • Tools → World Graph Editor → Create Transition Manager Prefab
  2. Перетащите сохранённый WorldGraphContainer в соответствующее поле в TransitionManager. Таким образом, вы указываете какой контейнер будет использоваться
  3. Установите AutoLoad в значение true. Это позволит менеджеру загружаться автоматически при старте игры
  4. Установите чекбокс в правой части поля PlayerPrefab в значение true и перетащите в него префаб игрока

Благодаря этому на каждой сцене автоматически будет создаваться объект игрока. Если вам не подходит данный способ загрузки менеджера, вы можете использовать свой.

Настройка сцен

Для представления портов на сценах добавьте соответствующие компоненты.
По умолчанию доступны:

  • Passage2D - Перемещает игрока к противоположному порту, опираясь на заданную связь в графе
  • Teleport2D - Перемещает игрока к любому другому существующему порту на графе

Вы также можете задать позицию по умолчанию, добавив на сцену объект с компонентом DefaultSpawnPosition. Благодаря этому, если на сцене не будет найден выходной порт OutputTransitionComponent, игрок появится на этой позиции.

Настройка компонентов

  • Passage2D

    • В поле AssignedPort выберите имя прохода, который будет соответствовать порту в графе
  • Teleport2D

    • В поле AssignedPort выберите имя прохода, который будет соответствовать порту в графе
    • В поле GoTo выберите порт, к которому должен быть осуществлён переход

[!TIP]
Каждый порт на сцене в поле AssignedPort должен ссылаться к одному порту на ноде. В поле AssignedPort для каждого порта на сцене должно быть указано соответствие определённому порту на ноде.

При необходимости, вы можете написать собственные реализации.

Проверка

Ожидаемое поведение

  • При запуске в DontDestroyOnLoad автоматически создаётся объект TransitionManager
  • При контакте с объектом порта, персонаж перемещается на другую сцену, в позицию соответствующую целевому порту

Ошибки

Если целевой порт отсутствует на сцене:

  • Объект игрока появится в позиции, заданной в DefaultSpawnPoint, или, если она не задана — в позиции (0, 0, 0)
  • В консоль будет выведено сообщение об ошибке

[!WARNING] Игра автоматически остановится, если:

  • TransitionManager отсутствует по пути:Assets/WorldGraphEditor/Resources/TransitionManager
  • Поле Container в TransitionManager пустое
  • Контейнер содержит в себе ошибки
  • Сцены в Build Settings были удалены, отключены или изменён их порядок

В консоль будет выведена ошибка с инструкцией по её исправлению.

Оверлеи Scene View

В окне Scene доступны два встроенных оверлея пакета: Scene Inspector WGE и Screenshot Utility WGE. Их можно включить, закрепить и скрыть так же, как и другие оверлеи Scene View.

Scene Inspector WGE

Показывает краткий статус конфигурации Transition Manager и контейнера: назначен ли контейнер, нет ли ошибок в данных, совпадают ли сцены с Build Settings. Для активной сцены выводится сводка по соответствию графу (в т.ч. наличие данных по сцене и портам). Внизу панели: кнопка Validate — запуск проверок сцен с записью результатов в ассет валидации проекта; Open TM — быстрое открытие префаба Transition Manager.

Screenshot Utility WGE

Служит для съёмки превью текущей сцены, которое затем используется в окне графа на ноде сцены. Задаются область кадра (позиция, размер, поворот), дистанция и тип камеры (перспектива / ортография), а также параметры изображения (разрешение, режим для текстур non-power-of-two). Настройки можно сохранить для этой сцены (Save Scene Preset), задать глобальный набор по умолчанию для сцен без своих настроек (Save as Global Default) или сбросить к встроенным значениям (Reset to Built-In). Кнопка Take Screenshot сохраняет изображение; в Scene View отображаются ручки для позиционирования и рамки кадра.

Кастомизация

Собственная реализация портов

Общее

Для создания собственной реализации:

  • Наследуйтесь от PassageBase или TeleportBase, либо реализуйте ITransitionComponent.
  • Используйте класс PortsDropdown и соответствующие методы для создания выпадающих списков для инициализации текущего порта или выбора любого порта на графе.
  • Реализуйте метод GetGuid(), возвращающий Guid текущего порта.
  • В редакторе реализуйте Refresh(RefreshContext context) и при необходимости вызывайте context.FillPortsDropdownData / context.FillAllPortsDropdownData либо обращайтесь к context.Manager и EditorData (см. обновление с 1.1).

PassageBase, TeleportBase и ITransitionComponent

PassageBase и TeleportBase — абстрактные базовые классы для прохода и телепорта: в них уже проведена работа с PortsDropdown и полями. Для большинства случаев достаточно наследоваться от одного из них и переопределить нужные методы (например, GetSpawnPosition()).

ITransitionComponent — минимальный контракт «порт на сцене»:

  • string GetGuid() — идентификатор порта в графе.
  • Vector3 GetSpawnPosition() — позиция появления персонажа у этого порта.

Дополнительно в редакторе ожидается метод void Refresh(RefreshContext context): в нём обычно вызывают context.FillPortsDropdownData / context.FillAllPortsDropdownData и считывают выбранные Guid из PortsDropdown.

Если не подходят готовые базовые классы, можно реализовать только ITransitionComponent и написать логику самостоятельно (см. примеры).

[!TIP]
Метод Refresh() рекомендуется обернуть в #if UNITY_EDITOR, так как он нужен только в редакторе.

Методы расширения

Для ITransitionComponent так же предусмотрены методы расширения. IsInput() и IsOutput() позволяют узнать, является ли текущий компонент начальным или конечным для перехода (через который персонаж вошел/вышел).

МетодНазначение
IsOutput()true если это компонент, в котором закончен переход.
IsInput()true если это компонент, из которого начат переход.

IsShortcutOutput() и IsShortcutInput() позволяют узнать, является ли текущий компонент для shortcut-связи между сценами входным или выходным.

МетодНазначение
IsShortcutOutput()true если компонент является "выходным"
IsShortcutInput()true если компонент является "входным"

[!TIP] Если тип связи не shortcut - оба метода вернут false.

RefreshContext

RefreshContext — структура, которую редактор передаёт в метод void Refresh(RefreshContext context) у компонентов с интерфейсом ITransitionComponent. Используется только в редакторе. При смене сцены, иерархии и обновлениях графа вызывается Refresh, чтобы подтянуть актуальные Guid портов в PortsDropdown и связанные поля.

Поля:

ИмяТипОписание
ManagerITransitionManagerСсылка на TransitionManager. Через Manager.Container доступен WorldGraphContainer и далее EditorData для ручного доступа к данным, если не хватает методов ниже.

Методы:

МетодНазначение
void FillPortsDropdownData(PortsDropdown dropdown)Заполняет список портами активной сцены.
void FillAllPortsDropdownData(PortsDropdown dropdown)Заполняет список всеми портами графа из контейнера.

Методы переходов в TransitionManager

Task LoadScene(int buildIndex, TransitionContext context = null)

Загружает сцену по переданному индексу.

Параметры:

ИмяТипОписание
buildIndexintBuild Index сцены
contextTransitionContextОпциональная задержка межу фазами перехода

void GoFrom(string currentPortGuid, bool ignoreShortcuts, TransitionContext context = null)

Перемещает игрока к противоположному порту, опираясь на связи в графе.

Параметры:

ИмяТипОписание
currentPortGuidstringGuid текущего порта
ignoreShortcutsboolИгнорировать ли шорткаты
contextTransitionContextОпциональная задержка межу фазами перехода

void GoTo(string currentPortGuid, string targetPortGuid, TransitionContext context = null)

Перемещает игрока на любой выбранный порт. Не опирается на связи в графе.

Параметры:

ИмяТипОписание
currentPortGuidstringGuid текущего порта
targetPortGuidstringGuid целевого порта
contextTransitionContextОпциональная задержка межу фазами перехода

Примеры GoTo/GoFrom

Пример минимальной рабочей реализации собственного прохода с помощью GoFrom:

using UnityEngine;

namespace WorldGraphEditor.Examples
{
public class ExamplePassage : MonoBehaviour, ITransitionComponent
{
[SerializeField] private PortsDropdown _assignedPort = new();

private string _currentGuid;

// Любой вызов, например при входе в триггер
private void OnTriggerEnter(Collider other)
{
// Вызов GoFrom
TransitionManager.Instance.GoFrom(_currentGuid, false);
}

#if UNITY_EDITOR
public void Refresh(RefreshContext context)
{
context.FillPortsDropdownData(_assignedPort);
_currentGuid = _assignedPort.GetSelectedValue();
}
#endif
public Vector3 GetSpawnPosition() => transform.position;

public string GetGuid() => _currentGuid;
}
}

Пример минимальной рабочей реализации собственного телепорта с помощью GoTo:

using UnityEngine;

namespace WorldGraphEditor.Examples
{
public class ExampleTeleport : MonoBehaviour, ITransitionComponent
{
[SerializeField] private PortsDropdown _assignedPort = new();
[SerializeField] private PortsDropdown _targetPort = new();

private string _currentGuid;
private string _targetGuid;

// Любой вызов, например при входе в триггер
private void OnTriggerEnter(Collider other)
{
// Вызов GoTo
TransitionManager.Instance.GoTo(_currentGuid, _targetGuid);
}

#if UNITY_EDITOR
public void Refresh(RefreshContext context)
{
context.FillPortsDropdownData(_assignedPort);
context.FillAllPortsDropdownData(_targetPort);

_currentGuid = _assignedPort.GetSelectedValue();
_targetGuid = _targetPort.GetSelectedValue();
}
#endif

public Vector3 GetSpawnPosition() => transform.position;

public string GetGuid() => _currentGuid;
}
}

TransitionContext

TransitionContext — это класс, позволяющий переопределить задержку между фазами для текущего перехода. Может быть передан в качестве дополнительного параметра в GoTo, GoFrom и LoadScene.

Пример


TransitionManager.Instance.GoFrom(_port.GetSelectedValue(), true,
new TransitionContext
{
PortEnteredDelay = new TransitionDelayData(DelayType.ThisFrame),
TransitionStartedDelay = new TransitionDelayData(DelayType.CustomDelay, 1.2f)
});

PortsDropdown

PortsDropdown — это класс, предназначенный для создания выпадающих списков, связанных с портами в графе. Он обеспечивает сохранность выбранного значения даже при переименовании портов или изменении их количества на ноде.

Содержит два метода:

string GetSelectedValue()

Возвращает выбранное значение.

Возвращаемые значения:

ТипОписание
stringВыбранное значение (Guid)

void SetData(IEnumerable<(string Guid, string DisplayName)> data)

Обновляет данные для выпадающего списка, состоящие из Guid и отображаемого имени.

Параметры:

ИмяТипОписание
dataIEnumerable<(string Guid, string DisplayName)>Данные, где каждый элемент содержитGuid и имя для отображения

Низкоуровневый доступ: EditorData и PortsDropdown

В типичном сценарии для заполнения PortsDropdown данными из графа в Refresh(RefreshContext context) достаточно вызвать context.FillPortsDropdownData(dropdown) или context.FillAllPortsDropdownData(dropdown) — см. Обновление с версии 1.1.

Ниже описаны методы поля EditorData у WorldGraphContainer: они возвращают сырые последовательности для SetData, если нужна фильтрация, объединение с другими данными или иная кастомная сборка списка. Доступ к EditorOnly-методам через EditorData; использование только в редакторе.

IEnumerable<(string Guid, string Name)> FillPortsDropdownData(string scenePath)

Параметры:

ИмяТипОписание
scenePathstringПуть к ассету сцены (файл .unity), как у Scene.path.

Возвращаемые значения:

ТипОписание
IEnumerable<(string Guid, string Name)>Данные обо всех портах на сцене. Их Guid и отображаемое имя

Пример

Рекомендуемый способ — через RefreshContext:

using UnityEngine;

namespace WorldGraphEditor.Examples
{
public class MyComponentWithDropdown : MonoBehaviour, ITransitionComponent
{
[SerializeField] private PortsDropdown _dropdown = new();
private string _currentPortGuid;

#if UNITY_EDITOR
public void Refresh(RefreshContext context)
{
context.FillPortsDropdownData(_dropdown);
_currentPortGuid = _dropdown.GetSelectedValue();
}
#endif
// public Vector3 GetSpawnPosition() {}
// public string GetGuid() {}
}
}

Вариант с EditorData:

using UnityEngine;
using UnityEngine.SceneManagement;

namespace WorldGraphEditor.Examples
{
public class MyComponentWithDropdown : MonoBehaviour, ITransitionComponent
{
[SerializeField] private PortsDropdown _dropdown = new();

private string _currentPortGuid;

#if UNITY_EDITOR
public void Refresh(RefreshContext context)
{
var container = context.Manager.Container;
var scenePath = SceneManager.GetActiveScene().path;
var scenePortsData = container.EditorData.GetPortsDropdownData(scenePath);

_dropdown.SetData(scenePortsData);
_currentPortGuid = _dropdown.GetSelectedValue();
}
#endif

// public Vector3 GetSpawnPosition() {}
// public string GetGuid() {}
}
}

[!NOTE] Перегрузка GetPortsDropdownData(int buildIndex) помечена как [Obsolete]. Предпочтительно использовать вариант с scenePath, чтобы не зависеть от порядка сцен в Build Settings.


IEnumerable<(string Guid, string Path)> FillAllPortsDropdownData()

Возвращает данные обо всех портах на графе, группируя их по сценам.

Возвращаемые значения:

ТипОписание
IEnumerable<(string Guid, string Path)>Данные обо всех портах на графе

Пример

Рекомендуемый способ — через RefreshContext:

using System;
using UnityEngine;

namespace WorldGraphEditor.Examples
{
public class MyComponentWithDropdown : MonoBehaviour, ITransitionComponent
{
[SerializeField] private PortsDropdown _dropdown = new();
private string _currentPortGuid;

#if UNITY_EDITOR
public void Refresh(RefreshContext context)
{
context.FillAllPortsDropdownData(_dropdown);
_currentPortGuid = _dropdown.GetSelectedValue();
}
#endif
// public Vector3 GetSpawnPosition() {}
// public string GetGuid() {}
}
}

Вариант с EditorData:

using System;
using UnityEngine;

namespace WorldGraphEditor.Examples
{
public class MyComponentWithDropdown : MonoBehaviour, ITransitionComponent
{
// Выпадающий список
[SerializeField] private PortsDropdown _dropdown = new();
private string _currentPortGuid;

#if UNITY_EDITOR
public void Refresh(RefreshContext context)
{
var container = context.Manager.Container;
var allPortsData = container.EditorData.GetAllPortsDropdownData();
_dropdown.SetData(allPortsData);
_currentPortGuid = _dropdown.GetSelectedValue();
}
#endif
// public Vector3 GetSpawnPosition() {}
// public string GetGuid() {}
}
}

Выталкивание из проходов

Вы можете добавить "силу выталкивания" для порта, реализовав интерфейс IPusher и метод GetPushData(), возвращающий PushData.

PushData это структура, содержащая в себе два поля:

  • Force — Сила выталкивания
  • Exists — Указывает, существует сила или нет
public readonly struct PushData
{
public readonly Vector3 Force;
public readonly bool Exists;

public PushData(Vector3 force)
{
Force = force;
Exists = true;
}
}

Пример

using UnityEngine;

namespace WorldGraphEditor.Examples
{
public class MyPushingPassage : PassageBase, IPusher
{
[SerializeField] private Vector2 _pushForce;

public PushData GetPushData()
{
// Если сила (0, 0), то можно вернуть PushData со значениями по умолчанию
if (_pushForce == Vector2.zero)
// Возвращает значение по умолчанию, где Exists будет false
return default;

// Если сила есть, то ее можно передать в конструктор. Exists в таком случае станет true
return new PushData(_pushForce);
}

// public void Refresh(RefreshContext context) {}
// public Vector3 GetSpawnPosition() {}
// public string GetGuid() {}
}
}

Чтобы применить эту силу к объекту игрока, используйте TransitionManager.Instance.PushData для получения актуального значения.

Пример

using UnityEngine;

namespace WorldGraphEditor.Examples
{
public class PlayerController : MonoBehaviour
{
[SerializeField] private Rigidbody2D _rigidbody;

private void OnTransitionEnded()
{
// Получение силы выталкивания
var pushData = TransitionManager.Instance.PushData;

// Применение силы к Rigidbody только если она существует
if (pushData.Exists)
_rigidbody.AddForce(pushData.Force, ForceMode2D.Impulse);
}

private void Awake()
{
TransitionManager.OnTransitionEnded += OnTransitionEnded;
}

private void OnDestroy()
{
TransitionManager.OnTransitionEnded -= OnTransitionEnded;
}
}
}

Собственная реализация запуска TransitionManager

По умолчанию TransitionManager запускается автоматически при старте игры, однако вы можете создать его в удобный для вас момент.

Отключение автозагрузки

  1. Установите AutoLoad в false
  2. Чтобы создать менеджер, вызовите TransitionManager.CreateInstance()

Пример

using UnityEngine;

namespace WorldGraphEditor.Examples
{
public class MyCustomTransitionManagerLoader : MonoBehaviour
{
[SerializeField] private float _loadDelay = 2f;

private void Start()
{
Invoke(nameof(MyLoadMethod), _loadDelay);
}

private void MyLoadMethod()
{
// Создание менеджера
TransitionManager.CreateInstance();
}
}
}

[!NOTE]
TransitionManager так же содержит в себе метод LoadFromResources(), однако он лишь возвращает экземпляр класса из папки Resources и не создает объект в DontDestroyOnLoad. В первую очередь этот метод используется в режиме редактора для получения ссылки. Рекомендуется использовать именно CreateInstance(), поскольку в нем предусмотрена защита от создания нескольких экземпляров класса.


События и вызов асинхронного кода

TransitionManager обладает событиями, которые оповещают о текущих фазах перехода:

  • OnInitialized: Вызывается после успешной инициализации TransitionManager
  • OnDestroyed: Вызывается в OnDestroy() в TransitionManager
  • OnPortEntered: Вызывается при вызове GoFrom() или GoTo()
  • OnTransitionStarted: Вызывается перед началом загрузки следующей сцены
  • OnSceneLoaded: Вызывается после загрузки сцены и после того, как был найден "выходной порт" OutputTransitionComponent а так же, определена позиция появления игрока PlayerSpawnPosition
  • OnTransitionEnded: Вызывается после создания префаба игрока. Если префаб не был указан, событие все равно будет вызвано
  • OnPortLeaved: Вызывается после OnTransitionEnded

Задержка между фазами перехода

Вы можете задать задержку между фазами перехода перед вызовом асинхронных методов с помощью EventCallConfig

  1. Создание
    • ПКМ → Create → World Graph Editor → Event Call Config
  2. Укажите один из доступных видов задержки
    • This Frame — В текущем кадре
    • Next Frame — В следующем кадре
    • Physics Update — В FixedUpdate()
    • Custom Delay — Через заданное время
  3. Перетащите EventCallConfig в соответствующее поле TransitionManager

Ожидание завершения асинхронных методов

Если нужно дождаться выполнения операций, таких, как генерация уровня, вы можете зарегистрировать асинхронные методы с учётом их приоритета с помощью TransitionManager.RegisterAsyncHandler(). Чем выше значение приоритета, тем раньше метод будет вызван.

[!TIP]
Таким образом, порядок вызова при каждой фазе перехода будет таким:

  1. Вызов события перехода (OnPortEntered / OnTransitionStarted / OnSceneLoaded / OnTransitionEnded)
  2. Ожидание задержки EventCallConfig
  3. Ожидание выполнения асинхронных методов

Тип события, после которого начинается выполнение зарегистрированных асинхронных методов, указывается с помощью перечисления EventType.

События, для которых можно зарегистрировать асинхронные методы:

namespace WorldGraphEditor
{
public enum EventType
{
OnPortEntered,
OnTransitionStarted,
OnSceneLoaded,
OnTransitionEnded,
}
}

Работа с асинхронными методами

static void RegisterAsyncHandler(EventType eventType, Func<CancellationToken, Task> func, int priority)

Параметры:

ИмяТипОписание
eventTypeEventTypeТип события, после которого должен начаться вызов асинхронных методов
funcFunc<CancellationToken, Task>Асинхронный метод, который принимает CancellationToken и возвращает Task
priorityintПриоритет вызова методов

static void UnregisterAsyncHandler(EventType eventType, Func<CancellationToken, Task> func)

Параметры:

ИмяТипОписание
eventTypeEventTypeТип события, для которого нужно удалить метод
funcFunc<CancellationToken, Task>Асинхронный метод, который принимает CancellationToken и возвращает Task

[!NOTE]
TransitionManager в методе OnDestroy автоматически вызывает CancellationTokenSource.Cancel(), а так же удаляет все зарегистрированные в нем методы.

Пример

using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;

namespace WorldGraphEditor.Examples
{
public class AsyncAfterSceneLoadedExample : MonoBehaviour
{
private readonly EventType _event = EventType.OnSceneLoaded;

private void Awake()
{
TransitionManager.RegisterAsyncHandler(Event, RunAfterSceneLoaded, 0);
}

private async Task RunAfterSceneLoaded(CancellationToken token)
{
await Awaitable.WaitForSecondsAsync(0.25f, token);
}

private void OnDisable()
{
TransitionManager.UnregisterAsyncHandler(Event, RunAfterSceneLoaded);
}
}
}

Дорожная карта

  • Поддержка нескольких сцен (вариаций) для одной ноды
  • Поддержка написания собственных реализаций TransitionManager
  • Кастомизация нод
  • Алгоритм для поиска пути через выбранные сцены

История обновлений

Версия 1.2.0

Нововведения:

  • Отображение превью сцен на нодах.
  • Оверлей Scene Inspector WGE отображающий информацию по текущей сцене.
  • Оверлей Screenshot Utility WGE для создания превью сцен.
  • Кнопки для переключения между сценами в Toolbar.
  • Окно настроек ассета в Edit > Project Settings > World Graph Editor.
  • Возможность вручную задать задержку между фазами перехода с помощью TransitionContext.
  • Валидатор сцен.
  • Поддержка собственных тестов для валидатора сцен.
  • Проверка на достижимость сцен в графе.
  • QOL функции в API.

Изменения:

  • Метод Refresh() в ITransitionComponent теперь принимает RefreshContext вместо TransitionManager.
  • Автоматический вызов Refresh() в ITransitionComponent при изменениях в сцене/иерархии/графе.
  • Убрана поддержка версии 2022.3.

Исправления:

  • Чтобы избежать возможной двусмысленности, названия портов с начальными или конечными пробелами теперь считаются недопустимыми. Например, "Left", "Left " и " Left" рассматриваются как одно и то же имя.
  • Различные незначительные улучшения и внутренние изменения.

Версия 1.1.0

Нововведения:

  • Методы RegisterAsyncHandler и UnregisterAsyncHandler теперь поддерживают CancellationToken для управления отменой задач
  • Добавлен способ переноса данных из версии 1.0 в 1.1+.

Изменения:

  • Изменен способ хранения данных в WorldGraphContainer.
  • Все EditorOnly методы перенесены из WorldGraphContainer в WorldGraphContainer.EditorData.
  • Методы RegisterAsyncHandler и UnregisterAsyncHandler принимающие Func<Task> больше не поддерживаются. Теперь требуется Func<CancellationToken, Task>.

Исправления:

  • Исправлена ошибка, из за которой при вызове TransitionManager.LoadScene() персонаж появлялся в позиции (0, 0, 0) а не в DefaultSpawnPoint.
  • Исправлена ошибка, при которой зарегистрированные асинхронные задачи не отменялись после выхода из Play Mode.

Версия 1.0.0

  • Первый релиз.