一、六大基础组件
Canvas上的三个组件

1.1 Canvas 画布组件
Canvas组件是画布组件,是其他UI控件的根对象
Canvas组件 用来渲染UI元素,是UI元素能被显示的根本,在创建UI控件时会自动创建Canvas和EventSystem组件

1.Canvas 组件的作用:
- Canvas组件 负责渲染自己的UI子对象,当UI控件对象不是Canvas的子对象时,该控件将不被渲染
- Canvas组件 决定了UI组件的渲染位置
- 场景中可以存在多个Canvas组件 ,可以分别管理不同画布的渲染方式,分辨率自适应等
如图,在右图中解除image(1)与Canvs的父子关系,会发现Image(1) 不被渲染
2.Canvas 组件的三种渲染模式及相关参数
- Screen Space - Overlay:屏幕空间,覆盖模式,UI始终在前面

Screen Space - Overlay参数解释
- Pixel Perfect:是否开启无锯齿精确渲染,开启会提高清晰度,但相对的会消耗性能
- SortOrder:排序层编号,当存在多个Canvas时,编号小的先渲染
- TargetDisplay:目标设备,可以指定该画布显示在哪一个设备上
- Additional Shader Channels:其他着色器通道,决定着色器可以读取哪些数据
- Vertex Color Always:强制让 Shader 始终读取并应用 UI 的顶点颜色
- Screen Space - Camera: 屏幕控件,摄像机模式,3D物体可以显示在UI之前

Screen Space - Camera参数解释
- Render Camera:指定渲染 UI 的相机,建议不要使用著摄像机,这可能会导致3D物品距离太近出现在UI前面
- Plane Distance:UI平面在摄像机前方的距离,类似整体Z轴的感觉
- Target Display:指定 UI 显示的目标屏幕
- Sorting Layer/Order in Layer:控制 UI 与 3D 物体的层级顺序
- World Space:世界空间,可以实现UI面板围绕玩家旋转或跟随移动

World Space 参数解释
- Event Camera:处理 UI 交互的相机(通常为主相机)
- 可通过 Transform 直接调整位置、旋转、缩放,支持与 3D 场景融合
1.2 CanvasScaler 画布缩放组件
1.2.1 CanvasScaler的作用
CanvasScaler是画布缩放控制器,用于实现分辨率自适应,只作用UI控件的大小,不会管理控件的位置
1.2.2 CanvasScaler的三种缩放模式
- Constant Pixel Size(恒定像素),该模式下无论屏幕大小如何,UI始终保持相同像素大小

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

- Scale With Screen Size(随屏幕缩放),该模式会根据屏幕尺寸进行缩放,以实现分辨率自适应的效果

Scale With Screen Size参数解释
- Reference Resolution:参考分辨率
- Screen Match Mode:屏幕匹配模式,当前屏幕分辨率宽高比不适应参考分辨率时,用于分辨率大小自适应的匹配模式,包含Expand、Shrink、Match Width Or Height三种模式
Expand:水平或垂直拓展画布区域,会根据宽高比的变化来放大缩小画布,可能有黑边,会缩小UI元素,让其完整的出现在屏幕上
Shrink:水平或垂直裁剪画布区域,会根据宽高比的变化来放大缩小画布,可能会裁剪

二者的对比,其中左图为Expand模式,右图为Shrink模式
Match Width Or Height:以宽高或者二者的平均值作为参考来缩放画布区域
- Constant Physical Size(恒定物理尺寸), 无论屏幕大小和分辨率如何,UI元素始终保持相同物理大小

参数解释
- DPI:(Dots Per Inch,每英寸点数)图像每英寸长度内的像素点数
- Physical Unit:物理单位,使用的物理单位种类
- Fallback Screen DPI:备用DPI,当找不到设备DPI时,使用此值
- Default Sprite DPI:默认图片DPI
3.补充
如何查看画布大小 和缩放系数: 选中Canvas对象后在RectTransform组件中看到画布的宽高和缩放

如何查看屏幕分辨率:在Game窗口的States中Screen中查看(屏幕分辨率 = 宽高 * 缩放系数)

什么是参看分辨率:用于参与分辨率自适应计算的,可以在缩放模式为Scale With Screen Size(随屏幕缩放)时查看,一般设置为屏幕分辨率

1.2.3 CanvasScaler中恒定像素模式 和 恒定物理模式区别
相同点:他们都不会进行缩放,因此在不改变缩放系数时,他们的显示大小和图片大小一致,因此他们都不会进行分辨率自适应
1.3 Graphic Raycaster 图形射线投射组件
作用: 用于检测UI输入事件的射线发射组件, 主要负责通过射线检测玩家和UI元素的交互,判断是否点击到了UI元素
参数:
- Ignore Reversed Graphics:是否忽略反转图形
- Blocking Objects:射线被哪些类型的碰撞器阻挡(在屏幕空间-覆盖模式下无效)
- Blocking Mask:射线被哪些层级的碰撞器阻挡(在屏幕空间-覆盖模式下无效)
EventSystem 上的两个组件

1.4 EventSystem 事件系统组件
作用:管理玩家的输入实践,并将其分发给UI控件,如按钮的点击、拖拽、滑动等,是UI交互的基础
没有EventSystem,UI将不能交互,如我之前在换场景时,UI面板成功的过来了,但是不能点击,检查后射线投射正常,且没有遮挡,最后发现是EventStstem丢失的原因
参数:
- First Selected:游戏启动时默认选择的UI对象
- Send Navigation Event:是否开启导航事件,开启后可以通过实现通过WASD来切换选中、确定、取消等操作
- Drag Threshold:拖拽操作的阈值,只有超过该阈值才能认定为有效拖拽
1.5 Standalone Input Module 独立输入组件
作用:用来监听设备的输入,与EventSystem配合使用
1.6 RectTransform 矩形变换组件
作用:继承 transform,是用于处理UI元素位置大小相关的组件。相较于transtorm新增了中心点,锚点,长宽等属性
参数:
2.Pivot:轴心点,取值范围为 0~1,他决定了UI组件的中心位置,若UI组件进行旋转,将会以轴心点作为旋转轴

