Skip to content

Unity-UGUI

更新: 8/8/2025 字数: 0 字 时长: 0 分钟

UGUI (Unity GUI) 是 Unity 引擎中用于创建用户界面 (UI) 的官方系统。它在 Unity 4.6 版本中被引入,取代了老旧且效率不高的 IMGUI (Immediate Mode GUI),成为了制作游戏 UI 的主流方案。

UGUI 源码位于编辑器根目录下的: Data/Resources/PackageManager/BuiltInPackages/com.unity.ugui

六大基础组件

  1. Canvas 对象上依附的:
    • Canvas:主要用于渲染UI控件
    • Canvas Scaler:画布分辨率自适应组件,主要用于分辨率自适应
    • Graphic Raycaster:射线事件交互组件,主要用于控制射线响应相关
    • RectTransform:UI对象位置锚点控制组件,主要用于控制位置和其对应方式
  2. EventSystem 对象上依附的
    • EventSystem:玩家输入事件相应系统
    • Standalone Input Module:独立输入模块组件,用于监听玩家操作

Canvas-渲染模式的控制

  1. Canvas 组件用来干什么
    • Canvas 是 UGUI 中所有 UI 元素能够被显示的根本
    • 主要负责渲染自己的所有 UI 子对象
  2. 场景中可以有多个 Canvas 对象
    • 可以分别管理不同画布的渲染方式,分辨率自适应方式等等参数
    • 如果没有特殊需求,一般一个场景上一个 Canvas 对象即可

Canvas 组件的三种渲染方式

  1. Screen Space - Overlay:屏幕空间,覆盖模式,UI 始终在前
    • Pixel Perfect:是否开启无锯齿精确渲染模式(性能换效果)
    • Sort Order:排序层编号(用于控制多个 Canvas 时的渲染先后顺序)
    • Target Dislay:目标设备(在哪个显示设备上显示)
    • Additional Shader Channels:其他着色器通道,决定着色器可以读取那些数据
  2. Screen Space - Camera:屏幕空间,摄像机模式,3D 物体可以显示在 UI 之前
    • Render Camera:用于渲染 UI 的摄像机(如果不设置效果将类似与覆盖模式;不建议设置成主摄像机,因为会难以控制 UI 与物体的渲染顺序)

      通过分离主摄像机与专门的 UI 摄像机,设置让 UI 摄像机只渲染自己的图层(depth only),实现 UI 在 3D 物体前显示的效果

    • Plane Distance:UI 平面在摄像机前方的距离,类似整体 Z 轴的感觉
    • Sorting Layer:所在排序层
    • Order in Layer:排序层的序号
  3. World Space:世界空间,3D 模式
    • Event Camera:用于处理 UI 事件的摄像机(如果不设置不能正常注册 UI 事件)

CanvasScaler 简单介绍

CanvasScaler(画布缩放器)是 Unity UI 系统中的一个核心组件,它的主要作用是控制 Canvas(画布)上所有 UI 元素的整体缩放和尺寸,以适应不同的屏幕分辨率和尺寸。画布缩放器并不负责位置,位置由 RectTransform 来控制。

在 Game 窗口中的 Stats 中能观到当前的分辨率,该分辨率会参与到自适应的计算。同时,在 Canvas 对象上 RectTransform 组件中也能看到宽高和缩放。

INFO

屏幕分辨率 = 宽高 * 缩放系数

CanvasScaler 有三种适配模式:

  1. Constant Pixel Size(极少用):无论屏幕大小如何,UI始终保持相同像素大小。
  2. Scale With Screen Size(最常用):根据屏幕尺寸进行缩放,随着屏幕尺寸大小缩放。
  3. Constant Physical Size(极少用):类似于第一种模式。无论屏幕大小和分辨率如何,UI元素始终保持相同物理大小。

接下来会一一介绍这三种模式。

CanvasScaler-恒定像素模式

Constant Pixel Size 模式的参数:

  • Scale Factor:缩放系数,会按此系数缩放画布中的所有UI元素。
  • Reference Pixels Per Unit:单位参考像素,多少像素对应 Unity 中的一个单位(默认100像素为1单位)。在图片 Inspect 窗口中的 Pixels Per Unit 设置会和该参数一起参与计算。

恒定像素模式计算公式

UI原始尺寸 = 图片大小(像素)/ (Pixels Per Unit / Reference Pixels Per Unit)

CanvasScaler-缩放模式

Scale With Screen Size 模式的参数:

  • Reference Resolution:参考分辨率。需要填写你希望参考的屏幕分辨率大小。例如,开发 pc端游戏时,可填入常用的 1920x1080;开发移动端游戏时,可填入常用的 1080x1920。
  • Screen Match Mode:屏幕匹配模式,当前屏幕分辨率宽高比不适应参考分辨率时, 用于分辨率大小自适应的匹配模式。
    • Match Width Or Height(最常用):以宽高或者二者的平均值作为参考来缩放画布区域。
      • Match:以二者的某种平均值作为参考来缩放画布。
        1. 当 Match = 0 时,强制 UI 的缩放只参考屏幕宽度的变化;
        2. 当 Match = 1 时,强制 UI 的缩放只参考屏幕高度的变化;
        3. 当 Match = 0.5 时,它会在“匹配宽度”和“匹配高度”之间取得一个平衡。它试图找到一个中间点,让你的 UI 在各种不同的宽高比下都能合理地显示。

        TIP

        Match Width Or Height 也有可能产生黑边或裁剪,但它会尽量保持 UI 元素的完整性。

        Match = 0 时,UI 会优先使屏幕水平方向被完整的填充,此时可能会导致屏幕上下出现黑边或裁剪。同理,Match = 1 时,UI 会优先使屏幕垂直方向被完整的填充,此时可能会导致屏幕左右出现黑边或裁剪。

        综上一般来说,横屏游戏会设置 Match = 1,竖屏游戏会设置 Match = 0。

    • Expand: 水平或垂直拓展画布区域,会根据宽高比的变化大小来放大缩小画布。可以完全的展示参考分辨率下创建的所有内容,但可能会展示出未设置的区域,如屏幕上或下出空白区域。
      • 拓展匹配,将Canvas Size进行宽或高扩大,让他高于参考分辨率。
      • 缩放系数 = Mathf.Min(屏幕宽 / 参考分辨率宽, 屏幕高 / 参考分辨率高)
      • 画布尺寸 = 屏幕尺寸 / 放系数
    • Shrink: 水平或垂直裁剪画布区域,会根据宽高比的变化来放大缩小画布。可以完整占据整个屏幕,但会导致会对UI的裁剪。
      • 收缩匹配,将Canvas Size进行宽或高收缩,让他低于参考分辨率。
      • 缩放系数 = Mathf.Max(屏幕宽 / 参考分辨率宽, 屏幕高 / 参考分辨率高)
      • 画布尺寸 = 屏幕尺寸 / 缩放系数

CanvasScaler-恒定物理模式

Constant Physical Size 模式的参数:

  • DPI:图像每英寸长度内的像素点数。
  • Physical Unit:物理单位,使用的物理单位种类。
  • Fallback Screen DPI:备用DPI,当找不到设备DPI时,使用此值。
  • Default Sprite DPI:默认图片DPI。

计算公式:新单位参考像素 = 单位参考像素 * Physical Unit / Default Sprite DPI

CanvasScaler-World

当 Canvas 的渲染模式(Render Mode)设置为 World Space 时,Canvas Scaler 组件的模式就会被强制锁定为 World 模式。

这个 World 模式和我们之前讨论的 Scale With Screen Size 等模式在目标上完全不同。它不负责缩放 UI 的大小,而是负责控制 UI 在 3D 世界中的渲染质量或像素密度。

Canvas Scaler 此时有一个关键属性:Dynamic Pixels Per Unit (每单位动态像素数),这个设置决定了UI “贴图”的分辨率。

Graphic Raycaster-图形射线投射器组件

Graphic Raycaster 是图形射线投射器,用于检测UI输入事件的射线发射器,主要负责通过射线检测玩家和UI元素的交互,判断是否点击到了UI元素

相关参数:

  • Ignore Reversed Graphics:是否忽略反转图形。
  • Blocking Objects: 射线被哪些类型的碰撞器阻挡(在覆盖渲染模式下无效)。
  • Blocking Mask: 射线被哪些层级的碰撞器阻挡(在覆盖渲染模式下无效)。

EventSystem

