全面理解 Unity UI 系统

【狗刨学习网】

随着 Unity 4.6 发布,新 UI 系统终于与大家见面了。

RectTransform

Unity UI 系统使用 RectTransform 实现基本的布局和层次控制。RectTransform 继承于 Transform,所以 Transform 的所有特征 RectTransform 同样拥有。在
Transform 基础上,RectTransform 增加了 轴心(pivot)、锚点(实际上是用 anchorMin、anchorMax 两个点定义的矩形区域)、和 尺寸变化量(sizeDelta)。

轴心:表示UI元素的中心,使用相对于自身矩形范围的百分比表示的点位置,这会影响定位、缩放和旋转。

锚点:相对于父级矩形的子矩形区域,这个矩形各个边界值使用百分比表示。

尺寸变化量:相对锚点定义的子矩形的大小变化量,与锚点定义的子矩形合并后的区域才是最终的UI矩形。

在 Inspector 界面上,为了更方便的调节 RectTransform 的属性,锚点的两个点重合时会显示位置和宽高(直接调节位置和sizeDelta),否则显示相对锚点矩形边界的偏移量(通过计算后再赋值给位置和sizeDelta)。在程序中,RectTransform
添加了 anchoredPosition 和 rect 属性来更方便的编程。

RectTransform 组件同样负责组织 GameObject 的层级关系。在 UI 系统中,子级 UI 对象总是覆盖显示在父级 UI 对象上;层级相同的 UI 对象,下方的 UI 对象总是覆盖显示在上方的
UI 对象上。这样的设计避免了繁琐的深度设置。在程序中,Transform 添加了 SetSiblingIndex、GetSiblingIndex、SetAsFirstSibling、SetAsLastSibling 这些方法来方便的修改物体的层级顺序。

EventSystem

如果你使用 UI 系统,那么 EventSystem 对象会自动创建。这个对象负责监听用户输入。默认情况下,在电脑上可以使用键盘和鼠标输入,在移动设备上可以使用触摸输入。但是如果你要为surface这样的设备开发,你也可以同时启用两种输入。当需要屏蔽用户输入时,将此对象关闭即可。UnityEngine.EventSystems.EventSystem.current
保存了当前活动的 EventSystem 对象。

Canvas

Canvas 是其他所有 UI 对象的根。在一个场景里 Canvas 数量和层级都没有限制。子 Canvas 使用与父 Canvas 相同的渲染模式。一个 Canvas 有三种渲染模式:

Screen Space - Overlay:UI元素相对于屏幕空间,以2D方式显示在任何相机画面的上面。这是非常标准的
UI 风格。典型例子:大量窗口、文本和按钮的策略游戏。

Screen Space - Camera:UI元素相对于屏幕空间,由指定的相机负责显示,相机的参数影响显示的结果。你可以把
Canvas 理解为相机的子物体。典型例子:射击游戏屏幕上的 3D HUD。

World Space:UI元素相对于世界空间,和其他场景里的物体一样有世界位置、遮挡关系。通常用来做非常创新的
UI 设计。例子:游戏内的手机屏幕、与场景绑定的游戏指导等。

CanvasScaler

这个组件负责屏幕适配。UI 系统使用 RectTransform 来计算 UI 的位置和大小,但这还不够。如何让设计的 UI 可以适配不同的分辨率、宽高比和 DPI?这个组件给出了以下3种适配方法,注意任何一种适配方法都不会改变UI的宽高比和相对定位。

