UnityScreenNavigator中文文档

此文档内容为 OpenAI GPT 翻译,不保证其准确性

Unity Screen Navigator

Unity Screen Navigator 是一个用于在 Unity 的 uGUI 中进行屏幕过渡、过渡动画、过渡历史堆栈以及屏幕生命周期管理的库。

演示

概述

功能

  • 您可以轻松灵活地创建页面、模态框、页签及其过渡效果。
  • 从加载到销毁,管理屏幕的生命周期和内存。
  • 使用动画器实现复杂屏幕过渡动画的分离工作流程。
  • 精心分离的库,没有额外的功能(例如 GUI 库、状态机)。
  • 还包括历史堆栈和过渡期间的点击防护等标准功能。

演示

您可以按照以下步骤播放演示场景。

  1. 克隆此存储库。
  2. 打开并播放以下场景。

请注意,此演示中使用的某些图像来自以下免费内容。有关更多信息,包括版权信息,请参阅以下网站。

设置

系统需求

  • Unity 2019.4 或更高版本
  • uGUI (不支持 UIElements)

安装

  1. 从窗口(Window)菜单中打开包管理器(Package Manager)
  2. 点击”+”按钮 > 从 git URL 添加包
  3. 输入以下内容进行安装

Package Manager

或者,打开 Packages/manifest.json 文件,并在 dependencies 块中添加以下内容。

{
"dependencies": {
"com.harumak.unityscreennavigator": "https://github.com/Haruma-K/UnityScreenNavigator.git?path=/Assets/UnityScreenNavigator"
}
}

如果您想设置目标版本,请按照以下方式进行指定。

基本屏幕过渡

屏幕和过渡的概念

Unity Screen Navigator 将屏幕分为三种类型:「页面」(Page)、「模态」(Modal)和「表格」(Sheet)。

「页面」是按顺序过渡的屏幕。
例如,当从页面 A 过渡到页面 B 时,页面 A 将被堆叠在历史记录中。
当从页面 B 返回时,页面 A 将以其状态完好的形式重新显示。

Demo

「模态」是以窗口形式堆叠的屏幕。
当显示模态屏幕时,除前景模态屏幕外,所有交互都将被阻止。

Demo

而「表格」用于类似选项卡的 GUI。
不管理历史记录,只显示一个活动屏幕。

Demo

这些屏幕可以嵌套。
而且,可以自由指定每个屏幕的区域(不一定是整个窗口)。

Demo

创建页面和过渡

要创建页面过渡,请首先将「页面容器」(Page Container)组件附加到 Canvas 下的一个 GameObject 上。
页面将被显示到适应它的大小,因此请调整大小。

然后,将 Page组件附加到页面视图的根 GameObject 上。
将此 GameObject 放置在 Resources 文件夹下,使用任意名称。

调用 PageContainer.Push()并传入资源路径以显示页面。
以下是一个示例,将页面推送到 Assets/Resources/ExamplePage.prefab中的页面:

PageContainer pageContainer;

// 推送名为 "ExamplePage" 的页面。
var handle = pageContainer.Push("ExamplePage", true);

// 等待过渡完成。
yield return handle;
//await handle.Task; // 也可以使用 await。
//handle.OnTerminate += () => { }; // 也可以使用回调函数。

还有,使用 PageContainer.Pop()来取消当前页面并显示前一个页面。

PageContainer pageContainer;

// 推送名为 "ExamplePage" 的页面。
var handle = pageContainer.Push("ExamplePage", true);

// 等待过渡完成。
yield return handle;
//await handle.Task; // 也可以使用 await。
//handle.OnTerminate += () => { }; // 也可以使用回调函数。

如果你想在调用 Pop()时跳过某个特定页面,你可以通过使用可选参数来禁用页面堆叠历史

创建模态框和过渡

要创建模态框过渡效果,首先在 Canvas 下的一个游戏对象上附加“Modal Container”组件。
通常,模态框被设计为使用其背景覆盖整个窗口并阻止点击。
因此,游戏对象的 RectTransform 的大小应该基本上设置为与窗口大小相匹配。