3.Pos(X,Y,Z):轴心点(中心点)相对锚点的位置,Width/Height:矩形的宽高,在这里坐标原点为锚点位置

4.Anchors:相对父矩形的锚点,他包含以下两组属性,其取值范围都是0~1,他们会存在4个锚点,这四个点共同组成一个矩形(如图所示的黑色矩形)

Min是矩形锚点范围X和Y的最小值,即坐下角坐标
Max是矩形锚点范围X和Y的最大值,即右上角坐标
当不存在重合锚点时Pos X/Pos Y/ Width/Height会转变为Left/Top/Right/Bottom,他们代表矩形边缘相对于锚点的位置如图所示,这里Left代表着精灵图像的左边界距离黑色矩形左边的距离为64,其余的Top/Right/Button分别代表其距离对应边的距离

二、三大基础控件(Image、Text、RawImage)
2.1 Image图像控件
作用:Image是ugui中用来显示精灵图片的组件,除了背景图和一些大图外,都会使用Image显示UI中的图片元素
2.1.1 Image参数相关:
SourceImage 图片来源,关联的图片类型必须是sprite精灵类型
Color 图像颜色,会为对应精灵图像叠加一种颜色,默认为白色,不进行颜色叠加
Raycast Target 是否进行射线检测,当勾选时将进行射线检测,此时如果该控件下面有其他控件,将无法与其交互
Image Type 图片类型,包含Simple普通模式、Sliced切片模式、Tiled平铺模式、Filled填充模式
Image Type的三种不同模式
Simple 普通模式,会均匀的缩放整张图片,适用于希望图片以原始比例显示
Sliced 切片模式,需要先对精灵图像进行切片,之后拉伸精灵图将会只拉伸切片后的中央十字部分,如图可以观察到中间有红色的地方被明显拉伸,而其余四个区域可以观察左小角的箭头,这个就没有被拉伸(颜色不一样是应为上面的Color叠加了一个绿色的原因)

Tiled 平铺模式,该模式下会重复平铺(这里恢复了图片的切片)

Filled 填充模式,这个可以实现图片按某种样式填充,可以模拟技能冷却

注意,进行图像切片时需要下载对应的2D包,下载方式如图

2.1.2 代码控制
可以通过获取Image组件对象,对其中的参数控制,如将Bune的精灵图像改为MushRoom
//获取脚本关联对象上的Image组件
Image img = this.GetComponent<Image>();
img.sprite = Resources.Load<Sprite>("MushRoom");
//设置图像大小
(transform as RectTransform).sizeDelta = new Vector2(200, 200);
2.2 Text 文本显示控件
这里的Text组件是指Text Mash Pro组件提供的文本控件,使用时需要导入包,在我的Unity版本中原版的Text控件被删除

Text Mash Pro的相关参数

Font Size 为字体大小,Auto Size将忽视字体大小自动设置合适大字体大小
Vertex Color 为显示文字的颜色,Color Gradient 为颜色渐变
Override Tags 是否允许文字溢出,勾选后文字可以溢出

Alignment 文字对齐方式,以实现 左对齐 / 居中 / 右对齐 / 两端对齐,还有垂直方向的上 / 中 / 下对齐
Rich Text 富文本开关,开启后允许使用富文本

2.3 RawImage 大图显示控件
RawImage 是Unity中用于直接渲染Texture纹理的UI控件,可以用于相机画面的显示=》可以做地图
三、组合控件
3.1 Button 按钮控件
Button是UGUI中用于处理玩家按钮交互的组件,是由两个对象组成 如图所示

, 其中父Button上挂载Image和Button两个组件,Image是按钮的背景图,子对象Text用于按钮上的文本显示
3.1.1 Button组件上的参数
- Interactable,bool类型,表示该按钮是否可以交互
- Transition,按钮交互时的状态类型,包含None、Color Tint、Sprite Swap、Animation四种模式,None模式下与按钮交互将没有视觉上的反应,但其余三种模式,可以设置对应状态来实现交互的不同状态下,显示不同的性状
- Navigation 导航模式,可以设置UI元素如何通过输入系统来切换当前选择的组件,导航模式包含None无键盘导航,Everything全导航模式,Horizontal水平导航模式,Verticval垂直导航模式,Automatic自动导航模式,Explicit指定周边导航模式(可以点击Visualize在Scene窗口观察导航路径)
- On Click 点击事件,在这里可以添加点击按钮后会执行的函数

3.1.2 Button按钮组件监听事件
添加监听事件的方法
1.通过关联,如GameObject空对象上挂载了一个ButtonClick脚本,在该脚本中有一个方法为ClickButton方法,通过该方式关联,可以是实现点击按钮就会执行该方法


2.通过代码进行添加方法
语法:
添加方法:按钮实例.onClick.AddListener(方法名);
移除方法:按钮实例.onClickRemoveListener(方法名)
移除所有方法:按钮实例.onClick.RemoveAllListeners()
3.1.3 Button按钮练习题
作业内容:场景上有一个对象,实现点击UGUI的发射按钮后,上场景上的对象发射一颗子弹
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//玩家具备的方法
public class PlayerFunc : MonoBehaviour
{
//发射方法
public void ShotButtle()
{
GameObject buttlePrefab = Resources.Load<GameObject>("ButtlePrefab");
if(buttlePrefab == null )
{
Debug.Log("预设体加载失败");
return;
}
Instantiate(buttlePrefab,transform.position,Quaternion.identity);
}
}

3.2 Toggle 开关控件
UGUI中用于处理玩家单选框多选框相关交互的关键组件,默认为多选模式,可以与ToggleGruop组件配合使用形成单选,该组件由四个对象组成