Constant Pixel Size:通过调节 Canvas 像素大小来维持缩放不变。它的意思是在任何屏幕上不改变
Canvas 的缩放系数(Scale Factor),而是调节 Canvas 的像素大小与屏幕保持一致。你可以手动或通过代码调节 Canvas 的缩放系数。这是 UI 系统默认的适配方案。如下图两种分辨率下相同的UI显示的不同之处,虽然不同屏幕下UI元素定位、大小没有发生变化(图中两个白色元素定位分别为屏幕左上角和右下角),但是较小的屏幕上UI元素占用了大部分屏幕空间,显得更拥挤。这就是这种适配方式的缺点,小屏幕太拥挤、大屏幕太空旷,没有考虑到屏幕的分辨率和DPI。但是这种模式的好处是UI元素可以保持设计时的细节(因为没有缩放)。这种模式可能适用于:你希望UI在一定范围内按原始大小显示,这样既可以让UI显示的尽可能清晰、又可以让屏幕较大的玩家拥有更广阔的视野,但是在太小或太大的屏幕上,你可以通过程序来调节缩放系数,不至于小屏幕被UI占满、大屏幕找不到UI。

Scale With Screen Size:根据屏幕分辨率缩放。这可能是大部分游戏最方便的适配方法。在这种模式下,你需要指定一种设计分辨率,然后指定缩放的算法。无论哪种缩放算法,如果实际宽高比与设计宽高比相同,UI
都会被等比缩放。实际上,Canvas 只是保持自己的大小和设计分辨率一致。如果实际宽高比与设计宽高比不同,这时缩放算法才会影响显示结果。缩放算法有三种:扩展、收缩 和 匹配宽高。扩展算法的逻辑是,扩大 Canvas (在宽高比上)较短的一边,使得 Canvas 宽高比与屏幕一致。如下左图,设计分辨率宽高比为1:1(红色线框),实际屏幕更宽所以 Canvas 的 width 增加以匹配屏幕。这样的算法在宽高比不同的屏幕上将始终导致UI更“开阔”。收缩算法的逻辑是,收缩 Canvas (在宽高比上)较长的一边,使得
Canvas 宽高比与屏幕一致。如下中图,设计分辨率宽高比为1:1(红色线框),实际屏幕更窄所以 Canvas 的 height 减小以匹配屏幕。这样的算法在宽高比不同的屏幕上将始终导致UI更“紧凑”。匹配宽高的算法逻辑是,根据指定的权重,同时调节 Canvas 的宽和高,使得 Canvas 宽高比与屏幕一致。如下右图,设计分辨率为红色线框,设定宽度和高度的权重相等(0.5),实际屏幕上 Canvas 的宽和高都被调整以匹配屏幕。这样的算法目的是,通过可调节的宽高权重,尽可能的保持UI的原始设计。

 

Constant Physical Size:通过调节 Canvas 物理大小来维持缩放不变。它的意思是在任何屏幕上不改变
Canvas 的 DPI,而是调节 Canvas 的物理大小总是与屏幕保持一致。这种说法可能比 Constant Pixel Size 更难以理解,实际上他们本质是一样的,只不过 Constant Pixel Size 通过逻辑像素大小调节来维持缩放,而 Constant Physical Size 通过物理大小调节来维持缩放。使用这种模式必须指定一个像素转换物理大小的因数(填写96方便在windows上进行开发)。运行时通过具体设备报告的dpi计算 Canvas 像素大小和缩放系数。这种模式从设计的意图来看,是为了在开发时使用物理单位而非像素单位,这只会让程序和美术的工作变得复杂,实际使用价值并不高。因为开发人员更关心设计的像素分辨率,他们需要绘制明确的像素大小的图片!如果未来开发人员和玩家都使用了超高DPI的显示器,那时或许会更注重物理尺寸。

Selectable

可交互UI组件的基类。它负责响应用户的输入,产生视觉变化、切换导航目标 以及 处理通用的UI事件。

Transition:可交互组件有4种视觉状态:正常(normal), 高亮(highlighted),
按下(pressed)和 禁用(disabled)。Selectable 根据用户的输入和自己当前的状态执行状态切换,切换状态的视觉效果有4种类型:none、color tint、sprite swap 和 animation。使用 animation 效果必须再添加一个 animator 组件,此动画控制器含有上述4种状态。可以通过Auto Generate Animation 按钮自动添加组件并创建动画控制器。