接下来,将“Modal”组件附加到模态框视图的根游戏对象上。
这个根游戏对象会调整到适应“Modal Container”的大小。
所以,如果你想要创建带有边距的模态框,可以创建一个较小大小的子游戏对象,并在其中创建内容。

Demo

将此游戏对象放在任意命名的资源文件夹下。

并使用资源路径调用ModalContainer.Push()来显示页面。
以下是将模态框放置在Assets/Resources/ExampleModal.prefab中进行推送的示例代码。

ModalContainer modalContainer;

// 推送名为"ExampleModal"的模态框。
var handle = modalContainer.Push("ExampleModal", true);

// 等待过渡完成。
yield return handle;
//await handle.Task; // 也可以使用await。
//handle.OnTerminate += () => { }; // 也可以使用回调函数。

另外,使用ModalContainer.Pop()来取消当前模态框并显示上一个模态框。

ModalContainer modalContainer;

// 弹出当前活动的模态框。
var handle = modalContainer.Pop(true);

// 等待过渡完成。
yield return handle;

请注意,你可以根据需要更改模态框的背景

创建表和过渡

要创建表过渡效果,首先在 Canvas 下的一个游戏对象上附加“Sheet Container”组件。
表将显示在其中,所以请调整大小。

接下来,将“Sheet”组件附加到表视图的根游戏对象上。
将此游戏对象放在任意命名的资源文件夹下。

使用资源路径调用SheetContainer.Register()来创建表。
创建后,可以通过调用SheetContainer.Show()来更改活动的表。
此时,如果已经存在活动的表,它将被停用。

以下是显示放置在Assets/Resources/ExampleSheet.prefab中的表的示例代码。

SheetContainer sheetContainer;

// 实例化名为"ExampleSheet"的表
var registerHandle = sheetContainer.Register("ExampleSheet");
yield return registerHandle;

// 显示名为"ExampleSheet"的表
var showHandle = sheetContainer.Show("ExampleSheet", false);
yield return showHandle;

请注意,当使用Register()方法实例化具有相同资源键的多个表时,无法通过资源键来保证表实例的唯一性。
在这种情况下,可以使用表 ID 而不是资源键,如下所示。

SheetContainer sheetContainer;

// 实例化名为"ExampleSheet"的表并获取表ID。
var sheetId = 0;
var registerHandle = sheetContainer.Register("ExampleSheet", x =>
{
sheetId = x.sheetId;
});
yield return registerHandle;

// 显示具有sheetId的表。
var showHandle = sheetContainer.Show(sheetId, false);
yield return showHandle;

另外,如果想隐藏活动的表而不是切换表,请使用Hide()方法。

SheetContainer sheetContainer;

// 隐藏活动的表。
var handle = sheetContainer.Hide(true);

// 等待过渡完成。
yield return handle;

如何等待过渡完成

每个过渡方法都会返回AsyncProcessHandle作为返回值。
使用这个对象,你可以等待过渡过程完成。

你可以使用协程、异步方法和回调来实现。
要在协程中等待,使用yield return,如下所示。

yield return pageContainer.Push("ExamplePage", true);

要在异步方法中等待,像下面这样使用await等待AsyncProcessHandle.Task

await pageContainer.Push("ExamplePage", true).Task;

如果你想使用回调函数,可以使用AsyncProcessHandle.OnTerminate

pageContainer.Push("ExamplePage", true).OnTerminate += () => { };

使用静态方法获取容器

每个容器(PageContainer/ModalContainer/SheetContainer)都有用于获取实例的静态方法。

使用以下的Container.Of()方法,你可以获取与给定的 Transform 或 RectTransform 关联的最近父级上的容器。

var pageContainer = PageContainer.Of(transform);
var modalContainer = ModalContainer.Of(transform);
var sheetContainer = SheetContainer.Of(transform);

