(1) Простой и удобный асинхронный фреймворк для UI
(2) Совместимость с различными системами управления ресурсами (Addressable, YooAssets и т.д.)
(3) Поддержка автоматического ссылочного механизма, при этом поля, отображаемые на панели Inspector, автоматически ссылаются на панель Hierarchy
(4) Поддержка вложенных UI (под-UI, под-под-UI и т.д.)
(5) Поддержка пользовательских шаблонов скриптов
(6) Поддержка управления уничтожением UI-панелей для более удобной оптимизации памяти
(7) Высокая расширяемость, позволяющая автоматически привязывать события с помощью пользовательских событий, например, автоматическая привязка события нажатия кнопки
Использование git URL
https://github.com/feifeid47/Unity-Async-UIFrame.git
Импорт unitypackage
Создайте预制体UIFrame, используя следующую структуру
Режим рендеринга Canvas должен быть установлен на Screen Space - Camera
RectTransform для PanelLayer и WindowLayer должны быть настроены на полный экран (левая, правая, верхняя и нижняя границы должны быть равны 0)
--UIFrame (RectTransform, Canvas, CanvasScaler, GraphicRaycaster, UIFrame)
------UICamera (Transform, Camera, AudioListener)
------PanelLayer (RectTransform)
------WindowLayer (RectTransform)
------EventSystem (Transform, EventSystem, StandaloneInputModule)
Создайте预制体UIFrame, используя следующую структуру
Режим рендеринга Canvas должен быть установлен на Screen Space - Camera
RectTransform для PanelLayer и WindowLayer должны быть настроены на полный экран (левая, правая, верхняя и нижняя границы должны быть равны 0)
--UIFrame (RectTransform, Canvas, CanvasScaler, GraphicRaycaster, UIFrame)
------UICamera (Transform, Camera, AudioListener)
------PanelLayer (RectTransform)
------WindowLayer (RectTransform)
------EventSystem (Transform, EventSystem, StandaloneInputModule)
```Инициализация
```C#
private void Awake()
{
// Регистрация события освобождения ресурсов
UIFrame.OnAssetRequest += OnAssetRequest;
UIFrame.OnAssetRelease += OnAssetRelease;
// Регистрация события "заклинившего" UI
// Событие "заклинившего" UI срабатывает, если время загрузки превышает 0.5 секунды
UIFrame.StuckTime = 0.5f;
UIFrame.OnStuckStart += OnStuckStart;
UIFrame.OnStuckEnd += OnStuckEnd;
}
``````csharp
// Событие запроса ресурсов, type - тип скрипта UI
// Можно использовать Addressables, YooAssets и другие системы управления ресурсами
private async Task<GameObject> OnAssetRequest(Type type)
{
if (!handles.ContainsKey(type))
{
var handle = Addressables.LoadAssetAsync<GameObject>(type.Name);
await handle.Task;
handles[type] = handle;
}
return handles[type].Result;
}
// Событие освобождения ресурсов
private void OnAssetRelease(Type type)
{
if(handles.ContainsKey(type))
{
handles[type].Release();
handles.Remove(type);
}
}
private void OnStuckStart()
{
// Если время инициализации UI слишком велико, UI "заклинивается", открываем панель с крутящимся значком
}
private void OnStuckEnd()
{
// Если UI больше не "заклинивается", закрываем панель с крутящимся значком
}
Создайте скрипт UI, унаследованный от UIComponent и прикрепите его к预制体,具有与脚本相同的名称。
public class UITestData : UIData
{
}
[UIPanel]
public class UITest : UIComponent<UITestData>
{
[SerializeField] private Image img;
[SerializeField] private Text content;
[SerializeField] private Button close;
// Вызывается при создании, выполняется один раз в течение всего жизненного цикла
protected override async Task OnCreate()
{
// Асинхронный запрос ресурсов
var completionSource = new TaskCompletionSource<Sprite>();
var handle = Resources.LoadAsync<Sprite>("sprite");
handle.completed += _ =>
{
completionSource.SetResult(handle.asset as Sprite);
};
img.sprite = await completionSource.Task;
}
// Привязка событий
protected override void OnBind()
{
close.onClick.AddListener(OnClose);
}
// Отвязка событий
protected override void OnUnbind()
{
close.onClick.RemoveListener(OnClose);
}
}
``` // Обновление
protected override async Task OnRefresh()
{
// Асинхронный запрос данных из сети
var completionSource = new TaskCompletionSource<string>();
using var request = UnityWebRequest.Get("http://xxxx");
request.SendWebRequest().completed += _ =>
{
completionSource.SetResult(request.downloadHandler.text);
};
var data = await completionSource.Task;
content.text = data;
}
// Вызывается при отображении
protected override void OnShow()
{
}
// Вызывается при скрытии
protected override void OnHide()
{
}
// Вызывается при удалении, выполняется один раз в течение всего жизненного цикла
protected override void OnDied()
{
} private void OnClose()
{
// Закрытие текущей панели
UIFrame.Hide(this);
}
}
Используйте атрибуты [UIPanel]
или [UIWindow]
для маркировки UI
Атрибут [UIPanel]
обычно используется для обозначения полноразмерных панелей, которые управляются стеком. Отображение следующей панели приводит к закрытию текущей панели, а скрытие текущей панели приводит к отображению предыдущей панели
Атрибут [UIWindow]
обычно используется для обозначения всплывающих окон, которые отображаются поверх панелей
Одно UI-окно не может одновременно быть панелью и окном```C#
// Отображение UI
UIFrame.Show(new TestUIData());
// Отображение под-UI
UIFrame.Show(UIBase uibase);
// Скрытие UI
UIFrame.Hide();
// Скрытие под-UI
UIFrame.Hide(UIBase uibase);
// Обновление UI
UIFrame.Refresh();
// Обновление под-UI
UIFrame.Refresh(UIBase uibase);
// Освобождение ресурсов
UIFrame.Release();
// Инстанцирование ресурсов UI
UIFrame.Instantiate(gameObject, parent);
// Уничт Yöndürme UI kaynakları
UIFrame.Destroy(gameObject);
// Немедленное уничтожение ресурсов UI
UIFrame.DestroyImmediate(gameObject);
# Лифт-цикл UIBaseВот последовательность выполнения при вызове UIFrame.Show для отображения следующего Panel
Взять `UITest` в качестве примера, `TestUI` наследуется от `UIComponent<T>`. При отображении `UITest` будут последовательно выполнены следующие шаги:
```
(1) UITest.OnCreate
(2) OnCreate всех компонентов, наследующихся от UIBase, в UITest
(3) UITest.OnRefresh
(4) OnRefresh всех активных объектов, наследующихся от UIBase, в UITest
(5) UITest.OnBind
(6) OnBind всех активных объектов, наследующихся от UIBase, в UITest
(7) UITest.OnShow
(8) OnShow всех активных объектов, наследующихся от UIBase, в UITest
```
При скрытии `TestUI` будут последовательно выполнены следующие шаги:
```
(1) OnUnbind всех активных объектов, наследующихся от UIBase, в UITest
(2) UITest.OnUnbind
(3) OnHide всех активных объектов, наследующихся от UIBase, в UITest
(4) UITest.OnHide
(5) OnDied всех компонентов, наследующихся от UIBase, в UITest
(6) UITest.OnDied
```
Метод `OnCreate` и жизненный цикл `OnDied` выполняются только один раз.
Только после выполнения `OnCreate` и `OnRefresh` объект становится активным, то есть метод `Awake` класса `MonoBehaviour` выполняется после `OnCreate` и `OnRefresh`.
Не рекомендуется использовать методы жизненного цикла `MonoBehaviour`.
Важно отметить, что привязка и отвязка событий UI должны быть выполнены в `OnBind` и `OnUnbind` соответственно, чтобы избежать множественного отклика в процессе асинхронной работы, что может привести к непредсказуемым ошибкам. В процессе асинхронной работы UI временно прекращает отклики. Если время отклика превышает `UIFrame.StuckTime`, будет сработано событие зависания.# Автоматическая ссылка
Сначала создайте `UIFrameSetting`, правый клик мыши -> Создать -> UIFrame -> UIFrameSetting
Вы можете переместить файл `UIFrameSetting` в другое место, не обязательно в директории Assets.
Включите `Auto Reference` в `UIFrameSetting`
Если вы хотите отключить автоматическую ссылку, просто выключите `Auto Reference` в `UIFrameSetting`
Пример:
```C#
[SerializeField] private UIRed uiRed;
[SerializeField] private UIBlue uiBlue;
[SerializeField] private Button btnRed;
[SerializeField] private Button btnBlue;
[SerializeField] private Button btnBack;
[SerializeField] private List<Image> listImg;
```
Необходимо изменить имя объекта в Hierarchy на имя поля (не учитывая регистр букв) и добавить к нему префикс @.
После изменения имени дополнительные действия не требуются, при сохранении Prefab значения из Hierarchy будут автоматически присвоены полям в Inspector.
Для полей типа List достаточно, чтобы имя родительского объекта совпадало с именем поля.
При включении автоматической ссылки, поля, которые участвуют в ссылке, будут заблокированы, и вы не сможете удалить их или изменить их значения.
``````# ПодUI
Иногда на одной панели может быть несколько подпанелей и некоторые элементы UI, и вы хотите, чтобы при отображении одного UI, подпанели и элементы UI были инициализированы и обновлены одновременно.
Например, `UITest` имеет два подUI: `UIRed` и `UIBlue`. Оба `UIRed` и `UIBlue` имеют компонент Text, который отображает Data = xxx.
При отображении `UITest` вы хотите инициализировать и обновлять `UIRed` и `UIBlue`, обновлять значение Data = xxx и иметь возможность открывать подUI через две кнопки на `UITest`, а также иметь кнопку закрытия на каждом подUI, чтобы закрыть его.