EventSystem 是整个 Unity UI 交互的中枢。它是一个独立的游戏对象,负责处理来自玩家的所有输入(鼠标、触摸、手柄、键盘),并将这些输入转化为具体的事件,再派发给正确的游戏对象(主要是 UI 元素)。

一个标准的 EventSystem 对象通常由以下几个组件构成:

  1. Event System:这是核心组件,负责管理整个事件的派发逻辑和协同其他组件工作。

  2. Standalone Input Module (独立输入模块):这是默认的输入模块,专门用于处理来自PC平台(Windows, Mac, Linux)的鼠标、键盘和手柄输入。

在移动端项目中,这个组件会被 Touch Input Module 的功能所扩展(尽管现在 Standalone Input Module 已经整合了触摸功能)。

  1. Base Input Module:这是一个基类,Standalone Input Module 就是从它继承而来的。如果你想创建自定义的输入方式(比如语音控制、手势识别),你就需要编写一个继承自 Base Input Module 的新脚本。

EventSystem 组件用于管理玩家的输入事件并分发给各UI控件,它是实践逻辑处理模块,所有的UI事件都通过 EventSystem 组件中轮询检测并做相应的执行,它类似一个中转站,和许多模块一起共同协作。EventSystem 组件有三个参数:

  • First Selected:首先选择的游戏对象,可以设置游戏一开始的默认选择。
  • Send Navigation Events:是否允许导航事件(通过键盘等设备进行移动、按下、取消)。
  • Drag Threshold:拖曳操作的阈值(移动多少像素算拖曳)。

Standalone Input Module 是连接传统硬件输入与事件系统之间的一座关键“桥梁”。它的主要工作就是监听来自玩家电脑的输入设备——主要是鼠标、键盘和手柄——然后将这些原始的硬件信号(比如“鼠标左键被按下了”或“键盘W键被按下了”)翻译成 EventSystem 能理解的、更高级的逻辑事件(比如 PointerClick 点击、Submit 提交、Maps 导航等)。

Standalone Input Module组件参数(一般不会修改):

  • Horizontal Axis:水平轴按钮对应的热键名(该名字对应Input管理器)。
  • Vertical Axis:垂直轴按钮对应的热键名(该名字对应Input管理器)。
  • Submit Button:提交(确定)按钮对应的热建名(该名字对应Input管理器)。
  • Cancel Button:取消按钮对应的热建名(该名字对应Input管理器)。
  • Input Actions Per Second:每秒允许键盘/控制器输入的数量。
  • Repeat Delay:每秒输入操作重复率生效前的延迟时间。
  • ForceModule Active:是否强制模块处于激活状态。

RectTransform

RectTransform 继承自 Transform,所以它也包含 Position (位置)、Rotation (旋转) 和 Scale (缩放) 这些基本属性。但它的强大之处在于增加了专门为 2D 矩形界面设计的几个关键概念,用来控制 尺寸 (Size) 和 自适应布局 (Responsive Layout)。

最核心的两个概念就是 Pivot (轴心) 和 Anchors (锚点)。