Navigation:可以使用键盘和游戏控制器切换导航目标,如果你要开发一个仅使用游戏控制器就可以玩的游戏(主机游戏),那么这个功能非常重要,因为玩家没有鼠标也无法使用触摸屏,只能通过按钮来切换导航目标。这个功能被设计的非常完善,以至于你几乎什么都不用做就可以处理的很好。一共有5种导航选项:不使用(None)、水平(Horizontal)、垂直(Vertical)、自动(Automatic)、显式指定(Explicit)。在非显式指定的情况下,导航系统根据每个UI元素的矩形位置和大小,自动查找4个方向上是否存在最合适的切换目标。如果选择显式指定,需要为4个方向指定切换目标(Selectable)。在Inspector
界面点击 Visualize 按钮可以查看导航路径(下图中的黄色线条),在 EventSystems 中可以设置默认选中的对象。

通用事件:OnSelect, OnDeselect, OnPointEnter, OnPointExit,
OnPointDown, OnPointUp, ... 。重写这些方法来定义自己的可交互组件。文末将通过一个例子来说明如何实现自定义控件。

Auto Layout

自动布局用于简化UI的布局工作。自动布局基于 RectTransform 的布局系统,包含 布局元素(Layout Elements) 和 布局控制器(Layout Controllers)两个概念。

布局元素含有 最小尺寸、首选尺寸 和 可选尺寸 这些参数,布局控制器根据这些参数来调整布局元素的大小和位置。布局控制器调整的基本原则是:首先分配最小尺寸,然后如果还有足够空间就分配首选尺寸,最后如果还有空间则分配可选尺寸。一个含有
RectTransform 组件的游戏对象就是一个布局元素。添加某些组件会修改布局元素的参数。LayoutElement 是一个用来修改默认布局参数的组件。布局控制器以多种组件的形式存在,它们控制自身或子级的布局元素的大小和位置。关于各种布局控制器组件的功能和使用方法请参考 Unity 文档。

Rich Text

默认情况下一个 Text 组件以单一样式显示所有文本,使用富文本可以让显示样式更丰富,比如高亮部分文本。实际上,富文本功能不仅可以用在 UI 系统,还可以用在 Legacy GUI 系统 和 Debug 中。

富文本使用方法类似 html 标签,比如 "Hello" 将显示为加粗的 "Hello"。这些标签还可以嵌套使用。可用的标签有
b(加粗)、i(倾斜)、size(大小)和 color (颜色)。其中 size 和 color 必须指定属性值,如:"Hello" "Hello"。size 属性的单位是像素,color 属性使用RGBA格式的16进制表示颜色或直接填写常用颜色名称。下图为使用示例。

 

如果使用 TextMesh,还可以使用 material 和 quad 标签。material 需要指定 material 数组中的 material 下标,如 "Cool";quad 标签没有结束标签,通常用来在文本中显示一个图片,如
"This is me: "。

UnityEvent

一个可序列化的、可显示在 Inspector 上的事件类型。典型的用途就是 Button 的 OnClick 事件。拖拽一个对象或组件到方框中,就可以选择事件触发时调用的方法。可选方法必须是公开的、无返回值的、含有0个或1个可序列化的参数。也可以调用
set 类型的属性。你也可以在自己的脚本里使用 UnityEvent,只要定义一个序列化的字段,就可以和 OnClick 看起来一样了!

UnityEvent 可以通过代码添加、移除或调用方法,是对 C# 的 delegate 的包装。另外还有泛型版本的 UnityEvent,最多支持 4 个参数,不过由于是泛型抽象类,需要先继承再使用。

public class MyEvent : UnityEvent { }    public MyEvent
myEvent;

UnityEvent 是从程序里通过 Invoke 方法调用的,Invoke 需要的参数类型和数量与泛型参数一致。但是,Inspector上依然只能填写0个或1个参数,在 Inspector 上添加的方法是无法直接获得
Invoke 时传递的参数的。

自定义控件

通过此自定义控件的例子,来说明如何灵活运用 UI 系统各种功能实现各种奇葩需求。