,其中父对象Toggle挂载了Toggle组件,在子对象中Background挂载Image组件是Toggle的背景图显示,Checkmark是显示选中的对标,就是Toggle被选中时的对勾,Label为说明文字
3.2.1 Toggle参数相关
- IsOn 是否被选中,用来表示该当前Toggle是否处于打开状态
- Toggle Transition 开启关闭的状态切换,None为无过渡状态,Fade会有淡入淡出过渡,有平滑的透明度变化
- Group 单选框分组,把多个 Toggle 加入同一个组,就能实现 "单选互斥" 效果,如图为Toggle添加ToggleGroup组件,并将Toggle_1和Toggle_2的Group都设置为Toggle_1,就会实现以下单选的效果
- OnValueChanged 开关状态变化时执行的函数列表,

3.2.2 Toggle作业练习题
为子弹的发射添加音效,并通过Toggle开关来控制音效的开关
为子弹的脚本添加以音效功能,并根据是否开关来实现发生时是否有音效
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class Buttle : MonoBehaviour
{
//子弹的移动速度
public float moveSpeed= 1f;
//子弹关联的音效
public AudioClip clip;
private void Start()
{
//子弹在创建时,关联音效组件
AudioSource source = this.AddComponent<AudioSource>();
source.clip = clip;
source.Play();
Destroy(gameObject,5);//子弹在五秒后销毁
}
// Update is called once per frame
void Update()
{
transform.Translate(transform.forward*Time.deltaTime*moveSpeed);
}
}
3.3 InputField 文本输入控件
InputField 是 UGUI 中用于接收玩家文本输入的控件,一般由4个对象组成

,父对象(挂载 Image 背景和 InputField 组件)和子对象Placeholder默认显示文本组件(用于无输入时显示提示文字),Text文本显示组件(用于显示输入内容),与父对象的Text互通数据
3.3.1 参数介绍
- Text,string 类型,表示输入框中当前的文本内容
- Placeholder,Text 类型,表示输入框未输入内容时显示的提示文本
- Character Limit,int 类型,表示输入的最大字符数,0 表示不限制长度可以用于限制输入,如名字最多8最多8个字节
- ContentType,输入内容的类型,包含 Standard普通文本、Integer仅整数、Decimal仅小数、Password密码模式,显示为星号、Email邮箱格式等选项,不同类型会自动限制输入规则
- LineType,输入框的换行模式,包含 Single Line单行回车提交、Multi Line Submit多行回车提交、MultiLine Newline多行回车换行
- Read Only,输入框是否为只读,勾选后玩家无法修改内容
- Caret Blink Rate,float 类型,表示输入光标闪烁的频率

3.3.2 InputField作业练习
在之前练习题的基础上新增:左上角显示用户名,用户名旁边可以通过点击按钮来打开改名窗口,在窗口中可以是实现输入新的名字来达到修改用户名的效果
第一步,UI界面搭建,目前我们有两个面版,其中一个是GamePanel,另一个是ReNamePanel,如图


第二步,改名窗口界面控制脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ReNamePanel : MonoBehaviour
{
//重命名窗口的静态,方便各面板的相互通信
public static ReNamePanel panel;
//重命名相关控件
public TMPro.TMP_InputField reName_Input; //输入新的名字
public Button confirmButton; //确认按钮
public Button cancelButton; //取消按钮
private void Awake()
{
panel = this;
//游戏开始时先隐藏该界面
this.gameObject.SetActive(false);
}
private void Start()
{
//点击确认时,将名字改了,然后关闭该界面
confirmButton.onClick.AddListener(() =>
{
GamePanel.panel.text_Name.text = reName_Input.text;
this.gameObject.SetActive(false);
});
//点击取消时,直接隐藏该界面
cancelButton.onClick.AddListener(() =>
{
this .gameObject.SetActive(false);
});
}
}
3.4 Slider 滑动条控件
Slider是滑动条组件,由4个对象组成

,父对象Slider,子对象BackGround为滑动条的背景图,FillArea为填充区域(包涵滑动后的填充图),HardlesideArea为滑动块
3.4.1 Slider参数相关
- FillRect 用于填充的进度条图形
- HandleRect 用于滑动的滑动块图形
- Direction 滑动块移动时的正方向,包含从左到右、从右到左、从下到上、从上到下四种模式
- Min valve和Max valve 滑动条所能被允许的最大或最小值
- Value 当前滑动条的值,Whole Numbers 是否约束Value为整数变化通过代码(soundValueSlider.normalizedValue,可以将大小设置为0-1)
- 0nValueChanged 滑动条value改变时执行的函数列表

3.4.2 Slider作业练习
在之前的基础上完成通过滑动条控制音量大小的功能
这里发现,之前提供监视Toggle的开关来控制有些不好,所以该为了通过UI来设置音效数据,再通过数据来同一设置音效,以下为修改和涉及到的脚本
public class MusicData
{
//全局的音效数据
private static MusicData instance = new();
public static MusicData Instance => instance ;
private MusicData() { }
public bool IsOpenSound; //是否开启音效
public float soundValue; //音效大小
}
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class Buttle : MonoBehaviour
{
//子弹的移动速度
public float moveSpeed= 1f;
//子弹关联的音效
public AudioClip clip;
private void Start()
{
//子弹在创建时,关联音效组件,且只有Toggle被开启时才可以播放音效
if (MusicData.Instance.IsOpenSound)
{
AudioSource source = this.AddComponent<AudioSource>();
//设置音量大小
source.volume = MusicData.Instance.soundValue;
source.clip = clip;
source.Play();
}
Destroy(gameObject,5);//子弹在五秒后销毁
}
// Update is called once per frame
void Update()
{
transform.Translate(transform.forward*Time.deltaTime*moveSpeed);
}
}
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class GamePanel : MonoBehaviour
{
//游戏面版的静态属性,方便与其他面板进行通信
public static GamePanel panel;
//游戏面板上的组件
public Button shotButton; //发射子弹按钮
public Toggle soundOpenToggle; //音效开关选项
public Slider soundValueSlider; //音效大小
public TMP_Text text_Name; //用户名显示
public Button reName_Button; //用户名重新设置按钮
private void Awake()
{
panel = this;
}
private void Start()
{
shotButton.onClick.AddListener(() =>
{
PlayerFunc.playerFunc.ShotButtle();
});
reName_Button.onClick.AddListener(() =>
{
//当点击重命名按钮,打开改名窗口
ReNamePanel.panel.gameObject.SetActive(true);
});
soundOpenToggle.onValueChanged.AddListener((bool isOpe) =>
{
//这里的isOpen,没有实际意义,只是占位
MusicData.Instance.IsOpenSound = soundOpenToggle.isOn;
});
soundValueSlider.onValueChanged.AddListener((float value) =>
{
MusicData.Instance.soundValue = soundValueSlider.normalizedValue;
});
}
}
当前的UI界面