组件参数:

  • Pivot:轴心(中心)点,取值范围0~1。
  • Anchors(相对父矩形锚点)
    • Min 是矩形锚点范围 X 和 Y 的最小值;
    • Max 是矩形锚点范围 X 和 Y 的最大值;
    • 取值范围都是0~1。

    INFO

    锚点的两种状态:

    1. 锚点合并 (Anchors Together)

      当四个锚点值相同时,UI 元素会试图维持其轴心与锚点之间的固定偏移量。同时,它会保持一个固定的宽度 (Width) 和高度 (Height)。

    2. 锚点分离 (Anchors Apart / "Stretching)

      当四个锚点分离时,UI 元素会试图维持其四条边与四个锚点之间的固定边距 (Margin)。这四条边距在 Inspector 中显示为 Left, Right, Top, Bottom,替换原本的 X, Y, Width, Height 属性。该模式常用于背景图。

  • Pos(X,Y,Z):轴心点(中心点)相对锚点的位置。
  • Width / Height:矩形的宽高。
  • Left / Top / Right / Bottom:矩形边缘相对于锚点的位置;当锚点分离时会出现这些内容。
  • Rotation:围绕轴心点旋转的角度。

可以通过 anchoredPosition 属性来来获取或设置 UI 元素轴心点 (Pivot) 相对于其锚点 (Anchors) 位置,例如:

c#
// 获取组件相锚点相对轴心的位置
Vector2 anchoredPosition = rectTransform.anchoredPosition;

在 Inspector 窗口第二行参数的右侧,有两个按钮,分别代表 Blueprintt Mode (蓝图模式) 和 Raw Edit Mode (原始编辑模式)。这两个模式的区别在于:

  1. 开启蓝图模式后,编辑和旋转不会影响矩形,只会影响显示内容。
  2. 开启原始编辑模式后,改变轴心和锚点值不会改变矩形位置。

三大基础控件

Image-图像控件

Image 是什么:

  • 是 UGUI 中用于显示精灵图片的关键组件。
  • 除了背景图等大图,一般都使用 Image 来线束 UI 中的图片元素。

相关参数:

  • Source Image:图片来源(图片类型必须是“Sprite 精灵”类型,可以在 Inspector 中设置)。

  • Color:图像的颜色。

  • Material:图像的材质(一般不修改,会使用 UI 的默认材质)。

  • Raycast Target:是否作为射线检测的目标(如果不勾选将不会响应射线检测)。

  • Maskable:是否能被遮罩(之后结合遮罩相关知识点进行讲解)。

  • Image Type:图片类型。

    • Simple:普通模式,均匀缩放整个图片。
    • Sliced:切片模式,9宫格拉伸,只拉伸中央十字区域(需要在图片上通过 Sprite Editor 设置 Border 边框)。

    TIP

    在 Unity 2019 及以上版本中,使用切片模式时需要安装 2D Sprite 包。

    下图为切片模式效果,左边为切片模式,右边为普通模式。可以观察到,通过设置 Border,边框区域不会被拉伸,而是保持原样,只有中央部分被拉伸。

    切片模式效果

    • Tiled:平铺模式,重复平铺中央部分。也可以通过设置 Border 边框来控制平铺样式。
    • Filled:填充模式。
      • Fill Method:填充方式
      • Fill Origin:填充原点
      • Fill Amount:填充量
      • Clockwise:顺时针方向
      • Preserve Aspect:保持原图宽高比
  • Use Sprite Mesh:使用精灵网格,勾选的话 Unity 会自动生成图片网格。一般用于 2D 游戏。

  • Preserve Aspect:确保图像保持其现有尺寸。

  • Set Native Size:设置为图片资源的原始大小。

通过代码控制:

c#
void Start()
{
    Image image = this.GetComponent<Image>();
    image.sprite = Resources.Load<Sprite>("ui_TY_fanhui_01");
    //其余均可点出来使用
}

TIP

需要将图片类型转换为 Sprite(精灵)才可以使用。

Text-文本控件

WARNING

新版 Unity 中 Text 已经被遗弃,转而使用 TextMeshProTextMeshPro 会在后续的课程中讲解。

特性TextTextMeshPro
渲染质量低分辨率,缩放时易模糊。高分辨率,缩放时保持锐利(SDF技术)。
性能轻量级,但大文本或复杂UI效率较低。优化更好,适合大量文本或动态内容。
富文本支持仅支持简单标签。支持复杂富文本(颜色、动画、超链接等)。
字体控制有限(依赖系统字体或动态字体)。支持自定义SDF字体、字距调整、基线控制等。
多语言支持基础支持(依赖字体字符集)。更好支持(如表情符号、特殊字符)。
动态布局需手动调整。支持自动换行、文本对齐、溢出处理等。
3D场景文本仅限UI Canvas。支持3D场景中的TextMeshPro组件。

常用

  • Text:显示的文本内容。
  • Font:字体资源,决定文本显示的字体。
  • Font Style:字体样式(如 Normal、Bold、Italic、Bold And Italic)。
  • Font Size:字体大小。
  • Line Spacing:行间距。
  • Rich Text:是否启用富文本格式。
  • Alignment:文本对齐方式(左、中、右、顶部、底部等)。
  • Raycast Target:是否响应射线检测。
  • Color:文本颜色。
  • Horizontal Overflow:水平溢出处理(Wrap 换行,Overflow 溢出)。
  • Vertical Overflow:垂直溢出处理(Truncate 截断,Overflow 溢出)。
  • Best Fit:自动调整字体大小以适应区域。

Rich Text 富文本开启后

可以以类似 html 的格式对文本进行编辑:

html
<i><b>1231</b>23123131123</i>

效果类似于:123123123131123

RawImage-原始图像控件

RawImage 是什么:

  • 是UGUI中用于显示任何纹理图片的关键组件
  • 它和 Image 的区别是一般 RawImage 用于显示大图(背景图,不需要打入突击的图片,网络下载的图等等),且图片不需要转化为 Sprite 类型。

组件参数:

  • Texture:图像纹理。
  • UV Rect:图像在 UI 矩形内的偏移和大小。
    • 位置偏移 X 和 Y (取值0~1)。
    • 大小偏移 W 和 H (取值0~1)。
    • 改变他们图像边缘将进行拉伸来填充 UV 矩形周围的空间。

通过代码控制:

c#
void Start()
{
    RawImage raw = GetComponent<RawImage>();
    raw.texture = Resources.Load<Texture>("ui_TY_erjikuang_01");
}

UGUI-组合控件

Button-组合控件

Button 是什么:

  • 是 UGUI 中用于处理玩家按钮相关交互的关键组件。
  • 默认创建的 Button 由2个对象组成:
    1. 父对象——Button组件依附对象 同时挂载了一个 Image 组件作为按钮背景图;
    2. 子对象——按钮文本(可选)。

TIP

由于旧的 Text 文本已经被遗弃,现在子对象上的组件是 TextMeshPro ,其余无大区别。

重要参数

  • Interactable:是否能够交互。
  • Transition:响应用户输入(选中,悬停等)的过渡效果。
    • None:没有状态变化效果。
    • ColorTint:用颜色表示不同的状态变化。
    • Sprite Swap:用图片表示不同状态的变化。
    • Animation:用动画表示不同状态的变化。
  • Navigation:导航模式,可以设置UI元素如何在播放模式中控制导航。

通过代码控制:

c#
void Start()
{
    Button btn = this.GetComponent<Button>();
    btn.interactable = true;
    Image img = this.GetComponent<Image>();
}

监听点击事件的两种方法:

  1. 通过在 Inspector 窗口拖拽的方式。
  2. 通过代码添加的方式。

INFO

在按钮区域按下再抬起算一次点击事件。

代码示例:

c#
public class ButtonClickExample : MonoBehaviour
{
 
    void Start()
    {
        Button btn = this.GetComponent<Button>();
        btn.interactable = true;

        // 通过代码监听事件
        btn.onClick.AddListener(ClickBtn2);
        btn.onClick.AddListener(() =>
        {
            print("通过表达式直接添加");
        });

        // 移除方法
        btn.onClick.RemoveListener(ClickBtn2);
    }

    // 通过拖代码的形式;必须为公共方法
    public void ClickBtn()
    {
        print("按钮点击,通过拖代码的形式");
    }
}

Toggle-开关控件

Toggle是什么:

  • 是 UGUI 中用于处理晚间单选框多选框相关交互的关键组件。
  • 可以通过配合 ToggleGroup 组件制作为单选框。
  • 默认个创建的 Toggle 由4个对象组成:
    • 父对象—— Toggle 组件依附
    • 子对象—— 背景图、选中图、说明文字(可选)

重要参数:

  • IsOn:当前是否处于打开状态。
  • Toggle Transition:在开关值变化时的过渡方式。
    • None:无任何过渡直接显示隐藏。
    • Fade:√ 淡入淡出效果。
  • Graphic:用于表示选中状态的图片。
  • Group:单选框分组;需要为所有单选项设置同一个 ToggleGroup 组件即可实现单选框。在 ToggleGroup 组件上勾选 Allow Switch Off 可以实现允许不选择任何选项。
  • 部分参数和 Button 组件相同,不再赘述。

通过代码控制:

c#
// 获取 Toggle 组件
Toggle tog = this.GetComponent<Toggle>();
tog.isOn = true;
print(tog.isOn);

// 获取 ToggleGroup 组件
ToggleGroup togGroup = this.GetComponent<ToggleGroup>();

//遍历当前选中的toggle
foreach (Toggle item in togGroup.ActiveToggles())
{
    print(item.name + " : " + item.isOn);
}

InputField-文本输入控件

WARNING

现在使用的也是 InputField(TMP),只在文本组件上有区别。

InputField 是什么:

  • 是 UGUI 中用于处理玩家文本输入相关交互的关键组件。
  • 默认创建的 InputField 由三个对象组成:
    • 父对象—— InputField 组件依附对象,以及同时在其上挂载了一个 Image 作为背景图。
    • 子对象—— 文本显示组件(必备)、默认显示文本组件(必备)。

相关参数:

  • TextComponent:用于关联显示输入内容的文本组件。
  • Text:输入框的起始默认值。
  • Character Limit:可以输入字符长度的最大值。
  • Content Type:输入的字符类型限制。
  • Line Type:行类型,定义文本格式。
  • Placeholder:关联用于显示初始内容文本控件。
  • Caret Blink Rate:光标闪烁速率。
  • Caret Width:光标宽度。
  • Custom Caret Color:自定义光标颜色。
  • Selection Color:批量选中的背景颜色。
  • Hide Mobile Input:隐藏移动设备屏幕上键盘(仅适用于 iOS)。
  • Read Only:设为只读。

代码控制相关:

获取 InputField 组件:

c#
TMP_InputField input = this.GetComponent<TMP_InputField>();
print(input.text);
input.text = "2233";

监听事件:

c#
input.onValueChanged.AddListener(ChangeValue);
input.onEndEdit.AddListener(EndInput);
// ↓ 新版额外可监听的事件 
input.onSelect.AddListener(SelectInput);
input.onDeselect.AddListener(DeselectInput);

TIP

新版有四个监听事件,前两个是一样的。

Slider-滑动条控件

Slider 是什么?

  • 是 UGUI 中用于处理滑动条相关交互的关键组件。
  • 默认创建的 Slider 由4组对象组成:
    • 父对象—— Slider 组件依附的对象。
    • 子对象—— 背景图、进度图、滑动块三组对象。

相关参数:

  • Fill Rect:进度条填充图形(显示当前值的填充区域)。
  • Handle Rect:滑动块图形(用户拖拽的控制柄)。
  • Direction:滑动条数值增加的方向。
    • Left To Right:从左到右。
    • Right To Left:从右到左。
    • Bottom To Top:从下到上。
    • Top To Bottom:从上到下。
  • Min ValueMax Value:滑动条的最小值和最大值。
  • Whole Numbers:是否限制滑动条的值只能为整数值(如1、2、3而非1.5)。
  • Value:滑动条当前值。
  • OnValueChanged:值变化时触发的事件列表(可绑定函数)。

通过代码获取 Value

c#
Slider s = this.GetComponent<Slider>();
print(s.value);

监听事件:

c#
s.onValueChanged.AddListener((v) =>
{
    print("通过代码添加的监听:" + v);
});

ScrollBar-滚动条

ScrollBar 是什么

  • 是 UGUI 中用于处理滚动条相关交互的关键组件。
  • 默认创建的 ScrollBar 由2组对象组成:
  • 父对象—— ScrollBar 组件与背景图所依附的对象。
  • 子对象—— 滚动块对象。

TIP

一般情况下我们不会单独使用滚动条,都是配合 ScrollView 滚动条视图来使用的。

相关参数:

  • Handle Rect:用于关联滑动条的滚动块(滑块)图形对象。
  • Direction定义滑动条数值增加的方向:
  • Left To Right:从左到右(默认)。
    • Right To Left:从右到左。
    • Bottom To Top:从下到上。
    • Top To Bottom:从上到下。
  • Value 滚动条的初始位置值(取值范围0~1)同时表示滚动块在滑动条中的比例大小(0~1)。
  • Number Of Steps 设置允许的滚动位置数量(离散间隔数)。 例如设置为5时,滑块只能停在0、0.25、0.5、0.75、1这5个位置。
  • OnValueChanged 值变化时触发的事件列表(可绑定函数)。

通过代码控获取 ValueSize

c#
Scrollbar scrollbar = this.GetComponent<Scrollbar>();
print(scrollbar.value);
print(scrollbar.size);

监听事件:

c#
scrollbar.onValueChanged.AddListener((v) =>
{
    print(v);
});

ScrollView-滚动视图

ScrollView 是什么:

  • 是 UGUI 中用于处理滚动视图相关交互的关键控件。
  • 默认创建的 ScrollView 由4组对象组成:
    • 父对象—— ScrollRect 组件依附的对象,还有一个 Image 组件作为背景图。
    • 子对象:
      • Viewport:控制滚动视图可视范围和内容显示。
      • Scrollbar Horizontal:水平滚动条。
      • Scrollvar Vertical:垂直滚动条。

相关参数:

  • Content:控制滚动视图显示内容的父对象,它的尺寸决定滚动视图能拖多远。
  • Horizontal:启用水平滚动(勾选框)。
  • Vertical:启用垂直滚动(勾选框)。
  • Movement Type:滚动视图元素的运动类型,控制拖动时的反馈效果。
    • Elastic(常用选项):回弹效果,当拖动到边缘后会弹回边界,可以设置回弹系数 Elasticity
    • Clamped:夹紧效果,始终限制在滚动范围内,没有回弹效果。
    • Unrestricted(不常用):不受限制的滚动模式。
  • Inertia:开启移动惯性,松开鼠标会有滑动惯性。可以设置 Deceleration Rate 来控制减速率(0~1),0表示没有惯性,1表示不会停止。
  • Scroll Sensitivity:控制鼠标滚轮和触摸板的滚动事件敏感性。
  • Viewport:关联滚动视图内容视口对象。
  • Horizontal Scrollbar / Vertical Scrollbar:关联水平滚动条对象。关联垂直滚动条对象。
    • Visibility:滚动条显示模式,包括:
      • Permanent:始终显示;
      • Auto Hide:自动隐藏);
      • Auto Hide And Expand Viewport:自动隐藏并扩展视口,当 Content 未超出视口时会自动使用滚动条所在区域填充内容。
    • Spacing:滚动条和视口之间的间隔空间大小。
  • OnValueChanged:滚动视图位置改变时执行的事件列表。
  • Unrestricted:不受限制的滚动模式(一般不使用)。
  • Clamped:夹紧效果,始终限制在滚动范围内,没有回弹效果。
  • Permanent:滚动条一直显示的模式。
  • Auto Hide:滚动条自动隐藏的模式。
  • Auto Hide And Expand Viewport:自动隐藏滚动条并且自动扩展内容视口的模式。