此外,你还可以在容器的检视器中设置Name属性,以便通过名称获取容器。
在这种情况下,使用Container.Find()方法,如下所示。

var pageContainer = PageContainer.Find("SomePageContainer");
var modalContainer = ModalContainer.Find("SomeModalContainer");
var sheetContainer = SheetContainer.Find("SomeSheetContainer");

##屏幕过渡动画

设置通用的过渡动画

默认情况下,每种屏幕类型都设置了一个标准的过渡动画。

你可以创建一个继承自TransitionAnimationObject的类来创建自定义的过渡动画。
这个类有一个属性和一些方法来定义动画的行为。

// 持续时间(秒)。
public abstract float Duration { get; }

// 初始化。
public abstract void Setup();

// 在此时间点定义状态。
public abstract void SetTime(float time);

有关实际实现,请参考SimpleTransitionAnimationObject

然后,实例化这个 Scriptable Object,并将其分配给UnityScreenNavigatorSettings
您可以通过Assets > Create > Screen Navigator Settings创建UnityScreenNavigatorSettings

设置每个屏幕的过渡动画

您还可以为每个屏幕设置不同的动画。

每个页面(Page)、模态(Modal)和面板(Sheet)组件都有Animation Container属性。
您可以将过渡动画设置给它。

您可以通过将Asset Type设置为Scriptable Object并将之前描述的TransitionAnimationObject分配给Animation Object,来更改此屏幕的过渡动画。

此外,您也可以使用 MonoBehaviour 代替 ScriptableObject。
在这种情况下,首先创建一个继承自TransitionAnimationBehaviour的类。
有关实际实现,请参考SimpleTransitionAnimationBehaviour

然后,附加此组件,并将Asset Type设置为Mono Behaviour,并将引用分配给Animation Behaviour

根据伙伴屏幕更改过渡动画

例如,当屏幕 A 进入并且屏幕 B 退出时,屏幕 B 被称为屏幕 A 的”伙伴屏幕”。

如果您在下图所示的属性中输入伙伴屏幕的名称,则只有当此名称与伙伴屏幕名称匹配时,过渡动画才会应用。

默认情况下,使用预制件的名称作为屏幕名称。
如果您想要显式命名,请取消选中”Use Prefab Name As Identifier”并在”Identifier”属性中输入一个名称。

此外,可以在”Partner Page Identifier Regex”属性中使用正则表达式。
如果设置了多个动画,则它们将按照从顶部开始的顺序进行评估。

屏幕过渡动画和绘制顺序

在具有伙伴屏幕的屏幕的过渡动画中,绘制顺序可能很重要。
例如,一种屏幕覆盖伙伴屏幕的动画。

如果您想要控制绘制顺序,请使用”Rendering Order”属性。

在屏幕过渡期间,屏幕将按照此值减少的顺序进行绘制。

请注意,模态窗口没有”Rendering Order”属性,因为最新的模态窗口始终会显示在前面。

简单创建过渡动画

您可以使用SimpleTransitionAnimationObject作为简单的过渡动画实现工具。

可以通过Assets > Create > Screen Navigator > Simple Transition Animation创建它。
然后,将生成类似下方所示的 ScriptableObject,您可以从检视器中设置动画。

您还可以使用SimpleTransitionAnimationBehaviour将其作为这种实现的 MonoBehaviour。
通过直接将其附加到游戏对象上来使用它。

以下是各个属性的说明。

属性名描述
Delay动画开始之前的延迟时间(秒)。
Duration动画持续时间(秒)。
Ease Type缓动函数的类型。
Before Alignment过渡之前相对于容器的位置。
Before Scale过渡之前的缩放比例。
Before Alpha过渡之前的透明度。
After Alignment过渡之后相对于容器的位置。
After Scale过渡之后的缩放比例。
After Alpha过渡之后的透明度。

通过伙伴屏幕实现交互动画

您还可以创建引用伙伴屏幕状态的动画。
在以下示例中,前一个模态窗口的图像在平滑过渡到下一个模态窗口时被放大。