Структура панели `UITest` следующая:
`UITest` содержит скрипт `UITest`, ссылается на @UIRed, @UIBlue, @BtnRed, @BtnBlue.
`@UIRed` содержит скрипт `UIRed`, ссылается на @DataTxt, @BtnClose.
`@UIBlue` содержит скрипт `UIBlue`, ссылается на @DataTxt, @BtnClose.
В соответствии с жизненным циклом UIBase, при отображении `UITest`, все компоненты, наследующие от UIBase, будут выполнены одновременно, и выполнение будет происходить в определенном порядке, сначала выполняются методы родительского объекта, а затем методы дочерних объектов.```C#
[UIPanel]
public class UITest : UIBase
{
[SerializeField] private UIRed uiRed;
[SerializeField] private UIBlue uiBlue;
[SerializeField] private Button btnRed;
[SerializeField] private Button btnBlue;
[SerializeField] private Button btnBack;
protected override void OnBind()
{
btnRed.onClick.AddListener(OnBtnRed);
btnBlue.onClick.AddListener(OnBtnBlue);
btnBack.onClick.AddListener(OnBack);
}
protected override void OnUnbind()
{
btnRed.onClick.RemoveListener(OnBtnRed);
btnBlue.onClick.RemoveListener(OnBtnBlue);
btnBack.onClick.RemoveListener(OnBack);
}
private void OnBtnRed()
{
// Это правильный шаг для отображения подUI, неправильный шаг: UIFrame.Show<UIRed>(data);
var data = new UIRedData() { Content = "Это UIRed" };
UIFrame.Show(uiRed, data);
}
private void OnBtnBlue()
{
var data = new UIBlueData() { Content = "Это UIBlue" };
UIFrame.Show(uiBlue, data);
}
private void OnBack()
{
UIFrame.Hide(this);
}
}
```
```C#
public class UIRedData : UIData
{
public string Content;
}
public class UIRed : UIComponent<UIRedData>
{
[SerializeField] private Text dataTxt;
[SerializeField] private Button btnClose;
protected override Task OnRefresh()
{
dataTxt.text = $"Данные = {Data.Content}";
return Task.CompletedTask;
}
protected override void OnBind()
{
btnClose.onClick.AddListener(OnBtnClose);
}
protected override void OnUnbind()
{
btnClose.onClick.RemoveListener(OnBtnClose);
}
protected void OnBtnClose()
{
UIFrame.Hide(this);
}
}
```# Пользовательский шаблон скрипта
Сначала создайте `UIFrameSetting`, правый клик -> Создать -> UIFrame -> UIFrameSetting
Вы можете переместить `UIFrameSetting` в любое место, а не только в директорию Assets
По умолчанию шаблон скрипта находится в директории UIFrame/Editor/Resources, который можно изменить по необходимости
Перетащите файл шаблона (.txt) в `UIFrameSetting`
Имя файла заменит `#SCRIPTNAME#` в шаблоне файла