3.5 Scrollbar 滚动条控件
Scrollbar是unity中处理滚动条交互的组件,该控件一共包含2个对象

,需要与ScrollView配合使用
3.6 Scrollbar参数相关
- HandleRect 关联滚动块的图形对象
- Direction 滑动块移动的方向
- Valve 滚动条的当前值,取值范围0~1
- Size 滚动块在滚动条中的比例大小
- Number Of Steps 允许可以滚动多少次,0为无限制,其余整数会将滚动条均分为对应等份,每次移动一份
- OnValueChanged 滚动条值改变时执行的函数列表

3.6 ScrollView 滚动视图控件
ScrollRect是unity中Scrollview控件中用于处理交互的组件,包含四个对象,其中Viewport是用来显示的区域,其余两个子对象分别为水平和竖直滑动条,此外Viewport还包含一个子对象Content,是该控件的全部区域(包含显示的区域和未显示的)

3.6.1 ScollView参数相关
- Content 滚动的内容物体,它的大小决定了可滚动区域的范围,也就是3.6图中四个蓝点构成的区域。
- Horizontal/Vertical 是否启用水平或竖直的滑动条,关闭后将无法使用对应的滑块
- MovementType 移动方式,决定了拖拽范围超出边界后的效果,包含Unrestricted无限制(超出边界后仍可以继续拖拽),Elastic回弹模式(超出边界后会回弹会边界,其中Elasticty越小反弹效果越好),Clamped边界锁定(不可以拖拽出边界)
- Inertia 是否开启惯性滑动, Deceleration Rate 惯性滑动的减速速率
- Scroll Sensitivity 滚动的灵敏度,值越大,一次拖拽的内容就会移动越多
- Viewport 视口物体,决定了可以看到的区域

3.6.2 ScollView作业练习
添加一个背包按钮,点击后可以打开一个背包面板,在面板中会有一个滚动视图,滚动视图中会根据道具的数量来动态创建道具图标显示
第一步,构建UI的面板

第二步,UI面板的对应脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class BagPanel : MonoBehaviour
{
public static BagPanel panel; //背包面板
public Sprite[] itemSprites; //道具的精灵图像
public Image[] itemSlots; //道具物品栏
private int index = 0; //道具索引
public Button closeBagPanel; //背包关闭按钮
private void Awake()
{
panel = this;
panel.gameObject.SetActive(false);
}
void Start()
{
//这里直接用精灵图像模拟道具
foreach (var item in itemSprites)
{
itemSlots[index].sprite = item;
index++;
}
closeBagPanel.onClick.AddListener(() =>
{
panel.gameObject.SetActive(false);
});
}
}
GamePannel的更新
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class GamePanel : MonoBehaviour
{
//游戏面版的静态属性,方便与其他面板进行通信
public static GamePanel panel;
//游戏面板上的组件
public Button shotButton; //发射子弹按钮
public Toggle soundOpenToggle; //音效开关选项
public Slider soundValueSlider; //音效大小
public TMP_Text text_Name; //用户名显示
public Button reName_Button; //用户名重新设置按钮
public Button bag_Button; //背包按钮
private void Awake()
{
panel = this;
}
private void Start()
{
shotButton.onClick.AddListener(() =>
{
//发射按钮
PlayerFunc.playerFunc.ShotButtle();
});
reName_Button.onClick.AddListener(() =>
{
//当点击重命名按钮,打开改名窗口
ReNamePanel.panel.gameObject.SetActive(true);
});
soundOpenToggle.onValueChanged.AddListener((bool isOpe) =>
{
//这里的isOpen,没有实际意义,只是占位
MusicData.Instance.IsOpenSound = soundOpenToggle.isOn;
});
soundValueSlider.onValueChanged.AddListener((float value) =>
{
MusicData.Instance.soundValue = soundValueSlider.normalizedValue;
});
bag_Button.onClick.AddListener(() =>
{
//点击背包按钮,开打背包
BagPanel.panel.gameObject.SetActive(true);
});
}
}
使用演示,现在提前向背包中添加四个物品

3.7 Dropdown 下拉列表控件
Dropdown 是 Unity 中下拉菜单控件的核心交互组件,包含3个对象与,用于实现选项选择与列表交互,去其中Label是文本显示(对应图中的Option A),Arrow为下拉箭头装饰用的,Template为下拉面板

3.7.1 Dropdown 参数相关
- Template 下拉菜单展开时的模板对象,决定了下拉列表的整体布局、大小和背景样式,此后所有选项都会以这个模板为父物体生成
- Caption Text 主按钮上的文本对象,是当前选中选项的字体样式
- Caption Image 主按钮上的图像对象
- Placeholder 未选中任何选项时显示内容
- Item Text 下拉菜单每个选项中的文本对象,决定了单个选项文字的显示样式,所有选项都会复用这个模板的文本组件生成
- Item Image 下拉菜单每个选项中的图像对象,决定了单个选项图标的显示样式,所有选项都会复用这个模板的图像组件生成
- Value 当前选中选项的索引值
- Alpha Fade Speed 下拉菜单展开 / 收起时的透明度动画速度,是列表淡入淡出的过渡快慢,值越大动画完成得越快
- Options 菜单的选项

3.7.2 Dropdown 作业练习
通过下拉列表实现当前场景是白天还是黑夜
这里直接在GamePanel里改了,就没有准备数据脚本来用UI驱动数据,数据驱动效果的逻辑了
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class GamePanel : MonoBehaviour
{
//游戏面版的静态属性,方便与其他面板进行通信
public static GamePanel panel;
//游戏面板上的组件
public Button shotButton; //发射子弹按钮
public Toggle soundOpenToggle; //音效开关选项
public Slider soundValueSlider; //音效大小
public TMP_Text text_Name; //用户名显示
public Button reName_Button; //用户名重新设置按钮
public Button bag_Button; //背包按钮
public TMP_Dropdown changeDay; //改变白昼黑夜
public Light light1; //用于模拟的灯光
private void Awake()
{
panel = this;
}
private void Start()
{
shotButton.onClick.AddListener(() =>
{
//发射按钮
PlayerFunc.playerFunc.ShotButtle();
});
reName_Button.onClick.AddListener(() =>
{
//当点击重命名按钮,打开改名窗口
ReNamePanel.panel.gameObject.SetActive(true);
});
soundOpenToggle.onValueChanged.AddListener((bool isOpe) =>
{
//这里的isOpen,没有实际意义,只是占位
MusicData.Instance.IsOpenSound = soundOpenToggle.isOn;
});
soundValueSlider.onValueChanged.AddListener((float value) =>
{
MusicData.Instance.soundValue = soundValueSlider.normalizedValue;
});
bag_Button.onClick.AddListener(() =>
{
//点击背包按钮,开打背包
BagPanel.panel.gameObject.SetActive(true);
});
changeDay.onValueChanged.AddListener((int i) =>
{
//改变灯光的亮暗来模拟黑夜白昼
light1.intensity = changeDay.value;
//这里白昼编号是0,黑夜是1.所以样子原点反,改一下顺序就好
});
}
}

四、事件相关
4.1 UI事件监听接口
目前所有控件都只能使用一些固定的事件,如按钮的点击,滑动条的拖动改变值等,而UI事件监听接口可以实现类似长按、双击、拖拽等功能,或者让三大基础控件实现事件的监听
优点:需要监听自定义事件的控件可以通过挂载继承对应接口的脚本来实现监听对应事件
缺点:需要些对应的脚本来实现自定义事件的监听
4.1.1 常用的事件接口
|----------------------|----------------|-------------------------|
| 接口名 | 实现方法 | 作用 |
| IPointerEnterHandler | OnPointerEnter | 鼠标进入对象时调用 |
| IPointerExitHandler | OnPointerExit | 鼠标退出对象时调用 |
| IPointerDownHandler | OnPointerDown | 在对象上点击时调用 |
| IPointerUpHandler | OnPointerUp | 在对象上点击后抬起时调用(可以不是同一个对象) |
| IPointerclickHandler | OnPointerclick | 在同一对象点击并抬起时调用 |
| IBeginDragHandler | OnBeginDrag | 即将开始拖动时在拖动对象上调用 (开始拖拽) |
| IDragHandler | OnDrag | 发生拖动时在拖动对象上调用 (拖拽中) |
| IEndDragHandler | OnEndDrag | 拖动完成时在拖动对象上调用(结束拖拽) |
演示,为图像控件实现点击后,出现对应的事件调用
//实现IPointerDownHandler接口中的OnPointerDown方法
public void OnPointerDown(PointerEventData eventData)
{
// 判断是鼠标左键按下
if (eventData.pointerId == -1)
{
Debug.Log("鼠标左键按下");
}
// 鼠标右键按下
else if (eventData.pointerId == -2)
{
Debug.Log("鼠标右键按下");
}
// 触摸输入
else if (eventData.pointerId >= 0)
{
Debug.Log($"触摸ID:{eventData.pointerId} 按下");
}
}
4.1.2 事件接口
|---------------------------------|---------------------------|--------------------|
| 接口名 | 实现方法 | 作用 |
| IInitializePotentialDragHandler | OnInitializePotentialDrag | 在找到拖动目标时调用,可用于初始化值 |
| IDropHandler | OnDrop | 在拖动目标对象上调用 |
| IScrollHandler | OnScroll | 当鼠标滚轮滚动时调用 |
| IUpdateSelectedHandler | OnUpdateSelected | 每次勾选时在选定对象上调用 |
| ISelectHandler | OnSelect | 当对象成为选定对象时调用 |
| IDeselectHandler | OnDeselect | 取消选择选定对象时调用 |
4.1.3 PointerEventData 参数相关
PointEventData是UI事件监听时需要传入的参数,其父类为BaseEventData
- pointerId:鼠标左右中键的对应ID,其中对应编号依次为-1、-2、-3,可以用来监听不同按键点击的监听
- position:当前鼠标指针的屏幕坐标系的位置
- pressPosition:指针按下时的位置
- delta:指针的移动增量Vector2类型,是指针移动的向量,所以可以通过计算控件的delta 来驱动玩家移动
- clickCount:连续点击的次数
- clickTime:长按点击的持续时间
- pressEventCamera:最后一个OnPointerPress长按事件监听时关联的摄像机
- enterEventCamera:最后一个OnPointerEnter鼠标进入时关联的摄像机
4.1.4 UI事件监听接口 作业练习
长按UI按钮0.2s后开始蓄力,松开按钮后结束蓄能,根据蓄能的时长来是是实习恢复生命值
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
//该脚本实现蓄力按钮的长按点击,需要挂载到蓄力按键上
public class ChargeupButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
public Image chargeupImage; //蓄力关联的进度条
public float needTime; //蓄力完成需要的时间
public float nowTime; //当亲蓄力的耗时
public bool isButonDown; //鼠标是否按下
//鼠标按下
public void OnPointerDown(PointerEventData eventData)
{
//鼠标按下,开始蓄力
isButonDown = true;
nowTime = 0;
}
//鼠标抬起
public void OnPointerUp(PointerEventData eventData)
{
if(nowTime>=needTime)
{
//进行回血相关的逻辑=> 蓄力的次数决定血量的恢复,这里进行简单模拟
int addHp = (int)(nowTime / needTime) * 10;
Debug.Log("回复生命值" + addHp);
}
//鼠标抬起,将蓄力进度归零
isButonDown = false;
chargeupImage.fillAmount = 0;
}
private void Update()
{
if (isButonDown)
{
//当鼠标按下时,进行时间积累
nowTime += Time.deltaTime;
chargeupImage.fillAmount = Mathf.Clamp01(nowTime/needTime);
}
}
}

4.2 EventTrigger事件触发器
EventTrigger事件触发器组件,集成了所有事件接口脚本,可以通过Inspector窗口来配置事件监听
使用方式:
- 通过添加EventTrigger组件,并拖拽相关脚本实现触发相关逻辑,实现对应的方法,如练习题中就未JoyStick添加了该组件,并使用了拖拽和拖拽结束的接口,这里关联了GamePanel中的对应方法

-
通过代码添加
void Start()
{
//首先,未对象添加EventTrigger组件
EventTrigger trigger = gameObject.AddComponent(); //然后,创建相关事件,如拖拽事件 EventTrigger.Entry drag = new EventTrigger.Entry(); drag.eventID = EventTriggerType.Drag; drag.callback.AddListener(() => { Debug.Log("拖拽中"); }); //最后,将该事件添加到EventTrigger对象中 trigger.triggers.Add(drag);}
4.2.2 EventTrigger事件触发器 作业练习
首先绘制UI,并未GamePanel添加摇杆相关的功能

using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class GamePanel : MonoBehaviour
{
//游戏面版的静态属性,方便与其他面板进行通信
public static GamePanel panel;
//游戏面板上的组件
public Button shotButton; //发射子弹按钮
public Toggle soundOpenToggle; //音效开关选项
public Slider soundValueSlider; //音效大小
public TMP_Text text_Name; //用户名显示
public Button reName_Button; //用户名重新设置按钮
public Button bag_Button; //背包按钮
public TMP_Dropdown changeDay; //改变白昼黑夜
public Light light1; //用于模拟的灯光
private void Awake()
{
panel = this;
}
private void Start()
{
shotButton.onClick.AddListener(() =>
{
//发射按钮
PlayerFunc.playerFunc.ShotButtle();
});
reName_Button.onClick.AddListener(() =>
{
//当点击重命名按钮,打开改名窗口
ReNamePanel.panel.gameObject.SetActive(true);
});
soundOpenToggle.onValueChanged.AddListener((bool isOpe) =>
{
//这里的isOpen,没有实际意义,只是占位
MusicData.Instance.IsOpenSound = soundOpenToggle.isOn;
});
soundValueSlider.onValueChanged.AddListener((float value) =>
{
MusicData.Instance.soundValue = soundValueSlider.normalizedValue;
});
bag_Button.onClick.AddListener(() =>
{
//点击背包按钮,开打背包
BagPanel.panel.gameObject.SetActive(true);
});
changeDay.onValueChanged.AddListener((int i) =>
{
//改变灯光的亮暗来模拟黑夜白昼
light1.intensity = changeDay.value;
//这里白昼编号是0,黑夜是1.所以样子原点反,改一下顺序就好
});
}
#region 摇杆移动相关
public RectTransform joyImage; //遥杆图像的位置
//开始拖拽的方法
public void DragJoystick(BaseEventData d)
{
//现在获取传入参数的delta
PointerEventData data = d as PointerEventData;
//设置摇杆移动时的点位置,并进行限制
joyImage.anchoredPosition += new Vector2(data.delta.x, data.delta.y);
//限制遥杆不可以出边界
joyImage.anchoredPosition = new Vector3(
Mathf.Clamp(joyImage.anchoredPosition.x, -130, 130),
Mathf.Clamp(joyImage.anchoredPosition.y, -130, 130),
0);
//首先模拟WASD的输入
float horizontal = joyImage.anchoredPosition.x / 130;
float vertical = joyImage.anchoredPosition.y / 130;
horizontal = Mathf.Clamp(horizontal, -1f, 1f);
vertical = Mathf.Clamp(vertical, -1f, 1f);
//玩家的移动方向
Vector3 dir = new Vector3(horizontal, 0, vertical);
Debug.Log(dir);
//然后调用玩家的移动方法
if (dir != Vector3.zero)
{
PlayerFunc.playerFunc.PlayerMoveInput(dir.x, dir.z);
}
}
//结束拖拽的方法
public void EndDragJoystick(BaseEventData d)
{
//将遥感的轴心点位置设置为0
joyImage.anchoredPosition = Vector3.zero;
//这里使用position会回到屏幕原点
//停止移动 传0向量进去 即可
PlayerFunc.playerFunc.PlayerMoveInput(0, 0);
}
#endregion
}
其次,添加玩家移动的方法,利用摇杆的移动偏移来模拟水平和竖直轴的输入输出,最终实现效果如图

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UIElements;
using UnityEngine.Windows;
//玩家具备的方法
public class PlayerFunc : MonoBehaviour
{
public static PlayerFunc playerFunc;
private void Awake()
{
playerFunc = this;
}
//发射方法
public void ShotButtle()
{
GameObject buttlePrefab = Resources.Load<GameObject>("ButtlePrefab");
if(buttlePrefab == null )
{
Debug.Log("预设体加载失败");
return;
}
Instantiate(buttlePrefab,transform.position,Quaternion.identity);
}
//玩家的移动方法
public float moveSpeed;
public float rotatedSpeed;
private Vector3 moveDir;
public void PlayerMoveInput(float h,float v)
{
moveDir = new Vector3(h,0,v);
}
private void Update()
{
if(moveDir!= Vector3.zero)
{
this.transform.Translate(moveDir * moveSpeed * Time.deltaTime,Space.World);
this.transform.rotation = Quaternion.Slerp(this.transform.rotation, Quaternion.LookRotation(moveDir), rotatedSpeed * Time.deltaTime);
}
}
}
五、补充
5.1 屏幕坐标转UI相对坐标
RectTransformUtility是RectTransform的辅助类,主要用于坐标转换等操作
将屏幕坐标转为UI本地坐标的点
语法:RectTransformUtility.ScreenPointToLocalPointInRectangle(相对的都对象,屏幕上的点,参考摄像机,out 最终的点)

使用举例:优化4.2.2中的摇杆控制,实现在摇杆区域内鼠标和摇杆同步,这里只需要改变计算摇杆位置的方式就好,效果如下

using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class GamePanel : MonoBehaviour
{
//游戏面版的静态属性,方便与其他面板进行通信
public static GamePanel panel;
//游戏面板上的组件
public Button shotButton; //发射子弹按钮
public Toggle soundOpenToggle; //音效开关选项
public Slider soundValueSlider; //音效大小
public TMP_Text text_Name; //用户名显示
public Button reName_Button; //用户名重新设置按钮
public Button bag_Button; //背包按钮
public TMP_Dropdown changeDay; //改变白昼黑夜
public Light light1; //用于模拟的灯光
private void Awake()
{
panel = this;
}
private void Start()
{
shotButton.onClick.AddListener(() =>
{
//发射按钮
PlayerFunc.playerFunc.ShotButtle();
});
reName_Button.onClick.AddListener(() =>
{
//当点击重命名按钮,打开改名窗口
ReNamePanel.panel.gameObject.SetActive(true);
});
soundOpenToggle.onValueChanged.AddListener((bool isOpe) =>
{
//这里的isOpen,没有实际意义,只是占位
MusicData.Instance.IsOpenSound = soundOpenToggle.isOn;
});
soundValueSlider.onValueChanged.AddListener((float value) =>
{
MusicData.Instance.soundValue = soundValueSlider.normalizedValue;
});
bag_Button.onClick.AddListener(() =>
{
//点击背包按钮,开打背包
BagPanel.panel.gameObject.SetActive(true);
});
changeDay.onValueChanged.AddListener((int i) =>
{
//改变灯光的亮暗来模拟黑夜白昼
light1.intensity = changeDay.value;
//这里白昼编号是0,黑夜是1.所以样子原点反,改一下顺序就好
});
}
#region 摇杆移动相关
public RectTransform joyImage; //遥杆图像的位置
//开始拖拽的方法
public void DragJoystick(BaseEventData d)
{
//现在获取传入参数的delta
PointerEventData data = d as PointerEventData;
//设置摇杆移动时的点位置,并进行限制
//joyImage.anchoredPosition += new Vector2(data.delta.x, data.delta.y);
Vector2 nowPos;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
joyImage.parent as RectTransform,
data.position,
data.enterEventCamera,
out nowPos);
joyImage.localPosition = nowPos;
//限制遥杆不可以出边界
joyImage.anchoredPosition = new Vector3(
Mathf.Clamp(joyImage.anchoredPosition.x, -130, 130),
Mathf.Clamp(joyImage.anchoredPosition.y, -130, 130),
0);
//首先模拟WASD的输入
float horizontal = joyImage.anchoredPosition.x / 130;
float vertical = joyImage.anchoredPosition.y / 130;
horizontal = Mathf.Clamp(horizontal, -1f, 1f);
vertical = Mathf.Clamp(vertical, -1f, 1f);
//玩家的移动方向
Vector3 dir = new Vector3(horizontal, 0, vertical);
Debug.Log(dir);
//然后调用玩家的移动方法
if (dir != Vector3.zero)
{
PlayerFunc.playerFunc.PlayerMoveInput(dir.x, dir.z);
}
}
//结束拖拽的方法
public void EndDragJoystick(BaseEventData d)
{
//将遥感的轴心点位置设置为0
joyImage.anchoredPosition = Vector3.zero;
//这里使用position会回到屏幕原点
//停止移动 传0向量进去 即可
PlayerFunc.playerFunc.PlayerMoveInput(0, 0);
}
#endregion
}
5.2 遮罩Mask
遮罩是指在不改变图像的前提下,实现只显示图像的一部分的功能
实现遮罩需要添加Mask组件,此外父对象添加了Mask组件后,其子对象也会具有遮罩的效果
注意:
- 需要被遮罩的图片需要勾选Maskable,且必须是Mask组件对象的子物体
- 可以通过遮罩组件的Show Mask Graphic参数来控制遮罩区域是否显示,此外只会显示遮罩区域中透明的地方
使用举例:添加一个头像遮罩显示

5.3 异型按钮
- 方法一:通过添加子对象的形式
原理:按钮的点击,主要时根据图片的矩形范围来检测的,而他的检测可以通过子物体来传递,所以我们可以通过子物体拼接来实现那使用不规则图像作为按钮的功能
评价:节省内存消耗,但是存在拼接不正确的现象,如下图中的非矩形区域点击就没有效果(这里为了观察Image的透明度不为0 )


注意:通过该方法实现的异形按钮需要将Button组件上只有的Target Graphic改为异形图片对象
- 方法二:通过读取图片信息,改变图片的透明度响应阈值
原理:通过读取图片的参数,改变图片透明度的响应阈值来实现,对于一张图片透明部分的透明度为0.所以可以根据小于某一值的区域不响应就可以是实现异形点击效果
评价:异形效果更佳准确,但是需要保存图片的参数,性能消耗大

5.4 自动布局组件
5.4.1 水平垂直布局组
水平布局组件:Horizontal Layout Group 可以让UI组件按水平自动排列自动排列
垂直布局组件:Vertical Layout Group 可以让UI组件按竖直自动排列自动排列
参数介绍:
- Padding:边缘偏移位置,可以决定布局组件中的UI控件与边界间的距离
- Spacing:各UI控件之间的间隔
- ChildAlignment:九宫格对齐方式,在布局中以哪一个为锚点进行布局
- Control Child Size:是否控制UI控件的宽高
- Use Child Scale:在设置子UI控件大小和布局时,是否考虑其缩放
- Child Force Expand:是否强制UI控件拓展以填充额外可用空间,该参数是导致没有设置间隔但仍然还存在间隔的原因

这里UI控件就算超出父对象的区域范围仍然会继续水平或竖直布局,不会进行换行,如图

5.4.2 网格布局组
网格布局组:Grid Layout Group,可以让UI控件按照指定顺序排列,当没有额外设置时,超过对应边界会自动换行
参数介绍:
- Cell Size:每个格子的大小
- Start Corner:第一个元素所在位置,存在左上、右上、左下、右下四个位置选择
- Start Axis:沿哪个轴放置元素;Horizontal水平放置满换行,Vertical竖直放置满换列(这里设置后,UI控件多时,只会不选择的轴超出边界,如图中竖直轴超出边界了)
- Constraint:行列约束,包含Flexible(灵活模式)、Fixed Column Count(固定列)和Fixed Row Count(固定行)三种模式

5.4.3 内容大小适配器
内容大小适配器:Content Size Fitter,可以自动的调整RectTransform的长宽来让组件自动设置大小,如在网格布局组件的实例对象中添加该组件,可以实现UI控件过多时,自动扩大
参数介绍:
- Horizontal Fit:控制宽度的方式
- Vertical Fit:控制高度的方式
(以上两个方式都包含三种模式:Unconstrained不根据布局元素伸展、Min Size根据布局元素的最小宽高度来伸展、Preferred Size根据布局元素的偏好宽度来伸展宽度)
三种模式的对比:(这里Min Height和Preferred Height相同,所以表现形式就一样)


5.4.4 宽高比适配器
宽高比适配器:Aspect Ratio Fitter,让UI控件按照一定比例来调整自己的大小,从而使UI控件在父对象内部根据父对象大小进行适配
参数介绍
Aspect Mode:适配模式,调整矩形大小来实施宽高比
- None:不让矩形适应宽高比
- Width Controls Height:根据宽度自动调整高度
- Height Controls Width:根据高度自动调整宽度
- Fit In Parent:自动调整宽度、高度、位置和锚点,使矩形适应父项的矩形,同时保持宽高比,会出现"黑边"
- Envelope Parent:自动调整宽度、高度、位置和锚点,使矩形覆盖父项的整个区域,同时保持宽高比,会出现"裁剪"
(四种模式的对比,宽高比都是2)

Aspect Ratio:宽高比;宽除以高的比值
5.5 画布组 Canvas Group
Canvas Group可以整体调控UI面版的透明度、是否可交互,是否进行整体射线检测设置,是否忽略父级CanvasGroup组件,因此可以用来实现淡入淡出、面板禁用等功能

5.6 模型和粒子显示在UI前
5.6.1 如何将模型显示在UI之前
方法一:直接用摄像机渲染3D物体,摄像机模式和世界模式都可以让模型显示在UI之前,在摄像机模式下,需要专门的摄像机渲染UI相关,且要显示的物体模型也要渲染在UI摄像机上(这里是否显示受到Z轴的影响)
(这里我发现我无论怎么调整物体都会在UI后面,这里我将Cube作为Canvas子对象后,并将层级设置为了UI,就可以正常显示了,对应参数及效果演示)

方法二:将3D物体渲染到图片上,通过Raw Image显示,该方法不需要特定的Canvas渲染模式,但是只适用于渲染对象少的情况
这里实现时也需要一个摄像机专门渲染物体,并将物体输出为Render Texture,然后利用Raw Image显示,注意你可能看到物体有明显的残影,这里摄像机仍然只渲染Model层,但是不要设置为Only Depth模式
5.6.2 粒子特效渲染在UI之前
粒子特效显示和物理模型显示方法一致,但是在摄像机模式下可以改变粒子组件的Renderer相关参数来改变渲染顺序,这样可以实现无视Z轴,让特效恒定显示在UI之前
5.7 图集制作
使用图集的优点:减少Drawcall,提高性能
首先,需要打开图集制作的功能:Edit------>Project Setting------>Editor------>Sprite Packer

Sprite Packer包含5种模式
- Disabled:默认设置,不会打包图集
- V1 Enabled For Builds:Unity仅在构建时打包图集,在编辑模式下不会打包图集
- V1 Always Enabled(Legacy Sprite Packer):Unity在构建时打包图集,在编辑模式下运行前会打包图集
- V2 Enabled For Build:Unity进在构建时打包图集,在编辑器模式下不会打包图集
- V2 Always Enabled:Unity在构建时打包图集,在编辑模式下运行前会打包图集
(这里V1 比V2 模式多个参数间隔距离的设置,V2会自动设置间隔距离)
Padding Power:选择打包算法在计算打包的精灵之间以及精灵与生成的图集边缘之间的间隔距离,int类型为2的n次方,这里已经被移除了,只可以通过脚本来访问该参数
Max SpriteAtlas Cache Size (GB) , 限制编辑器中精灵图集缓存的最大占用空间
其次,创建一个spriteAtlas资源,并设置相关参数,这里需要将要打成图集的小图添加到 Objects for Packing 中,然后点击 Pack Preview 就完成了图集的制作

Sprite Atlas参数介绍
- Include in Build 勾选后该图集会被打包进游戏构建包
- Allow Rotation 是否允许打包时旋转精灵,可优化图集空间利用率,像素动画帧和UI素材 不勾选
- Tight Packing 紧凑打包,去除精灵透明边缘冗余空间,UI素材 不勾选
- Padding 精灵图之间的间隔
此外,如何使用代码加载图集中的小图
//加载图集
SpriteAtlas sa = Resources.Load<SpriteAtlas>("MyAlas");
//从图集中加载指定名字的小图
sa.GetSprite("08");