要实现这一点,首先创建一个从TransitionAnimationObjectTransitionAnimationBehaviour派生的类。
然后,通过参考PartnerRectTransform属性来获取伙伴屏幕。
如果伙伴屏幕不存在,PartnerRectTransform将为 null。

有关实际实现,请参考CharacterImageModalTransitionAnimation中的示例。

使用时间轴创建动画

您可以使用时间轴(Timeline)来创建过渡动画。
对于复杂的过渡动画,建议使用时间轴。

要实现这一点,首先将Timeline Transition Animation Behaviour附加到一个 GameObject 上。
然后将Playable DirectorTimeline Asset分配给属性。

Playable DirectorPlay On Awake属性需要取消选中。

最后,将此Timeline Transition Animation Behaviour分配给Animation Container

此外,我推荐使用UnityUIPlayables来创建带有 uGUI 的动画。

## 生命周期事件

页面的生命周期事件

通过在派生自 Page 类的子类中重写以下方法,您可以编写与页面生命周期相关的过程。

using System.Collections;
using UnityScreenNavigator.Runtime.Core.Page;

public class SomePage : Page
{
// 在加载此页面后调用。
public override IEnumerator Initialize() { yield break; }
// 在释放此页面前调用。
public override IEnumerator Cleanup() { yield break; }
// 在通过 Push 过渡显示此页面前调用。
public override IEnumerator WillPushEnter() { yield break; }
// 在通过 Push 过渡显示此页面后立即调用。
public override void DidPushEnter() { }
// 在通过 Push 过渡隐藏此页面前调用。
public override IEnumerator WillPushExit() { yield break; }
// 在通过 Push 过渡隐藏此页面后立即调用。
public override void DidPushExit() { }
// 在通过 Pop 过渡显示此页面前调用。
public override IEnumerator WillPopEnter() { yield break; }
// 在通过 Pop 过渡显示此页面后立即调用。
public override void DidPopEnter() { }
// 在通过 Pop 过渡隐藏此页面前调用。
public override IEnumerator WillPopExit() { yield break; }
// 在通过 Pop 过渡隐藏此页面后立即调用。
public override void DidPopExit() { }
}

您也可以通过以下方式在外部注册生命周期事件,使用 Page.AddLifecycleEvents()

// IPageLifecycleEvent 是上述生命周期事件的接口。
// 您可以通过第二个参数指定执行优先级。
// 小于 0:在 Page 生命周期事件之前执行。
// 大于 0:在 Page 生命周期事件之后执行。
IPageLifecycleEvent lifecycleEventImpl;
Page page;
page.AddLifecycleEvent(lifecycleEventImpl, -1);

// 也可以仅注册部分生命周期事件,如下所示。
IEnumerator OnWillPushEnter()
{
// 某些代码。
yield break;
}
page.AddLifecycleEvent(onWillPushEnter: OnWillPushEnter);

此外,您还可以通过将实现了 IPageContainerCallbackReceiver 接口的对象传递给 PageContainer.AddCallbackReceiver(),从容器中挂钩过渡事件。

public interface IPageContainerCallbackReceiver
{
// 在执行 Push 过渡之前调用。
void BeforePush(Page enterPage, Page exitPage);
// 在执行 Push 过渡之后调用。
void AfterPush(Page enterPage, Page exitPage);
// 在执行 Pop 过渡之前调用。
void BeforePop(Page enterPage, Page exitPage);
// 在执行 Pop 过渡之后调用。
void AfterPop(Page enterPage, Page exitPage);
}

请注意,如果您将 IPageContainerCallbackReceiver 实现为 MonoBehaviour 并将其附加到页面的 GameObject 上,
它将自动注册到 PageContainer 中,而无需调用 PageContainer.AddCallbackReceiver()

模态框的生命周期事件

通过在派生自 Modal 类的子类中重写以下方法,您可以编写与模态框生命周期相关的过程。

using System.Collections;
using UnityScreenNavigator.Runtime.Core.Modal;