通过代码控制:

c#
ScrollRect sr = this.GetComponent<ScrollRect>();

// 改变内容的大小,具体可以拖动多少,都是根据它的尺寸来的
sr.content.sizeDelta = new Vector2(200, 200);

// 改变 Content 位置
sr.normalizedPosition = new Vector2(0, 0.5f);

监听控制(了解即可):

c#
sr.onValueChanged.AddListener(ChangeValue);

public void ChangeValue(Vector2 value) => print(value);

DropDown 是什么:

  • 是 UGUI 中用于处理下拉列表相关交互的关键组件。
  • 默认创建的 DropDown 由 4 组对象组成:
    • 父对象—— DropDown 组件依附的对象,还有一个 Image 组件作为背景图。
    • 子对象—— Label 是当前选项描述、Arrow 右侧小箭头样式、Template 下拉列表选单。

相关参数:

  • Template:关联下拉列表模板对象。
  • Caption Text:关联显示当前选项的文本组件。
  • Caption Image:关联显示当前选项的图片组件。
  • Item Text:关联选项列表中的文本控件。
  • Item Image:关联选项列表中的图片控件。
  • Value:当前选中选项的索引值。
  • Alpha Fade Speed:下拉列表淡入淡出动画速度。
  • Options:下拉列表中的选项集合。

通过代码获取选项信息:

c#
TMP_Dropdown dropdown = GetComponent<TMP_Dropdown>();
print(dropdown.value);
print(dropdown.options[dropdown.value]);

通过代码添加选项:

c#
dropdown.options.Add(new TMP_Dropdown.OptionData("123123"));

监听 onValueChanged 事件:

c#
dropdown.onValueChanged.AddListener((v) =>
{
    print("通过代码添加的监听:" + v);
});

图集制作

图集(Atlas)是什么:

简单来说,图集(Atlas),也经常被称为 精灵表(Sprite Sheet),是一种优化技术,它将许多零散的小图片(比如 UI 图标、道具、角色动画的序列帧)合并到一张更大的图片上。

多个 Sprite 都指向了同一张图集纹理,因而可以共享同一个材质。GPU 可以在不进行状态切换的情况下,一次性将它们全部绘制出来。这个过程,就是批处理 (Batching)。

通过使用图集可以减少 Draw Call:通常每个单独的纹理都需要一次 Draw Call,而图集可以将多个小图合并,大幅减少渲染调用次数,提升游戏性能。

什么是 Draw Call?为何会浪费性能?

Draw Call 本质上是 CPU 向 GPU 发出的一个“绘制”命令。

GPU 执行绘制操作本身是非常高效的,真正浪费性能的,是每次 Draw Call 之间,GPU 不得不进行的“状态切换”(Context Switching)。

性能浪费点总结:

  1. CPU 开销:CPU 需要找出要绘制的每个物体,准备好各自的渲染数据,再把它们一个一个发送给 GPU。物体越多,CPU 就越忙,没时间去处理游戏逻辑(比如 AI、物理计算)。

  2. 驱动程序开销:CPU 的指令不是直接给 GPU 的,中间还要经过显卡驱动程序的“翻译”。指令越多,“翻译”工作也越重。

  3. GPU 状态切换 (最主要的开销):每次 Draw Call 如果使用了和上一次不同的材质 (Material) 或纹理 (Texture),GPU 就必须:

    • 清空当前的工作状态。
    • 加载新的纹理到显存。
    • 设置新的着色器 (Shader) 和参数。
    • ...等等一系列准备工作。

图集的应用场景:

  • UI 界面中的各种图标、按钮、装饰元素
  • 角色/怪物的动画序列帧
  • 游戏道具、装备的图标
  • 粒子效果的纹理贴图
  • 地图中的小装饰物件

本篇会使用 Unity 自带的 Sprite Atlas 系统来制作图集。使用需要先安装 Unity 的 2D Sprite 包。

关于 Sprite Atlas 的历史(至2025.7.21)

大约在 Unity 2020.1 版本发布后,Unity 官方正式将旧的 Sprite Packer (基于Packing Tag) 标记为“已弃用” (Deprecated)。从那时起,对于所有新项目和新开发者来说,Sprite Atlas 就成了唯一且标准的图集解决方案。这也是本篇目前只介绍 Sprite Atlas 的使用方法。

创建图集:

在 Project 窗口中,在你希望存放它的位置(比如 Assets/SpriteAtlases 文件夹),点击右键,选择 Create -> Sprite Atlas。建议给它起一个有意义的名字,比如 MainMenu_Atlas 或 Player_Atlas。这个命名很重要,它能帮助你管理不同模块的图集。

TIP

尽量让图集的尺寸保持在 2 的幂次方。GPU 极其擅长处理以 2 为基础的运算。让纹理尺寸是 2 的幂次方,就是用它最喜欢、最高效的方式给它“喂”数据。

指定打包对象:

选中你刚才创建的 MainMenu_Atlas 资源,看向 Inspector 面板。你会看到下方有个区域叫做 Objects for Packing (打包对象)。

下方区域提示未在 Editor 里启用 Sprite Packer

如果下方区域提示未在 Editor 里启用 Sprite Packer,需要在 Project Settings -> Editor 中启用或者直接点击提示信息打开 Editor。

在 Sprite Packer 中,有4个选项:

模式优点缺点推荐指数
V1 - Always Enabled稳定可靠,编辑器/构建性能一致V1引擎打包速度可能较慢⭐⭐⭐⭐ (如果V2不稳定,这是首选)
V2 - EnabledV2引擎速度快,编辑器/构建性能一致V2可能在某些版本中存在实验性Bug⭐⭐⭐⭐⭐ (绝大多数情况下的最佳选择)
V1/V2 - Enabled for Builds进入播放模式最快编辑器性能与构建版本不符,无法准确调试⭐⭐ (仅在极端追求迭代速度的大型项目中考虑)

默认先使用 V2 - Enabled 模式就好,除非有特殊需求。