需求:实现一种角色点数分配的控件,角色有一定数量的点数,可以分配给多种属性,每种属性都可以分配一定范围的点数,各属性点数之和不能超过角色拥有的点数。一般的实现方法是,为每个角色显示剩余点数和多条属性滑块,拖动每个滑块会改变剩余点数。这里我们要求把所有滑块放到一个大滑动条中,可以直接拖动每个滑块。这样的好处是可以直观的看到每个角色总能力对比以及剩余可分配点数。如下为设计图。

我们先分析这个需求:整个滑动条代表点数总量;所有滑块是左对齐拼接的;每个滑块具有自身最大值、最小值限制;所有滑块总长度不超过整个滑动条;每个滑块是可交互的。可以得到初步的设想是,每个滑块拥有一个继承 Selectable
的组件,重写按下和弹起的事件,在这个过程中,控制滑块的长度。但是如何保持所有滑块始终左对齐呢?一种直接的想法是使用水平自动布局。但是这里我们不这么做(你可以试试这样来实现),而是充分利用 RectTransform 锚点的功能,让每一个滑块对其到前一个滑块的右端。这样要求每个滑块是前一个滑块的子级。解决方案有了,再考虑一下让这件事情更简单、更通用一些,所有初始参数填写在一个根部的组件里,这个组件根据这些参数来自动创建所有滑块。那么一个滑块需要的参数大概就是这样的吧:

// 属性

class Attribute{

public string name;

public Sprite image;

public Color color;

public int min;

public int max;

public int value;

public ValueSlider valueSlider;

}

min、max、value 分别为 最小点数、最大点数 和 初始点数。最后一个 ValueSlider 就是我们自定义的 Selectable 组件,在属性里保存对应的组件引用方便对其进行修改。ValueSlider
定义如下:

// 滑块

class ValueSlider : Selectable{

MultiAttributesSlider _multiAttributesSlider;

Attribute _attribute;

public void Init(MultiAttributesSlider multiAttributesSlider, Attribute attribute)    {

_multiAttributesSlider = multiAttributesSlider;

_attribute = attribute;

}

public override void OnPointerDown(PointerEventData
eventData)    {

base.OnPointerDown(eventData);

_multiAttributesSlider.BeginSlide(_attribute, eventData);

}

public override void OnPointerUp(PointerEventData
eventData)    {

base.OnPointerUp(eventData);

_multiAttributesSlider.EndSlide();

}

}

初始化的时候保存根部组件(起个名字叫“多属性滑动条”)和对应的属性引用,然后按下和弹起时分别调用根部组件的开始滑动、结束滑动方法。没有什么实际的内容,主要的操作都在根部组件上。根部组件定义如下:

class MultiAttributesSlider : MonoBehaviour{

// 总点数

int _totalValue;

// 属性数组

[SerializeField]

Attribute[] _attributes;

//剩余点数

int _restValue;

// 一个点数对应的像素大小

float pixelsPerPoint;

// 保存滑块按下时的信息

Attribute _currentAttribute = null;

PointerEventData _eventData;

int _beginValue;

int _beginRestValue;

// 当鼠标按下任何一个滑块时调用

public void BeginSlide(Attribute currentAttribute, PointerEventData eventData)

{

_currentAttribute = currentAttribute;

_eventData = eventData;

_beginValue = currentAttribute.value;

_beginRestValue = _restValue;

}

// 当鼠标从任何一个滑块释放时调用

public void EndSlide()    {

_currentAttribute = null;

}

// 初始化

void Awake()    {

// 需要通过自定义编辑器来保证 Inspector 填写的参数完全合理。这个例子忽略这一步。

// 统计已使用的点数

int valueCount = 0;

for(int i=0; i<_attributes.Length;
i++)        {

valueCount += _attributes.value;

}

// 计算剩余点数

_restValue = _totalValue - valueCount;

RectTransform lastParent = transform as RectTransform;

// 计算一个点数对应的像素大小

pixelsPerPoint = lastParent.sizeDelta.x / _totalValue;

// 创建每个滑块;更好的做法是,在自定义编辑器中使用一个按钮来生成所有滑块

for(int i=0;
i<_attributes.Length; i++)        {

GameObject slider = new GameObject(_attributes.name);

// 初始化 RectTransform

RectTransform rect = slider.AddComponent();

rect.SetParent(lastParent, false);

rect.localScale = Vector3.one;

rect.localRotation = Quaternion.identity;

rect.pivot = new Vector2(0, 0.5f);

rect.anchoredPosition = Vector2.zero;

if (i == 0)            {

rect.anchorMin = Vector2.zero;

rect.anchorMax = new Vector2(0, 1);

}            else            {

rect.anchorMin = new Vector2(1, 0);

rect.anchorMax = Vector2.one;

}

rect.sizeDelta = new Vector2(pixelsPerPoint * _attributes.value, 0);

// 初始化 Image

Image image = slider.AddComponent();

image.sprite = _attributes.image;

image.color = _attributes.color;

image.type = Image.Type.Sliced;

image.fillCenter = true;

// 初始化 ValueSlider

_attributes.valueSlider
= slider.AddComponent();

_attributes.valueSlider.Init(this, _attributes);

// 将当前 RectTransform 作为下一个滑块的父级

lastParent = rect;

}

}

// 更新滑块的值

void Update()    {

if(_currentAttribute != null)        {

// 计算滑动距离对应的点数变化

int deltaValue = Mathf.RoundToInt((_eventData.position.x - _eventData.pressPosition.x) / pixelsPerPoint);

// 受最小、最大值限制的点数变化

deltaValue = Mathf.Clamp(_beginValue + deltaValue, _currentAttribute.min, _currentAttribute.max) - _beginValue;

// 更新剩余点数

_restValue = _beginRestValue - deltaValue;

// 如果剩余点数用完,需要减少点数变化

if(_restValue < 0)            {

deltaValue += _restValue;

_restValue = 0;

}

// 更新当前点数

_currentAttribute.value = _beginValue + deltaValue;

// 更新滑块大小

(_currentAttribute.valueSlider.transform as RectTransform).sizeDelta

= new Vector2(pixelsPerPoint * _currentAttribute.value, 0);

}

}

}

代码的含义在上面的分析和注释里写的很清楚了,不再赘述。这个组件最好再配合一个自定义的编辑器,但是这里就不想写了,如果你感兴趣可以试试。

下面就是测试。创建一个滑动条背景,添加此脚本,填写参数,最后看起来这样的:

然后运行起来吧。图就不截了,就是上面的设计图......最后再来张合影。

声明:此篇文档时来自于【狗刨学习网】社区,是网友自行发布的Unity3D学习文章,如果有什么内容侵犯了你的相关权益,请与官方沟通,我们会即时处理。

更多精彩内容:www.gopedu.com

时间: 2024-10-06 01:18:46

全面理解 Unity UI 系统的相关文章

理解和使用 Unity UI 系统

随着 Unity 4.6 发布,新 UI 系统终于与大家见面了.这篇文章将不会介绍如何使用按钮.滚动条之类的UI控件,这些内容可以参考Unity Manual:这篇文章的重点是,如何理解 UI 系统的设计,以便更好的在实际中使用.自定义和扩展. EventSystem 如果你使用 UI 系统,那么 EventSystem 对象会自动创建.这个对象负责监听用户输入.默认情况下,在电脑上可以使用键盘和鼠标输入,在移动设备上可以使用触摸输入.但是如果你要为surface这样的设备开发,你也可以同时启用

Unity3D 4.6 新的UI系统

在Unity3D 4.6版本中,终于增加了新的UI系统.虽然从功能,效果等方面来讲,跟NGUI还有一定的差距,但NGUI毕竟是收费插件,对于游戏商用来说有一定的制约. 下面我们来看看,Unity3D 4.6中新的UI系统吧. 如上图所示,我们可以看到在GameObject菜单中,已将3D Object,2D Object和UI分类了. UI中,我们可以看到Panel,Button,Text,Image,RawImage,Slider,Scrollbar,Toggle,InputField,Can

【Unity编程】Unity动画系统(一)

Unity动画系统 Unity动画系统,也称为"Mecanim",提供了以下功能: 简单的工作流程,设置动画的所有元素,包括对象,角色和属性. 支持导入外部创建的动画片段和使用内置动画编辑器制作的动画片段. 人型动画重新定位,动画角色的运动控制可以被所有的角色模型共享,即角色的外观(SkinedMesh)和运动(Animator)是分离的,它们互相组合之后形成最终的动画. 用于编辑动画状态的的简化工作流程,即动画控制器. 方便预览动画片段,以及片段之间的插值过渡. 这使得动画师可以独立

《深入理解Android 卷III》第五章 深入理解Android输入系统

<深入理解Android 卷III>即将公布.作者是张大伟.此书填补了深入理解Android Framework卷中的一个主要空白.即Android Framework中和UI相关的部分.在一个特别讲究颜值的时代,本书分析了Android 4.2中WindowManagerService.ViewRoot.Input系统.StatusBar.Wallpaper等重要"颜值绘制/处理"模块 第5章 深入理解Android输入系统(节选) 本章主要内容: ·  研究输入事件从设

[转]深入理解 Android消息处理系统的原理

原文地址:深入理解 Android消息处理系统的原理作者:hoarn Android应用程序也是消息驱动的,按道理来说也应该提供消息循环机制.实际上谷歌参考了Windows的消息循环机制,也在Android系统中实现了消息循环机制. Android通过Looper.Handler来实现消息循环机制,Android消息循环是针对线程的(每个线程都可以有自己的消息队列和消息循环). 本文深入介绍一下Android消息处理系统原理. Android系统中Looper负责管理线程的消息队列和消息循环,具

Unity4.6新UI系统初探(uGUI)

一.引言 Unity终于在即将到来的4.6版本内集成了所见即所得的UI解决方案(视频).事实上从近几个版本开始,Unity就在为这套系统做技术扩展,以保证最终能实现较理想的UI系统.本文试图通过初步的介绍和试用,让读者对这套系统有大体的了解,以便更进一步评估这套UI系统好不好用,适合用在什么项目.为了避免坑挖太深,更进一步的试用和评估我将在<用uGUI开发自定义Toggle Slider控件>中进行论述.为论述方便,下文将这套New UI System简称为uGUI,并且以X-UI指代现有第三

UNITY光照系统简介

UNITY_光照系统 光照系统又称照明系统: 从字面意思理解,光照系统的作用就是给我们的场景带来光源,用于照亮场景.一个五彩缤纷的游戏场景肯定要比一个漆黑一片的游戏场景更具吸引力,想让游戏场景变的更漂亮,光照系统是必不可缺的. 在 Unity5.x 中光照系统主要组成部分有两个: 灯光组件(4 个灯光组件,2 个特殊功能组件)以及 Lighting 面板. 两种类型: 实时光照:PC,主机端运行,照明效果好,但是消耗资源较大. 烘焙光照:移动端运行,照明效果也不错,消耗资源较少. 光照系统之实时

Linux系统理解以及Linux系统学习心得

原创作品转载请注明出处  <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 作者:严哲璟 说一下我对Linux系统的理解 1.加载Linux内核准备:在加载基本输入输出模块(BIOS)之后,从磁盘的引导扇区读入操作系统的代码文件块到内存中,之后开始整个系统的初始化. 2.main.c的start_kernel函数是整个操作系统的入口,这也与Linux是基于C语言的特性相符,start_kernel具体做的动作很多

doom3的UI系统

doom3的UI系统是纯数据驱动的,例如 windowDef TextTitle2 { rect 20,341,600,55 visible 1 text "#str_00073" forecolor 0.6,1,1,0 textscale 0.8 font "fonts/micro" textalign 1 notime 1 onTime 0 { transition "forecolor" "1 1 1 0" "