# Управление разрушением UI

Скрипты, наследующие от `UIComponent<T>`, будут иметь в Inspector панели свойство `Auto Destroy`
При активации `Auto Destroy`, объект будет уничтожен и освобождены используемые им ресурсы при невидимости UI
Эту переменную можно управлять в коде во время выполнения
```
Внимание: при активации Auto Destroy, при закрытии и повторном открытии панели будет вызван метод OnCreate
Рекомендация: для часто используемых UI отключите этот параметр для ускорения открытия UI. Для редко используемых UI или UI, использующих много памяти, активируйте этот параметр для оптимизации памяти
```
# Динамическое создание или разрушение UI gameObject
Если вы хотите динамически создавать UI gameObject во время выполнения, используйте следующие методы
```C#
UIFrame.Instantiate(gameObject, parent);
UIFrame.Destroy(gameObject);
UIFrame.DestroyImmediate(gameObject);
```
Использование `UIFrame.Instantiate` и `UIFrame.Destroy` для создания или разрушения объектов автоматически заполняет дерево отношений `UIBase`.
Это гарантирует, что динамически созданные или разрушенные объекты будут правильно контролироваться `UIFrame`.
``` public static void Отключить()
{
UIFrame.OnCreate -= OnCreate;
UIFrame.OnBind -= OnBind;
UIFrame.OnUnbind -= OnUnbind;
UIFrame.OnDied -= OnDied;
}``` private static void OnCreate(UIBase uibase)
{
var methods = uibase.GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
.Where(item => Attribute.IsDefined(item, typeof(UGUIButtonEventAttribute)));
binds[uibase] = new Dictionary<string, (Button, UnityAction)>();
var bind = binds[uibase];
var buttons = new Dictionary<string, Button>();
}```markdown
[UGUIButtonEvent]
protected void OnBtnRed()
{
var data = new UIRedData() { Content = "Это UIRed" };
UIFrame.Show(uiRed, data);
}
[UGUIButtonEvent]
protected void OnBtnBlue()
{
var data = new UIBlueData() { Content = "Это UIBlue" };
UIFrame.Show(uiBlue, data);
}
```
Таким образом, не требуется вручную добавлять события в методы `OnBind` и `OnUnbind` для `UIBase`.
Можно напрямую указать методы с атрибутами, и они будут автоматически привязываться и отвязываться, как показано ниже: предварительно загруженный объект `UITest` имеет дочерние объекты `@BtnRed`, `@BtnBlue`, `@BtnBack`.
```C#
[UIPanel]
public class UITest : UIBase
{
[SerializeField] private UIRed uiRed;
[SerializeField] private UIBlue uiBlue;
}
```
```C#
[UGUIButtonEvent]
protected void OnBtnRed()
{
var data = new UIRedData() { Content = "Это UIRed" };
UIFrame.Show(uiRed, data);
}
[UGUIButtonEvent]
protected void OnBtnBlue()
{
var data = new UIBlueData() { Content = "Это UIBlue" };
UIFrame.Show(uiBlue, data);
}
``` [UGUIButtonEvent]
protected void OnBtnBack()
{
UIFrame.Hide(this);
}
}
```
Другие события можно расширить, зарегистрировав `UIFrame.OnCreate`, `UIFrame.OnRefresh`, `UIFrame.OnBind`, `UIFrame.OnUnbind`, `UIFrame.OnShow`, `UIFrame.OnHide`, `UIFrame.OnDied`.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )