UDN
Search public documentation:

UserInterfaceOverviewCH
日本語訳
한국어

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

UE3 主页 > 用户界面 & HUD > 用户界面系统概述

用户界面系统概述


UI 系统及其功能不再受支持。请参阅 Scaleform GFx 文档了解有关当前支持的用户界面系统的信息。

概述


本文档描述了 UI 系统的主要功能。UI 系统的设计分为两部分 – UI 系统和UI编辑器。

UI 系统


UI 系统是由美工人员驱动的系统,在这里美工人员为 UI 控制创建布局,会将这些控制绑定到游戏数据上,然后通过创建脚本事件和动作来在数据上执行各种操作。UI 系统有 3 个主要组件构成– 数据仓库、展示外观、交互作用。数据源组件展现了 UI 是如何和游戏数据进行交互的;展示外观组件展现了 UI 是如何把那些数据显示给用户的;交互作用组件显示了 UI 系统如何处理事件(用户生成的事件和系统生成的事件)。以下将会对这三个组件进行更加详细的描述。

数据存储

数据存储是 UI 如何从游戏中获得数据以及如何把数据的改变反映到游戏中的方法。一个数据存储可以是永久的,在这种情况下它将会被注册并被附加到 UI 控制器对象上,并且所有的控件都可以使用它;或者它也可以是临时的,在这种情况下它被附加到当前的页面上,并且仅那个页面包含的控件可以使用它。尽管无论数据存储提供的数据什么类型时,用于绑定和从数据存储中获取数据的界面是一样的,但是数据存储可以为 UI 提供任何类型的数据。

永久数据存储可能会跟踪信息,比如游戏类型以及人物资源或状态数据,然而基于每个页面的数据存储可以跟踪这样的信息,比如输入到页面中的某个控件的值的信息、或者关于未解决的连接的状态信息。数据存储可以提供静态信息,比如所有游戏类型或任务资源数据的名称,也可以提供动态信息,比如当前的游戏类型的名称。

UI 也是用了几个特殊的类型的数据存储。当前激活的 UISkin 包含了当前页面可用的风格列表,控件可以通过‘Styles(风格)’标签来引用这些风格。‘Attributes(属性)’数据仓库是一个虚拟数据仓库,它可以修改 UIString 的文本的风格(比如粗体、斜体等)。

数据存储功能

  • 负责向控件提供数据,从控件接受数据的改变,如果数据是可应用的,把数据传递到正确的位置。
  • 通过基于类的设计可以访问任意数据类型。
  • 可以和底层的数据源进行实时交互,比如,当游戏状态值发生改变时,立即传递这些改变。
  • 当向数据传递改变时,可以进行延迟处理、缓冲处理或批处理。

数据存储类别

数据存储有 4 种主要的类别。

  1. Game State(游戏状态) - 跟踪关于当前游戏状态的所有数据,比如玩家、目标、剩余时间、当前分数等。游戏数据存储可以是嵌套的,因为一个 GameState(游戏状态)数据存储可以包含到其他游戏状态数据存储的引用。这对于隔离和特定玩家相关的武器数据存储是有用的。游戏数据存储进一步分为两部分:
    • Game state data providers(游戏状态数据提供者): 提供了关于数据源的特定实例的状态和静态数据,比如一个玩家、武器、拾取物或游戏目标。数据提供者一般不能被 UI 直接引用。反之,它们一般通过或游戏状态数据存储访问,比如和自己的玩家相关的游戏状态数据仓库或者当前的游戏信息实例。
    • Game state data stores(游戏状态数据存储): 作为游戏和 UI 之间的第一层。每个数据存储包含一组游戏状态数据提供者,它为游戏对象实例提供数据。
  2. Game Settings(游戏设置) -为 UI 提供到特定游戏或全局可配置设置的访问权。游戏设置数据存储可以暴露这样的数据,比如游戏类型的配置的 MaxPlayers、GoalScore 等,或者针对特定用户的数据,比如用户的可配置的按钮布局。游戏设置数据存储也负责把用户的选项传递到适当的永久位置,以便当播放游戏时,游戏性代码可以使用这些值。
  3. Remote Data(远程数据) -为 UI 提供到从网络上的远程机器接受的数据的访问权。这种类型的数据存储的一个应用便是服务器浏览器,在那里,关于网络游戏会话的游戏和玩家数据从主要的服务器获得。
  4. Game Resources(游戏资源) - 跟踪游戏中可用资源的所有信息,比如游戏模式、武器选择、人物选择、地图等。

引用数据存储

每种数据存储有一个唯一的标志,它使得 UI 可以识别那个数据仓库。关于数据存储标签和真正的数据存储是如何相关联的详细信息一般由需求或那种数据类型设计目的所决定的。为了在 UI 中显示数据值,每个数据仓库提供了一系列可以和控件相绑定属性对应的名称。

通过使用标记语法,UI 可以引用数据存储和它们的值。这个语法是 <DataStoreName:PropertyName>。比如,我们假设您的游戏为用户提供了三个控制器布局选项,并且您想使当前的布局的名称出现在配置屏幕的标签上。要想绑定表标签和控制这个设置的属性,您或许想设置标签的值为类似于<Controls:LayoutType> 的东西(假设数据存储负责跟踪这个使用标签 'Controls' 的值,并且它显示了用于控制游戏布局的名称为 'LayoutType' 的属性)。

这种绑定控件的方法允许 UI 以安全的方式来引用游戏数据,因为数据存储封装了生命周期管理,从而确保了在控件和由数据存储表示的可以和垃圾回收进行交互的底层数据间永远没有任何引用。

交互作用

每个控件包含一个 EventProvider 组件,它为控件提供了那个控件可用的一些事件。EventComponents 包含了一组事件类(称为 UIEvent,尽管实际上这些或许是 kismet 系统中的 SequenceEvent 的子类)。每个 UIEvent 实例被具有一组动作的 EventComponent 所包含,当触发了那个事件时将会执行这些动作。对于每个实现那个事件的控件来说,分配给每个特定事件的动作是不同的。

有一个序列化的 UIEvent 类用于处理输入事件(为了简单,我们简称它为 UIEvent_ProcessInput)。为了使一个控件可以处理输入,则在它的事件列表中必须包含 EventComponent。对于一组内置的控件,需要处理输入的所有控件已经在它们的事件列表中有一个输入处理器。对于设计人员创建的自定义控件(通过预制)来说,如果他们想让那个控件处理输入,那么它们可以向那个控件中添加一个事件处理器。UIEvent_ProcessInput 类将可以处理任意数量个输入按键事件。对于它处理的每个输入按键事件来说,它可以定义一个或多个当那个动作发生时应该出现的动作。所以,对于任何给定的页面,进那些包含了输入处理器的控件可以处理输入。

因为我们可以通过查看控件的事件处理器来决定控件处理哪个输入事件,我们可以在任何特定的时间内决定哪个控件可以响应哪个按键事件,并忽略那个页面中的所有其它控件。无论何时,当事件发生时,它将会改变那组可以处理输入的控件,页面中所有发生事件的控件都会被更新。这将会通过使用一个 InputEventSubscription 来进行封装,InputEventSubscription 是一个将一个按键名称映射到一组控件的结构体。InputEventSubscription 中包含的控件是当前用于处理输入的控件,在这个控件的 EventComponent 事件列表中包换了 UIEvent_ProcessInput 事件,它会对该输入按键做出反应。通常,仅当控件是聚焦链的一部分时才符合处理输入的条件,但是配置为接受为聚焦的输入的控件也被认为是符合的。每个页面有一个输入按键名称到 InputEventSubscriptions 的映射。当接收到一个输入事件时,页面搜索找到与该按键名称相对应的 InputEventSubscription 结构体。这使得页面可以立即访问那些可以处理该输入事件的所有控件。数组中的符合条件的控件的顺序由它们处理输入的机会来决定。所以,当前的聚焦控制或许是数组中的第一个控件,后面跟随的是它的父项,接下来是可以处理那个输入的它父项的子项,以此类推。

一个页面的 InputSubscribers 不是一个静态数据结构。在页面的生命周期期间,控件将会不断地从页面的符合条件的控件列表中添加剂删除它们本身。当控件失去聚焦时,它们将会从页面的符合条件的控件列表删除它们本身。当控件获得聚焦时,它们将会把它们本身添加到页面的 InputSubscribers 列表中。另外,控件可以基于控件的状态变化或某些内部逻辑来从它能够处理的事件列表中添加或删除一个新的输入按键。(比如,一个编辑器框或许要求输入到文本框的第一个字符必须是字母字符,但是在第一个字符之后任何字符都是可以的)。当这种情况出现时,控件将通知页面,页面将会相应地调整它的 InputSubscribers。关键是页面的 InputSubscribers 仅对某些事件做出响应并进行修改。当输入事件发生时,InputSubscribers 已经几乎是最新的,所以它可以作为一个可靠地源来决定如何处理输入。

当接收到一个输入事件时,页面从它的 InputSubscribers 列表中查找适当的 InputEventSubscription。它迭代查找当前符合响应那个输入事件的控件列表,给予每个控件处理那个事件的机会。这些控件都能够保证可以响应那个输入事件(即使它们由于任何原因决定不做出响应)。如果 InputEventSubscription 中的控件都不想对该输入做出响应或者如果它们没有响应,但是这些控件却配置为允许该输入事件继续传递,那么 UI 系统将会允许 GameViewportClient 将该输入事件传递到它的数组中的其它交互作用。(当一个输入事件被处理后,但仍然继续传递时,则代表该事件的事件对象将会被标记,它指出了输入事件已经处理,可能包含了执行哪个动作或谁处理了输入按键的相关信息。这将会用于允许其它游戏工程和控件一同工作来执行默写共有任务。再次注意,这种情况仅当控件被配置为不捕获它响应的输入事件时发生)。

展示外观

展示外观子系统负责把 UI 展现给用户。这包括渲染控件及它们的资源,以及渲染数据仓库提供的渲染数据。

页面

一组控件的最外层的容器称为 UIScene。Scenes(页面)包含一组控件,页面和控件间的关系同地图和放置在那个地图中的 actor 的关系类型,所以,所有的控件必须被一个 UIScene 所包含。控件不能直接地被渲染 – 他们总是通过它们的容器页面进行渲染。一次可以有多个活动的页面,并且某些页面在地图变化时仍然可以持续显示。UIScenes 是在编辑器中创建的,它被存储在一个 Unreal 包文件中;打开一个 UISence 就像在游戏播放期间加载其它任何 Unreal 资源完全一样。UISences 可以和一个单独的玩家相关联,或者它们可以是全局的,所有的玩家都可以访问它。UIScenes 的责任包括跟踪及更新它的控件的位置、传递输入事件以及渲染它的控件。尽管可以在页面中层次化地管理控件(控件可能会包含其它控件),但是传入到每个控件的输入的顺序不是由页面的控件组织结构决定的。

控件

所有控件的基类称为 UIScenes(在很多地方,控件和 UIObject 可以互换地使用)。

功能:

  • 每个控件都可以作为另一个控件的容器。
  • 每个控件都一个唯一的 GUID。
  • 每个控件都可以指定任何数量个“states(状态)”,比如押下、激活、聚焦、禁用等。
  • 如果停靠目标存在和控件存在于同一个页面中,那么任何控件可以停靠到任何其它控件上。控件可以停靠到多个控件上,但是任何控件具有停靠设置的最大的数量等于那个控件的面的最大数量。2D 控件仅有 4 个停靠设置(一个面一个)。一个单独的控件可以是多个停靠设置中的目标,但有一个限制是您不能把源控件的两个面绑定到目标控件的同一个面上(也就是,您不能把一个控件的顶部面和右侧面都绑定到目标空间的右侧面上)。您可以停靠垂直的面;目标面的长度将会分为两半,源面将会停靠在目标面的那个点上。您不能再两个停靠设置之间创建一个循环关系(控件 A 把它的左侧的面停靠到控件 B 的右侧,而空间B的右侧面停靠到控件 A 的左侧)。一个控件不能在它自己的停靠设置中包含它本身。然而,如果控件 A 停靠到控件B并且控件 B 停靠到控件 A,如果它们之间不产生循环关系,那么这样做是完全可以的。想象一个 容器-子项 关系(控件 A 是一个容器,控件 B 是空间 A 的子项)。您或许想使得控件 B 的左侧停靠到控件 A 的左侧,然后控件 B 的右侧停靠到控件 A 的右侧。这将会导致控件 A 将会进行扩大或缩小来包含控件 B,并且控件 B 的左侧仍然会对其到控件 A 的左侧。您也可以指出每个停靠设置的留白的值。
  • 可通过像素或百分比的方式来制定控件的位置。这个值可以相对于整个视口、自己的页面或者 UIObject 的容器控件来进行设置。
  • 任何空间都可以配置为对任何事件(用户或系统)做出反应。控件类的程序员决定了特定的类能够处理哪些事件。设计人员选择在该控件类的实例中实现这些事件的具体哪个,或者控件程序可以使得改控件总是可以实现一个或多个事件类型。
  • 每个控件在对特定的事件做出反应时可以执行不同的动作。
  • 每个控件都可以处理输入。控件可以被配置为总是处理输入或者仅当聚焦此控件时处理输入。控件也可以选择是否既可以吞下处理的输入又可以吞下未处理的输入。
  • 控件的任何属性都可以使用 Matinee 来使其进行动画,包括旋转、缩放、位置、颜色、锚点、UV-坐标等。
  • 每个也控件都支持层次化的关联菜单。
  • 所有的控件在任何合适的地方都支持 拖拽-放下 操作。
  • 可以配置控件自动地对其并放置它包含的控件。

(以下不是所有可用的内置控件的参考;它仅是几个比较有意思的类概述。

标签

在 UI 中显示文本的基本控件。

  • 支持本身大小的自动调节,从而容纳它包含的字符串。
  • 支持三种剪切模式 – 正常(剪切)、自动换行以及省略号(字符串中的后面的几个字符被替换为省略号)。
  • UIString 是底层对象。

列表

设计 UIList 的目的是以一般的方式来支持各种非常复杂的列表类型,所以可以通过对其进行抽象,以便可以通过 UI 来进行配置。一个列表由三个部分组成: 数据源、容器控件、和展示器。

  • Container(容器) -容器是数据和 UI 之间的管道。容器不知道它所包含的数据类型的任何信息。容器负责跟踪它所包含的元素的数量、每个单元的大小、处理输入(包括跟踪选中的元素、改变选中的元素等)、从列表中添加及删除元素、及把数据在数据源和presenter(展示器)之间往返传递。该容器通过 UI 列表控件显示在 UI 中。
  • Data(数据) -数据源是列表获取渲染到列表中的数据的地方。任何给定的列表可以有多个数据源。列表有权访问任何它的父项页面可以访问的数据存储。列表的数据源使用 UIString 类进行处理。
  • Presenter(展示器) - 展示器是控制数据如何呈现到列表容器上的方式。展示器负责根据设计人员配置的布局来格式化数据。展示器根据列表容器中的非动态约束处理调整列表容器中的任何可以动态地调整的参数的值。

UIString

UIString 是 UI 所呈现的所有数据的核心的可渲染实体。UIStrings 被分为一个或多个 UIStringNodes,每个节点对应这正常的文本或者标记数据。标记数据定义为文本,这些文本将会被从数据仓存储中获得的数据所替换。正如在数据存储部分所提到的,通过 <DataStoreName:PropertyName> 来引用数据仓库。标记可以改变当前的方格: <Styles:NormalText> 可以启用或禁用一个风格属性: <Attributes:B> <Attributes:/B> 或者它可以指出那个标记应该被数据仓存储中的那个标记中指定的属性的值所替换: <SomeDataStoreName:PropertyName>。通过分解输入文本,UIStrings 可以动态地生成 UIStringNodes。比如,传入以下字符串到一个 UIString 时,生成了 7 个标记:
指定的名称 '<SceneData:EnteredName>' 不存在。按下 <ButtonImages:IMG_A> 来继续或者按下 <ButtonImages:IMG_B> 来取消。
生成的标记是:

[0] = "The name specified '"
[1] = "<SceneData:EnteredName>"
[2] = "' is not available.  Press "
[3] = "<ButtonImages:IMG_A>"
[4] = " to continue or "
[5] = "<ButtonImage:IMG_B>"
[6] = "to cancel."

这些标记然后将会被转换为 UIStringNodes。引用了一个数据存储的标记有那个数据存储来处理,这将会创建具有适当的值的 UIStringNode。有两种类型的 UIStringNodes – 一种类型用于渲染文本、一种类型用于渲染图片。UIStringNode_Text 节点用于字符串,以及那些渲染为文本的数据值,比如整型、布尔型值等。UIStringNode_Image 节点用于数据存储标记,它仅展现那些可以作为图像描画的数据,比如物体或贴图。每个字符串节点可以根据文本或标记的后期处理版本来计算及跟踪它自己的精确的范围值(宽度和高度)。这仅当节点状态的某些元素改变时才执行这个操作,比如数据本身、缩放或者字间距调整等。完整的 UIString 的范围是很容易计算的 – 它就是所有节点的范围的总和。一个 UIString 也可以配置为手动地控制每个 UIStringNode 的范围,比如当一个 UIStringNode 被一个 UIListElementCell 包含时,这是设计人员期望单元的特定区域具有一定的宽度或者靠右对齐或者其它的操作等。

控件风格

风格控制了一个控件的显示样子。一个风格可以包含关于如何渲染贴图的信息(缩放的、拉伸的等),并且它也可以包含描画字符串的信息(字体、颜色、属性等)。在 UI 编辑器中有一个风格浏览器。为了将一个风格应用到控件上,用户可以从现有的风格列表中选择一种风格,或者创建一种新的风格(基于现有的风格或者从从头开始创建)。当创建了以风格时,将会为它分配一个持久的 GUID。所有的特定控件皮肤的风格都存储在一个单独的 Unreal 包文件中。风格所需要的资源或许也存储在皮肤文件中,或者它们会被放置到另一个包中。一个完整的游戏 UI 要求至少具有一个作为默认风格包的风格包。任何额外的皮肤集是基于这个默认的皮肤的,并且仅从基础皮肤进行的改变会被存储在自定义皮肤文件中。当从一个皮肤包中为一个控件分配了一个皮肤时,将会在皮肤文件中放置一个控件的 GUID 到风格的 GUID 的一个映射。对于自定义的皮肤集,默认情况下,控件将会自动地映射自定义风格集中包含的自定义版本,但是用户可以选择为特定控件分配一个完全不同的风格。这仅会改变皮肤集中的空间的风格,并且任何皮肤集都是基于自定义集的。自定义集可以是继承关系的,所以自定义皮肤集可以基于其它的自定义皮肤集。

皮肤

动画

UI 编辑器


关于 UI 编辑器的主要功能的介绍以及如何使用这些功能的文档资料,请参阅 UI 编辑器用户指南?页面。