有两种方式可以添加对象到图集中:

  1. 打包整个文件夹(推荐)

    直接从项目窗口中,将我们里创建的那个存放图片的文件夹(ex: Assets/Sprites/MainMenu),整个拖拽到 Objects for Packing 列表里。

    在未来,你只要往这个文件夹里添加、删除或修改任何图片,Sprite Atlas 都会自动更新。你再也不用手动去管理列表了,这对于项目迭代和团队协作来说至关重要。

    这种方式也有助于维持项目逻辑的清晰:一个功能模块(如主菜单、玩家角色)的所有图片都放在一个文件夹里,对应一个图集,项目结构非常清晰。

  2. 打包单个文件

    你也可以把单个的精灵图片一个一个地拖拽到列表里。但当图片数量很多时,这种方式管理起来会非常痛苦。一般只在临时测试或有特殊需求时使用。

常用配置图集参数:

  1. Include in Build(必须勾选):勾选后,图集会在构建时打包到游戏中。如果不勾选,图集只在编辑器中使用,运行时不会加载。
  2. Allow Rotation:允许 Unity 在打包时旋转某些图片 90 度,以求得最紧凑的排列,节省空间。
  3. Tight Packing:允许 Unity 使用图片的实际轮廓而不是矩形边界来进行打包。
  4. Padding (内边距):设置每个精灵图片之间以及图片与图集边缘之间的最小像素间距。
  5. Show Platform Settings For (平台特定覆盖): 这是专业级优化的关键。你可以为不同平台(如 PC, Android, iOS)设置不同的压缩格式、压缩质量和最大尺寸。

TIP

当你绕过 Unity 的标准 UI 系统,去手动操作网格(Mesh)的 UV 坐标或使用特殊着色器(Shader) 时,Allow RotationTight Packing 才有可能导致“错误旋转”等问题。对于日常使用的 Image 组件和 Sprite Renderer,Unity 在后台为你处理好了一切,它会读取图片打包信息并自动对 UV 坐标进行补偿,把旋转过的图“转回来”。因此在开发普通 UI 时,你可以放心使用这两个选项。

下面是一些使用频率相对较低的参数 ↓

Type 参数

Type (类型):Master vs Variant

Type 属性允许你创建一种父子关系的图集,这在需要管理不同版本或风格的资源时非常强大。

Master Atlas (主图集)

  • 标准图集。它负责定义打包的结构——也就是哪些图片被打包进来,以及它们打包后的位置、旋转、裁切等所有布局信息。
  • 你可以把它想象成一个“蓝图”或“模板”。它规定了“贴纸簿”每一页的布局和上面应该有什么贴纸。

Variant Atlas (变体图集)

  • Variant Atlas 自身不决定任何打包结构。相反,它必须指定一个 Master Atlas 作为它的“主图集”。
  • 它的作用是“继承”主图集的完整布局,然后用自己列表中的图片去替换掉主图集中对应位置的图片。
  • 你可以把它想象成是“贴纸簿”的“皮肤”或“高清替换包”。它沿用原版贴纸簿的每一页布局,但把上面的所有贴纸都换成了新的图案。

一实际应用场景:高清(HD) / 标清(SD) 资源管理。

Texture 参数
  • Read/Write (读/写)

    • 作用: 勾选后,允许你通过 C# 脚本在 CPU 端访问和修改这张纹理的像素数据(使用 GetPixel, SetPixel 等函数)。
    • 代价: 这是一个极其昂贵的选项。它会在 CPU 内存中也创建一个纹理的副本,这会使这张纹理的内存占用直接翻倍。
    • 使用场景: 仅当你确定需要在运行时通过代码动态地改变纹理时才开启。例如:制作一个可以画画的画板、程序化生成纹理、将截图保存为图片等。
    • 结论: 对于普通的 UI 或精灵,永远不要勾选它。
  • Generate Mip Maps (生成 Mipmap)

    • 作用: Mipmap 是一系列预先计算好的、尺寸由大到小、逐渐模糊的纹理版本。它主要用于 3D 世界。当一个贴着此纹理的 3D 模型离摄像机很远时,GPU 会自动使用尺寸更小的 mipmap 版本来渲染,这样可以防止远处物体出现闪烁和锯齿(摩尔纹),并提升渲染性能。
    • 对 UI / 2D 的影响: 对于基本固定在屏幕上的 UI 元素,或者 2D 游戏中大小相对固定的精灵,我们不需要这个功能。开启它反而会额外增加约 33% 的内存占用。
    • 结论: 对于 UGUI 和大部分 2D 精灵,请关闭此选项。只有当你的精灵被放置在 3D 空间中,且会被摄像机从很远的地方观察时,才需要开启。
  • sRGB (Color Texture) (sRGB 色彩纹理)

    • 作用: 这个选项告诉 GPU,这张图片是用来表示“颜色”的(比如 UI 按钮的颜色、角色的皮肤颜色),它处在人眼视觉友好的 sRGB (Gamma) 空间。GPU 在进行光照计算时,会先将其转换到线性的色彩空间,以保证物理计算的正确性。
    • 什么时候关闭: 当你的纹理不代表颜色,而是代表纯粹的数据时。例如:法线贴图 (Normal Map)、高度图、金属度/粗糙度贴图等。这些数据图需要保持其原始的线性数值。
    • 结论: 对于所有用于显示的 UI 和精灵,请保持勾选。
  • Filter Mode (滤波模式)

    • 作用: 决定了当纹理被放大或缩小时,像素之间如何插值,影响最终的视觉效果。
    • Point (no filter): 不进行混合。像素边缘会非常清晰,放大后呈块状。像素艺术 (Pixel Art) 风格的游戏必选此项。
    • Bilinear: 对相邻的 4 个像素进行平滑混合。这是最常用的模式,放大后图像边缘会显得平滑但略带模糊。
    • Trilinear: 在双线性过滤的基础上,还会在两个最接近的 Mipmap 等级之间进行混合。只有在开启 Generate Mip Maps 时才有意义。可以提供最平滑的远景过渡效果。
    • 结论: 大多数高清 UI/精灵选择 Bilinear,像素风格选择 Point。
  • Max Texture Size (最大纹理尺寸)

    • 作用: 限制最终生成的图集大图的最大边长。例如,设置为 2048,那么图集的宽和高都不会超过 2048 像素。如果内容太多,Unity 会自动创建第二张、第三张图集(名字相同,但内部会编号)。
    • 一些旧的移动设备可能不支持超过 2048x2048 甚至 1024x1024 的纹理。为移动平台设置一个较低的尺寸上限可以保证兼容性。而 PC 平台则可以放心使用 4096 或更高。
  • Format (压缩格式)

    • 这是性能优化的核心。它决定了你的图集纹理在被 GPU 读取时使用的压缩算法。不同的平台对压缩格式的支持和效率是不同的。
    • Automatic: Unity 会根据平台自动选择一个它认为合适的格式,通常是比较安全的通用格式。
    • 手动选择:
      • PC (Standalone): 通常选择 DXT5 (也叫 BC3)。如果需要更高质量,可以选择 BC7。
      • Android: 优先选择 ASTC。它非常灵活,可以在压缩率和质量之间做很好的平衡。如果需要兼容非常老的设备,可以选择 ETC2。
      • iOS: 同样优先选择 ASTC。
      • RGBA 32 bit: 这是不压缩的格式。质量最高,但占用的显存也最大,通常只用于对图像质量有极端要求的特定情况。
  • Compression Quality (压缩质量)

    • Format 设置为 Automatic 或某些支持质量选择的格式(如 ASTC)时,这个选项才有效。
    • 它提供了 Low Quality, Normal Quality, High Quality 三个档位,让你在压缩速度和最终图片质量之间做取舍。通常选择 Normal Quality 就足够了。
  • Use Crunch Compression (使用 Crunch 压缩)

    • 作用: Crunch 是一种附加的、有损的压缩技术,它可以在 DXT 或 ASTC 等标准 GPU 压缩格式之上,进一步减小纹理在硬盘上的存储体积。
    • 工作原理: 当游戏加载时,需要先将 Crunch 格式解压成目标平台支持的普通 GPU 压缩格式(如 DXT),然后再加载到显存。
    • 优点: 能显著减小最终游戏包的大小。
    • 缺点: 可能会稍微增加初次加载这张资源的时间,并且因为是有损压缩,可能会轻微降低图像质量。
    • 结论: 对于那些对存储体积敏感的项目(如手游),这是一个非常有用的优化工具。你可以对一些次要的、或本身就比较模糊的背景图开启它,以换取更小的包体。

TIP

对于这些杂七杂八的参数很多的情况下,很多时候最好的做法就是保持默认设置,只做必要的修改。之后通过性能分析器(Profiler)找到问题,最后再“对症下药”地进行优化。

这种做法遵循了 KISS(Keep It Simple, Stupid) 原则和 不要过早优化(Do Not Prematurely Optimize) 原则。

预览和验证:

在 Inspector 面板的底部,点击 Pack Preview 按钮。Unity 会立即执行一次打包,并在下方的小窗口中显示出最终生成的大图集的样子。你可以看到所有小图是如何被排列组合的。

在项目中使用图集中的精灵:

可以像往常一样继续使用精灵,只不过现在它们都来自于图集。Unity 会自动处理从图集中加载精灵的细节。

不过也可以通过先加载图集,然后从中获取精灵的方式来使用图集,不过一般不会这样做。例如:

c#
// 加载图集资源
SpriteAtlas sa = Resources.Load<SpriteAtlas>("MyAtlas");

// 获取图集中的精灵 
sa.GetSprite("bk");

注意事项:

如果在渲染同一个图集的图片时,突然在渲染的顺序中插入了一个不同图集的图片,Unity 会先自动切换图集再切换回来,这会导致 Draw Call 增加。因此,尽量在渲染同一图集的图片时保持顺序一致。值得一提的是 UGUI 对该情况进行了优化,只要不同图集之间的图片没有交叉、重叠,就不会增加 Draw Call。

UGUI-进阶

UI 事件监听接口

UGUI 的事件接口是让你能够编写 C# 脚本来响应各种用户输入的“钩子”(Hooks)。

EventSystem 检测到用户的操作(如点击、拖拽)并确定了目标 UI 对象后,它就会检查该对象上的脚本是否实现了特定的事件接口。如果实现了,EventSystem 就会调用该接口定义的方法,从而让你能够执行自定义的交互逻辑。

为什么需要事件接口?

目前所有的控件都只提供了常用的事件监听列表,如果想做一些类似长按、双击、拖拽等功能是无法制作的,或者想让 ImageTextRawImage 三大基础控件能够响应玩家输入也是无法制作的。事件接口就是用来处理类似问题的,让所有控件都能够添加更多的事件监听来处理对应的逻辑。

指针事件接口

这些接口主要处理鼠标和触摸相关的交互:

接口名称触发时机方法名称
IPointerEnterHandler指针进入游戏对象OnPointerEnter
IPointerExitHandler指针离开游戏对象OnPointerExit
IPointerDownHandler指针按下瞬间OnPointerDown
IPointerUpHandler指针松开(需在原本按下的对象上)OnPointerUp
IPointerClickHandler完整点击(按下 → 松开)OnPointerClick

拖拽事件接口

专门处理拖拽操作的接口:

接口名称触发时机方法名称
IBeginDragHandler开始拖拽OnBeginDrag
IDragHandler拖拽过程中(每帧)OnDrag
IEndDragHandler拖拽结束OnEndDrag

其他交互事件接口

接口名称触发时机方法名称
IInitializePotentialDragHandler找到拖拽目标时,初始化拖拽操作OnInitializePotentialDrag
IDropHandler拖拽物体释放到目标上OnDrop
IScrollHandler鼠标滚轮滚动OnScroll
ISelectHandler对象被选中(Tab 键/手柄选择)OnSelect
IDeselectHandler对象取消选中OnDeselect
IUpdateSelectedHandler选中状态下持续调用(每帧)OnUpdateSelected
IMoveHandler方向移动事件(导航操作)OnMove
ISubmitHandler确认/提交按钮(Enter /手柄 A 键)OnSubmit
ICancelHandler取消按钮(Esc /手柄 B 键)OnCancel

使用事件接口

基本使用示例:

c#
using UnityEngine;
using UnityEngine.EventSystems;

public class UIEventHandler : MonoBehaviour, 
    IPointerEnterHandler, IPointerExitHandler, 
    IPointerDownHandler, IPointerUpHandler, IPointerClickHandler
{
    public void OnPointerEnter(PointerEventData eventData)
    {
        Debug.Log("鼠标进入");
        // 可以在这里改变 UI 外观,比如高亮显示
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        Debug.Log("鼠标离开");
        // 注意:在移动设备上不存在"悬停"的概念
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        Debug.Log($"鼠标按下 - 按键ID: {eventData.pointerId}");
        // 0 = 左键,1 = 右键,2 = 中键
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        Debug.Log("鼠标抬起");
    }
    
    public void OnPointerClick(PointerEventData eventData)
    {
        Debug.Log("完整点击事件");
        // 只有在同一对象上完成"按下→抬起"才会触发
    }
}

拖拽功能示例:

DragHandler.cs
c#
using UnityEngine;
using UnityEngine.EventSystems;

public class DragHandler : MonoBehaviour, 
    IBeginDragHandler, IDragHandler, IEndDragHandler
{
    private Vector3 originalPosition;
    
    public void OnBeginDrag(PointerEventData eventData)
    {
        originalPosition = transform.position;
        Debug.Log("开始拖拽");
    }
    
    public void OnDrag(PointerEventData eventData)
    {
        // 跟随鼠标位置移动
        transform.position = eventData.position;
    }
    
    public void OnEndDrag(PointerEventData eventData)
    {
        Debug.Log("拖拽结束");
        // 可以在这里判断是否拖拽到有效区域
    }
}

PointerEventData 重要参数

PointerEventData 是事件接口方法的参数,包含了丰富的事件信息:

参数名称类型说明
pointerIdint指针事件ID:0 = 左键,1 = 右键,2 = 中键
positionVector2当前指针的屏幕坐标(原点在屏幕左下角)
pressPositionVector2指针按下时的初始屏幕坐标,常用于计算拖动距离
deltaVector2指针移动增量(当前帧与上一帧的位置差)
clickCountint连续点击次数,可用于实现双击检测
clickTimefloat点击发生的时间(Unity时间系统)
pressEventCameraCamera触发 OnPointerPress 事件的摄像机
enterEventCameraCamera触发 OnPointerEnter 事件的摄像机

使用示例:

c#
public void OnPointerDown(PointerEventData eventData)
{
    // 判断是否为右键点击
    if (eventData.pointerId == 1)
    {
        Debug.Log("右键点击");
    }
    
    // 获取点击位置
    Vector2 clickPos = eventData.position;
    Debug.Log($"点击位置: {clickPos}");
    
    // 检测双击
    if (eventData.clickCount == 2)
    {
        Debug.Log("双击检测");
    }
}

事件接口总结

优点:

  • 提供了比内置事件更丰富的交互功能
  • 可以让基础控件(Image、Text等)响应用户输入
  • 能够实现长按、双击、拖拽等高级交互
  • 事件信息详细,便于实现复杂逻辑

缺点:

  • 需要自己编写脚本继承接口
  • 管理相对麻烦,每个需要事件的对象都要挂载脚本
  • 代码量相对较大

EventTrigger 事件触发器

EventTrigger 是一个集成了上节课中学习的所有事件接口的脚本组件,可以让我们更加方便的为控件添加事件监听,而无需自己编写继承接口的脚本。

使用事件触发器

可以通过拖曳脚本的方式为 EventTrigger 添加事件监听。

例如我们有一个图像对象,其父对象是一个面板 panel。如果我们希望为这个图像添加对应事件,可以为图片对象添加 EventTrigger 组件,然后在 panel 中编写方法并注册到 EventTrigger 中。

EventTrigger 组件中,选择对应类型的事件,然后就可以将 panel 中的方法拖拽到事件列表中。方法例如:

c#
public void TestPointEnter(BaseEventData data)
{
    PointerEventData eventData = data as PointerEventData;
    print("鼠标进入"+ eventData.position);
}

此外,还可以通过代码的方式添加事件监听。通过 EventTrigger.Entry 来创建一个新的事件条目,并将其添加到 EventTrigger.triggers 列表中实现事件的注册。例如:

c#
public class Panel : MonoBehaviour
{
    [SerializeField] private EventTrigger eventTrigger;

    private void Start()
    {
        // 在父对象 panel 的脚本中监听 EventTrigger 的事件
        EventTrigger.Entry entry = new EventTrigger.Entry
        {
            eventID = EventTriggerType.PointerUp
        };
        entry.callback.AddListener(baseEventData => print("抬起"));
        
        eventTrigger.triggers.Add(entry);
    }
}