public class SomeModal : Modal
{
// 在加载此模态框后调用。
public override IEnumerator Initialize() { yield break; }
// 在释放此模态框前调用。
public override IEnumerator Cleanup() { yield break; }
// 在通过 Push 过渡显示此模态框前调用。
public override IEnumerator WillPushEnter() { yield break; }
// 在通过 Push 过渡显示此模态框后立即调用。
public override void DidPushEnter() { }
// 在通过 Push 过渡隐藏此模态框前调用。
public override IEnumerator WillPushExit() { yield break; }
// 在通过 Push 过渡隐藏此模态框后立即调用。
public override void DidPushExit() { }
// 在通过 Pop 过渡显示此模态框前调用。
public override IEnumerator WillPopEnter() { yield break; }
// 在通过 Pop 过渡显示此模态框后立即调用。
public override void DidPopEnter() { }
// 在通过 Pop 过渡隐藏此模态框前调用。
public override IEnumerator WillPopExit() { yield break; }
// 在通过 Pop 过渡隐藏此模态框后立即调用。
public override void DidPopExit() { }
}

您也可以通过以下方式在外部注册生命周期事件,使用 Modal.AddLifecycleEvents()

// IModalLifecycleEvent 是上述生命周期事件的接口。
// 您可以通过第二个参数指定执行优先级。
// 小于 0:在 Modal 生命周期事件之前执行。
// 大于 0:在 Modal 生命周期事件之后执行。
IModalLifecycleEvent lifecycleEventImpl;
Modal modal;
Modal.AddLifecycleEvent(lifecycleEventImpl, -1);

// 也可以仅注册部分生命周期事件,如下所示。
IEnumerator OnWillPushEnter()
{
// 某些代码。
yield break;
}
modal.AddLifecycleEvent(onWillPushEnter: OnWillPushEnter);

此外,您还可以通过将实现了 IModalContainerCallbackReceiver 接口的对象传递给 ModalContainer.AddCallbackReceiver(),从容器中挂钩过渡事件。

public interface IModalContainerCallbackReceiver
{
// 在执行 Push 过渡之前调用。
void BeforePush(Modal enterModal, Modal exitModal);
// 在执行 Push 过渡之后调用。
void AfterPush(Modal enterModal, Modal exitModal);
// 在执行 Pop 过渡之前调用。
void BeforePop(Modal enterModal, Modal exitModal);
// 在执行 Pop 过渡之后调用。
void AfterPop(Modal enterModal, Modal exitModal);
}

请注意,如果您将 IModalContainerCallbackReceiver 实现为 MonoBehaviour 并将其附加到页面的 GameObject 上,
它将自动注册到 ModalContainer 中,而无需调用 ModalContainer.AddCallbackReceiver()

抽屉的生命周期事件

通过在派生自 Sheet 类的子类中重写以下方法,您可以编写与抽屉生命周期相关的过程。

using System.Collections;
using UnityScreenNavigator.Runtime.Core.Sheet;

public class SomeSheet : Sheet
{
// 在加载此抽屉后调用。
public override IEnumerator Initialize() { yield break; }
// 在释放此抽屉前调用。
public override IEnumerator Cleanup() { yield break; }
// 在显示此抽屉前调用。
public override IEnumerator WillEnter() { yield break; }
// 在显示此抽屉后立即调用。
public override void DidEnter() { }
// 在隐藏此抽屉前调用。
public override IEnumerator WillExit() { yield break; }
// 在隐藏此抽屉后立即调用。
public override void DidExit() { }
}

您也可以通过以下方式在外部注册生命周期事件,使用 Sheet.AddLifecycleEvents()

// ISheetLifecycleEvent 是上述生命周期事件的接口。
// 您可以通过第二个参数指定执行优先级。
// 小于 0:在 Sheet 生命周期事件之前执行。
// 大于 0:在 Sheet 生命周期事件之后执行。
ISheetLifecycleEvent lifecycleEventImpl;
Sheet sheet;
Sheet.AddLifecycleEvent(lifecycleEventImpl, -1);

// 也可以仅注册部分生命周期事件,如下所示。
IEnumerator OnWillEnter()
{
// 某些代码。
yield break;
}
sheet.AddLifecycleEvent(onWillEnter: OnWillEnter);

此外,您还可以通过将实现了 ISheetContainerCallbackReceiver 接口的对象传递给 SheetContainer.AddCallbackReceiver(),从容器中挂钩过渡事件。

public interface ISheetContainerCallbackReceiver
{
// 在执行 Show 过渡之前调用
void BeforeShow(Sheet enterSheet, Sheet exitSheet);
// 在执行 Show 过渡之后调用
void AfterShow(Sheet enterSheet, Sheet exitSheet);
// 在执行 Hide 过渡之前调用
void BeforeHide(Sheet exitSheet);
// 在执行 Hide 过渡之后调用
void AfterHide(Sheet exitSheet);
}

请注意,如果您将 ISheetContainerCallbackReceiver 实现为 MonoBehaviour 并将其附加到页面的 GameObject 上,
它将在不调用 SheetContainer.AddCallbackReceiver() 的情况下被注册到 SheetContainer

使用异步方法而不是协程

您也可以使用异步方法而不是协程来定义生命周期事件,如下所示。

using System.Threading.Tasks;
using UnityScreenNavigator.Runtime.Core.Page;

public class SomePage : Page
{
// 使用异步方法来定义生命周期事件
public override async Task Initialize()
{
await Task.Delay(100);
}
}

要使用异步方法,请按以下步骤添加 Scripting Define Symbols

  • 玩家设置 > 其他设置
  • USN_USE_ASYNC_METHODS 添加到 Scripting Define Symbols

请注意,Scripting Define Symbols 需要设置为所有平台。

加载屏幕资源

更改屏幕资源的加载方法

如上所述,默认情况下,每个屏幕的资源以 Prefab 形式放置在 Resources 文件夹中。

如果要更改加载方法,请首先创建从 AssetLoaderObject 派生的 Scriptable Object。
AssetLoaderObjectIAssetLoader 的一种实现,并具有以下方法。

// 加载由键指示的资源。
public abstract AssetLoadHandle<T> Load<T>(string key) where T : Object;

// 异步加载由键指示的资源。
public abstract AssetLoadHandle<T> LoadAsync<T>(string key) where T : Object;

// 释放由句柄指示的资源。
public abstract void Release(AssetLoadHandle handle);

请参考 ResourcesAssetLoader 来实现实际操作。
创建完成后,将其实例分配给 UnityScreenNavigatorSettingsAssetLoader 属性。

您可以从 Assets > Create > Screen Navigator Settings 创建 UnityScreenNavigatorSettings

您还可以通过设置每个 ContainerAssetLoader 属性来为每个容器设置 IAssetLoader

使用地址资产系统进行加载

默认情况下,为地址资产系统提供了一个 IAssetLoader 实现。如果您想使用地址加载每个屏幕,请按照以下步骤进行设置。

  1. 选择 Assets > Create > Resource Loader > Addressable Asset Loader
  2. 将在步骤 1 中创建的 ScriptableObject 分配给 UnityScreenNavigatorSettingsAssetLoader 属性。

同步加载

您可以将 loadAsync 参数设置为 false,以在每个容器的过渡方法中同步加载屏幕。
例如,PageContainer.Push() 应写为以下形式。

PageContainer container;

// 同步加载
var handle = container.Push("FooPage", true, loadAsync: false);

// 等待过渡动画结束
yield return handle;

此外,您还可以使用 onLoad 回调将其在与过渡方法调用相同的帧中进行初始化。

PageContainer container;

// 同步加载,并在加载后接收回调
var handle = container.Push("FooPage", true, loadAsync: false, onLoad: x =>
{
// 初始化页面(在 Push 的同一帧中调用)
x.page.Setup();
});

// 等待过渡动画结束
yield return handle;

请注意,如果您使用 AddressableAssetLoader 并进行同步加载,则需要 Addressables 1.17.4 或更高版本。
此外,由于 Addressable 的规范,性能注意事项也很重要。

预加载

页面和模态框仅在请求屏幕过渡时加载。
当加载大型资源时,可能需要较长的时间来加载,从而阻止平滑的过渡。

在这种情况下,预加载资源会非常有用。
以下是使用 PageContainer 进行预加载的示例。

const string pageName = "FooPage";
PageContainer container;

// 预加载 FooPage
var preloadHandle = container.Preload(pageName);

// 等待预加载结束
yield return preloadHandle;

// 因为 FooPage 已经预加载,所以可以平滑过渡
container.Push(pageName, true);

// 释放预加载的 FooPage
container.ReleasePreloaded(pageName);

请参考演示中的 HomePage 来了解实际实现。当初始化 Home 页面时,Shop 页面也会同时加载和销毁。

其他特性

一次性弹出多个屏幕

PageContainerModalContainer 中,您可以一次性弹出多个屏幕。
要做到这一点,请在 PageContainer.Pop()ModalContainer.Pop() 的第二个参数中指定要弹出的屏幕数。

PageContainer pageContainer;
pageContainer.Pop(true, 2);

ModalContainer modalContainer;
modalContainer.Pop(true, 2);

您还可以指定目标 PageIDModalID
可以使用 Push()onLoad 回调来获取 PageIDModalID,如下所示。

PageContainer pageContainer;
pageContainer.Push("fooPage", true, onLoad: x =>
{
var pageId = x.pageId;
});

ModalContainer modalContainer;
modalContainer.Push("fooModal", true, onLoad: x =>
{
var modalId = x.modalId;
});

此外,您可以通过指定 Push()pageIdmodalId 参数来指定任何 ID。

PageContainer pageContainer;
pageContainer.Push("fooPage", true, pageId: "MyPageID");

ModalContainer modalContainer;
modalContainer.Push("fooModal", true, modalId: "MyModalID");

此外,在同时弹出多个页面或模态框时,被跳过的页面或模态框的生命周期事件(跳转之前和之后)将不会被调用,仅会调用销毁前的事件。

PageContainer 中,被跳过的页面的过渡动画将不会播放。

ModalContainer 中,被跳过的模态框关闭时的过渡动画将会同时播放。

不将页面堆叠在历史记录中

有些页面在回退过渡时想要跳过,例如加载屏幕。

在这种情况下,您可以在 PageContainer.Push() 方法中将可选参数 stack 设置为 false,以防止页面被堆叠在历史记录中。
当过渡到下一个页面时,该页面的实例将被销毁,从而在后退时被跳过。

PageContainer container;

// 过渡到 FooPage,不将其堆叠在历史记录中
yield return container.Push("FooPage", true, stack: false);

// 过渡到 BarPage,FooPage 将被销毁
yield return container.Push("BarPage", true);

// 在 Pop 时,不会返回到 FooPage,而是返回到它之前的页面
yield return container.Pop(true);

请参考演示中的 TopPage了解实际实现方式。
在不将其堆叠在历史记录中的情况下过渡到加载页面。

更改模态框的背景

默认情况下,模态框的背景设置为一个黑色半透明的屏幕。
您可以在设置中更改此设置。

首先,将 Modal Backdrop 组件附加到模态框背景视图,并将其制作成预制件。

然后,将此预制件分配为模态框的背景。
要更改整个应用程序的模态框背景,请将其分配给 UnityScreenNavigatorSettings 中的 Modal Backdrop Prefab

您可以通过 Assets > Create > Screen Navigator Settings 创建 UnityScreenNavigatorSettings

您还可以通过将预制件分配给 Modal ContainerOverride Backdrop Prefab,为每个 Modal Container 设置特定的背景。

单击背景关闭活动的模态框

默认情况下,背景不可点击。
如果您希望在单击背景时关闭活动的模态框,请首先按照上述步骤更改背景。
然后,勾选 Modal Backdrop 组件的 Close Modal When Clicked 选项。

在过渡期间启用交互

从过渡开始到结束的整个过程中,禁用屏幕上的所有容器的交互,例如点击屏幕。

您可以通过更改 UnityScreenNavigatorSettingsEnable Interaction In TransitionControl Interactions Of All Containers 属性来更改设置。
默认情况下,Enable Interaction In TransitionfalseControl Interactions Of All Containerstrue

要在过渡期间启用交互,请将 Enable Interaction In Transition 设置为 true
如果只想为当前正在过渡中的容器禁用交互,请将 Enable Interaction In Transition 保持为 false,并将 Control Interactions Of All Containers 设置为 false

您可以通过 Assets > Create > Screen Navigator Settings 创建 UnityScreenNavigatorSettings

但是,在一个容器正在过渡时无法过渡到其他屏幕。
因此,如果启用交互,请适时控制过渡的时机。

禁用容器的遮罩

默认情况下,容器外部的屏幕部分将被遮罩。
如果要显示容器外的屏幕,请取消容器的 GameObject 上附加的 Rect Mask 2D 组件的勾选。

获取动画播放信息

您可以从 PageModalSheet 类的以下属性获取当前播放的过渡动画的信息。

属性名描述
IsTransitioning是否正在过渡中。
TransitionAnimationType过渡动画的类型。如果不在过渡中,则返回 null。
TransitionAnimationProgress过渡动画的进度。
TransitionAnimationProgressChanged当过渡动画的进度发生变化时触发的事件。

在加载屏幕时使用预加载的 Prefab 实例

PreloadedAssetLoaderObject 允许您直接加载预加载的 Prefab 实例,而不是在加载屏幕时使用资源或 Addressables。您可以通过从 Assets > Create > Resource Loader > Preloaded Asset Loader 创建可脚本化对象,并在其中输入键和 Prefab 来使用它,如下所示。

我还为运行时提供了 PreloadedAssetLoader 的实现。

常见问题

如何将每个屏幕制作为场景而不是 Prefab

您可以通过实现 AssetLoader 来加载放置在场景中的屏幕。
实现 IAssetLoader 来加载包含所请求屏幕的场景文件,并返回屏幕的 GameObject。
有关详细信息,请参阅 更改屏幕资源的加载方法

如何分离视图和逻辑

我编写了以下博文,以演示该概念和实现方法。

https://light11.hatenadiary.com/entry/2022/01/11/193925
(抱歉,仅提供日语)

如何向每个屏幕传递数据

首先,以示例为例,在加载完成时向屏幕传递数据,如下所示。

https://github.com/Haruma-K/UnityScreenNavigator/blob/8a115b1b25ac1d9fcf4b1ab6d5f2c1cd1d915ee5/Assets/Demo/Scripts/CharacterModal.cs#L91

但是,还有许多其他可能的传递数据的方法。
例如,可能有一种情况,您希望使用 DI 容器来设置数据。
因此,本库的策略不是实现和强制执行特定的方法。

如何重用弹出的页面或模态

弹出的页面和模态会立即销毁,无法重用。

对于重用的需求本质上可以分为以下两种类型。

  1. 不希望每次加载屏幕资源
  2. 希望保留屏幕的状态

其中,加载时间的问题可以通过 预加载 来解决。
至于状态保留,从可维护性的角度来看,状态和视图应该解耦,以便可以重构。

此外,从可用性的角度来看,应该保留的是 “Tab” 转换。
在本库中,使用 “Sheet” 来实现选项卡时,状态始终保持不变。
有关详细信息,请参阅 创建 Sheet 和转换

如果可重用,用户需要自己管理生命周期。
换句话说,当不再需要时,用户必须调用 Cleanup 方法来销毁实例和清理内存。

许可证

本软件在 MIT 许可下发布。您可以在许可范围内自由使用它。但使用时必须包含以下版权和许可声明。