屏幕坐标转 UI 相对坐标

RectTransformUtility 公共类是一个 RectTransform 的辅助类,主要用于进行一些坐标的转换等等操作,其中对于我们目前最重要的函数是:将屏幕空间上的点,转换成 UI 本地坐标下的点。

通过 RectTransformUtility 中的方法 ScreenPointToLocalPointInRectangle() 可以将屏幕坐标转换为相对于父对象的本地坐标。

方法完整的定义方式:

c#
RectTransformUtility.ScreenPointToLocalPointInRectangle(
    this.transform.parent as RectTransform, eventData.position,
    eventData.enterEventCamera, out nowPos);
  • 参数1:父对象的 RectTransform
  • 参数2:屏幕坐标 (如鼠标位置)
  • 参数3:事件摄像机(一般为 eventData.enterEventCamera
  • 参数4:最终得到的点

TIP

一般配合拖拽事件使用

c#
public void OnDrag(PointerEventData eventData)
{
    Vector2 nowPos;
    RectTransformUtility.ScreenPointToLocalPointInRectangle(
    this.transform.parent as RectTransform, eventData.position,
    eventData.enterEventCamera, out nowPos);
    this.transform.localPosition = nowPos;
}

Mask 遮罩

遮罩使图片只显示自身的一部分。

通过在父对象上添加 Mask 组件,子对象勾选 Maskable(默认开启),可以实现遮罩其子对象。需要注意父对象透明的部分会被遮罩,只有不透明的部分会显示。

模型和粒子显示在 UI 之前

方法一:修改Canvas渲染模式

  1. 将Canvas的 Render Mode 改为 Screen Space - Camera
  2. 指定一个专用的UI摄像机(避免与主场景摄像机混用)。
  3. 调整渲染层级:
    • Tags and Layers 设置中创建新的 Sorting Layer
    • 将UI放在下层,模型放在上层。

方法二:渲染到纹理

  1. 使用专门的摄像机渲染3D模型。
  2. 将渲染结果输出到 Render Texture
  3. 通过UI Image 组件显示纹理(类似小地图的制作方式)。

粒子特效号可以通过设置 Particle SystemRenderer 组件中的 Sorting LayerOrder in Layer 来控制其渲染顺序。

异形按钮

什么是异形按钮

异形按钮是指图片形状不是传统矩形的按钮,如圆形、多边形或其他不规则形状。

实现精确点击检测

方法一:子对象拼接法

原理: 按钮的点击检测基于图片的矩形范围判断,且检测范围采用自下而上的层级判断,所以子对象的图片范围也会被算入可点击范围。

实现步骤:

  1. 创建多个透明图片作为按钮的子对象。
  2. 用这些透明图片拼接出不规则形状。

方法二:透明度阈值法

⚠️ 注意
该方法相对第一种方法会消耗更多内存资源

实现步骤:

  1. 在 Inspector 中开启图片的 Read/Write Enable 选项。

  2. 为控件(ex::按钮)编写脚本,通过代码修改子对象图片透明度的响应阈值,例如:

    c#
    // 透明度小于 0.1f 的像素点不会响应点击事件
    img.alphaHitTestMinimumThreshold = .1f;

参数说明:

  • alphaHitTestMinimumThreshold:设置像素点被射线检测的最小 alpha 值。
  • 当像素点的 alpha 值小于设定值时,该像素点不会响应射线检测。
  • 简单来说:透明或半透明区域不会响应点击事件。

自动布局组件

什么是自动布局

虽然 UGUI 的 RectTransform 已经非常方便地帮助我们快速布局,但 UGUI 中还提供了很多可以帮助我们对UI控件进行自动布局的组件。

这些组件可以帮助我们自动设置UI控件的位置和大小等属性。

工作原理:

  • 自动布局控制组件 + 布局元素 = 自动布局
  • 自动布局控制组件:Unity提供的用于自动布局管理的组件
  • 布局元素:具备布局属性的对象,主要指具备 RectTransform 的UI组件

水平/垂直布局组件 (Horizontal/Vertical Layout Group)

主要参数:

  • Padding:控制布局边缘的偏移量(左、右、上、下)
  • Spacing:子对象之间的间距(单位:像素)
  • Child Alignment:子对象的对齐方式(九宫格定位,如居中、靠左、靠右等)
  • Control Child Size:是否强制控制子对象的宽度(Horizontal)或高度(Vertical)以填充
  • Use Child Scale:布局时是否考虑子对象的缩放(若子对象被缩放,勾选后按缩放后尺寸计算布局)
  • Child Force Expand:是否强制子对象填充剩余空间(勾选后子对象会均分额外空间),在不设置 Layout Element 的情况下不会影响子对象的原始尺寸。

效果展示:水平布局效果

此外还可以通过给子对象添加 Layout Element 组件来控制子对象的最小/最大宽度和高度等布局属性。

网格布局组件 (Grid Layout Group)

基础参数:

  • Padding:控制网格边缘的偏移量(左、右、上、下)
  • Cell Size:每个格子的固定大小(宽度 × 高度)
  • Spacing:格子之间的间隔(水平间距、垂直间距)

起始位置设置:

  • Start Corner:第一个子对象的起始位置
    • Upper Left(左上)
    • Upper Right(右上)
    • Lower Left(左下)
    • Lower Right(右下)

排列方向:

  • Start Axis:子对象的排列方向
    • Horizontal:水平排列,填满一行后换行
    • Vertical:垂直排列,填满一列后换列

对齐方式:

  • Child Alignment:网格整体的对齐方式(九宫格定位,如居中、靠左等)

约束模式:

  • Constraint
    • Flexible:灵活模式,自动调整行列数量以适应容器大小
    • Fixed Column Count:固定列数模式,限制网格的列数
    • Fixed Row Count:固定行数模式,限制网格的行数

内容大小适配器 (Content Size Fitter)

核心功能: 自动调整UI元素(如Text)的 RectTransform 尺寸,使其适配内容大小。通常用于动态文本或配合布局组件使用。

主要参数:

  • Horizontal Fit(水平适配模式):

    • Unconstrained:不自动调整宽度
    • Min Size:按子对象最小宽度调整
    • Preferred Size:按子对象理想宽度调整(如文本自然宽度)
  • Vertical Fit(垂直适配模式):

    • 选项同水平模式(Unconstrained / Min Size / Preferred Size

宽高比适配器 (Aspect Ratio Fitter)

核心功能: 控制UI元素按固定宽高比自动调整尺寸,确保内容比例不变形。适用于需要保持特定比例的图像、视频等UI元素。

主要参数:

  • Aspect Mode(适配模式):

    • None:禁用比例适配
    • Width Controls Height:以宽度为基准自动计算高度
    • Height Controls Width:以高度为基准自动计算宽度
    • Fit In Parent:在父容器内完整显示(可能产生黑边)
    • Envelope Parent:填满父容器(可能内容被裁剪),可以用来做背景图
  • Aspect Ratio(宽高比):

    • 手动设置宽度与高度的比值(如 16:9 = 1.78,4:3 = 1.33)

Canvas Group

使用目前学过的知识点,无法方便快捷地设置面板的整体淡入淡出效果,因此需要 CanvasGroup 来解决这个问题。

为面板添加 CanvasGroup 组件,即可实现整体控制效果。

主要参数:

  • Alpha(透明度)

    • 范围:0-1,控制组内所有UI的全局透明度
    • 不影响子对象单独设置的Alpha值
  • Interactable(交互开关)当 Match = 0.5 时

    • 禁用时组内所有按钮/输入框等交互组件失效
    • 失效的控件会显示为灰色状态
  • Blocks Raycasts(射线阻挡)

    • 关闭时,组内UI不会阻挡射线检测
    • 相当于将所有子对象的 Raycast Target 设为 false
  • Ignore Parent Groups(忽略父组)

    • 启用时不受父对象 CanvasGroup 属性影响
    • 让当前组独立作用

插件

  1. DoTween
    • 一个强大的动画插件,支持各种类型的动画效果。
    • 可以轻松实现 UI 元素的平移动画、缩放动画、颜色渐变等。

实践技巧

创建专门渲染 UI 的摄像机

设置一个专门渲染 UI 的摄像机与主摄像机分离能带来更好的灵活性和逻辑上的清晰度。

我们可以创建一个新的摄像机,并且将主摄像机和 UI 摄像机的 Clear Flags 设置为 Depth Only,再修改主相机和 UI 摄像机的 Culling Mask,使其只渲染各自需要的层。

然后设置 CanvasRender ModeScreen Space - Camera,这种模式下,Canvas 会使用指定的摄像机进行渲染,并且方便我们以后在 UI 上添加 3D 元素。然后,可以在 Canvas Scaler 中设置 UI Scale ModeScale With Screen Size,然后修改 Reference Resolution 使 UI 能够根据屏幕分辨率自动缩放,这个数值可以根据实际需求调整,比如可以设置背景图大小。对于下方的 Screen Match Mode,可以设置为 Match Width Or Height,然后根据实际需求调整 Match 的值,比如横屏游戏设置为 1,竖屏游戏设置为 0。

创建 UI Manager 管理器

首先为了方便管理 UI 元素和逻辑上的清晰,我们先来管理一下 UI 元素。

我们在 Hierarchy 窗口中创建两个空的 GameObject,分别命名为 [UI][Managers]。然后将所有 UI 元素(ex:Canvas, EventSystem, UICamera)放入 [UI] 下,将所有管理器脚本(ex:_UIManager)放入 [Managers] 下。

先创建一个 GameManager 脚本,挂载到 [Managers] 下,然后为其添加如下代码防止切换场景时自身被删除:

GameManager.cs
c#
using UnityEngine;

public class GameManager: MonoBehaviour
{
    private void Awake()
    {
        DontDestroyOnLoad(gameObject);
    }
}
UIManager.cs
c#
using System.Collections.Generic;
using System.Threading.Tasks;
using MyGame.UI;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

namespace MyGame.Managers
{
    /// <summary>
    /// UI 管理器,负责所有 UI 面板的加载、显示、隐藏和卸载。
    /// 采用单例模式,并使用 Addressables 进行异步资源管理。
    /// </summary>
    public class UIManager : MonoBehaviour
    {
        // 单例模式
        public static UIManager Instance { get; private set; }
    
        // [UI] 引用
        [SerializeField] private Transform uiRoot;

        // 画布的 Transform 引用
        [SerializeField] private Transform canvasTrans;

        // 面板字典,存储已加载的面板
        private Dictionary<string, BasePanel> _panelDict;

        private void Awake()
        {
            // 确保只创建一个 UIManager 实例
            if (Instance != null)
            {
                Debug.LogWarning("UIManager 已经存在,不会重复创建。");
                Destroy(gameObject);
                return;
            }

            Instance = this;

            // 确保画布存在
            if (canvasTrans == null)
            {
                canvasTrans = GameObject.Find("Canvas")?.transform;
                if (canvasTrans is null)
                {
                    Debug.LogError("UIManager 未能找到 Canvas!请在 Inspector 中手动指定。");
                }
            }

            if (uiRoot == null)
            {
                uiRoot = new GameObject("[UI]").transform;
                if (uiRoot == null)
                {
                    Debug.LogError("UIManager 无法创建 UI 根对象,请检查场景设置。");
                }
                else
                {
                    DontDestroyOnLoad(uiRoot);
                }
            }
        
            _panelDict = new Dictionary<string, BasePanel>();
        }

        /// <summary>
        /// 异步加载并显示指定类型的 UI 面板。
        /// 如果面板已加载,则直接返回已加载的实例。
        /// 面板资源通过 Addressables 系统加载,并挂载到指定的 Canvas 下。
        /// </summary>
        /// <typeparam name="T">面板类型,需继承自 BasePanel。</typeparam>
        /// <returns>
        /// 返回异步任务,任务结果为面板实例(T)。
        /// 如果加载失败或未找到 BasePanel 组件,则返回 null。
        /// </returns>
        public async Task<T> ShowPanelAsync<T>() where T : BasePanel
        {
            string panelName = typeof(T).Name;
            if (TryGetPanel(out T panel))
            {
                Debug.LogWarning($"面板 {panelName} 已经加载,无需重复加载。");
                return panel;
            }

        
            // 异步加载面板资源
            AsyncOperationHandle<GameObject> handle = Addressables.InstantiateAsync("UI/" + panelName, canvasTrans);
            print(panelName);
            GameObject panelGo = await handle.Task;

            // 检查加载结果
            if (handle.Status != AsyncOperationStatus.Succeeded || panelGo == null)
            {
                Debug.LogError($"加载或实例化面板失败: {panelName}");
                return null;
            }

            // 确保面板上有 BasePanel 组件并获取该组件
            panel = panelGo.GetComponent<T>();
            if (panel == null)
            {
                Debug.LogError($"面板 {panelName} 上未找到 BasePanel 组件。");
                Addressables.ReleaseInstance(panelGo);
                return null;
            }

            _panelDict[panelName] = panel;
            panel.Show();
            return panel;
        }

        /// <summary>
        /// 隐藏并卸载指定类型的 UI 面板。
        /// 如果面板未加载,则不会执行任何操作。
        /// 面板隐藏后会释放其实例资源(通过 Addressables)。
        /// </summary>
        /// <typeparam name="T">面板类型,需继承自 BasePanel。</typeparam>
        public void HidePanel<T>() where T : BasePanel
        {
            string panelName = typeof(T).Name;
            if (!TryGetPanel(out T panel))
            {
                Debug.LogWarning($"面板 {panelName} 未加载,无法隐藏。");
                return;
            }

            panel.Hide(() => Addressables.ReleaseInstance(panel.gameObject));
            _panelDict.Remove(panelName);
        }

        /// <summary>
        /// 尝试从已加载的面板字典中获取指定类型的面板实例。
        /// </summary>
        /// <param name="panel">输出参数,返回找到的面板实例;如果未找到则为 null。</param>
        /// <typeparam name="T">面板类型,需继承自 BasePanel。</typeparam>
        /// <returns>如果找到面板则返回 true,否则返回 false。</returns>
        public bool TryGetPanel<T>(out T panel) where T : BasePanel
        {
            string panelName = typeof(T).Name;
            if (_panelDict.TryGetValue(panelName, out BasePanel basePanel))
            {
                panel = basePanel as T;
                return panel != null;
            }

            panel = null;
            return false;
        }
    }
}

外部字体导入

先获得你想使用的字体文件(.ttf 或 .otf 格式 或 .ttc),然后将其放入 Unity 项目的 Assets 文件夹中。图方便的话字体也可以在 C:\Windows\Fonts 这个目录下找。

导入字体后,在 Project 窗口右键你的字体文件选择 Create -> TextMeshPro -> Font Asset,然后会生成一个新的字体资产文件,可以被使用在 TMP 中。

接下来修改字体资产的图集分辨率。找到 Generation Setting 中的 Atlas Width,更改为 8192 (或更高),然后点击 Apply 按钮。更高清的图集分辨率可以让字体在高分辨率屏幕上显示得更清晰。

此外可以通过Windows -> TextMeshPro 里的 Font Asset Creator 进行更高级的设置和优化。这里暂时先不展开(挖坑)。

排序文字和图片时减少 DC

在渲染同一个图集的图片时,如果突然插入渲染一个文字或不同图集的图片,Unity 会先自动切换图集再切换回来,这会导致 Draw Call 增加。因此,尽量在渲染同一图集的图片时保持顺序一致。文字和图片也分开渲染,避免和图片混合渲染增加 Draw Call。

例如:

> Camera
> Canvas
    > Panel
    > Image1.1
    > Image1.2
    > Text1
    > Text2
    > Image2.1
    > Image2.2
    > ..
> EventSystem

当然如果是小型项目,或者图片数量不多,Draw Call 数量也不多,且实现该方法不方便,那么可以不必过于强求。对于大型项目,或者图片数量较多的项目,会有更好的方法,而不是简单通过排序来减少 Draw Call。

Aspect Ratio Fitter

Aspect Ratio Fitter 组件可以帮助我们保持 UI 元素的宽高比,确保在不同分辨率下仍然保持正确的比例。

Comming Soon...

Grid

Grid 组件可以帮助我们创建网格布局,自动排列子对象。它可以与 Horizontal Layout GroupVertical Layout Group 结合使用,来实现更复杂的布局。

Comming Soon...

Content Size Fitter

Content Size Fitter 组件可以自动调整 UI 元素的大小,以适应其内容。它通常与 Layout Group 组件一起使用,以确保子对象的大小和位置正确。

Comming Soon...