第1章: 概述

第2章: UGUI基础——六大基础组件

第1节: 六大基础组件概述

六大基础组件概述

知识点

第2节: 六大基础组件—Canvas画布组件

Canvas——渲染模式的控制

知识点

注意

使用摄像机模式的时候,建议单独绑定一个UI层摄像机而不是总摄像机,通过渲染顺序和层级来对物体和UI层级进行调整和操控。(否则如果物体相比UI离主摄像机太近,会导致物体和UI错位)。

第3节: 六大基础组件—CanvasScaler画布缩放控制器组件

CanvasScaler——必备知识

知识点

CanvasScaler——恒定像素模式

知识点

博主总结

该模式UI大小将不会变化,无法自适应(除非代码主动修改,但是太麻烦),所谓的算法也仅仅会在set naive Size会进行一次图片大小的计算。

CanvasScaler——缩放模式

知识点

博主总结

缩放模式模式的重点在于画布分辨率 实际分辨率 缩放比

具体的实现步骤是:

确定标准分辨率——>用户用实际分辨率打开游戏后——>根据实际分辨率的长和宽和标准分辨率的长和宽分别计算进行计算得出分别的缩放比——>根据三个模式确定最终缩放比,该缩放比就是Rect当中的scale,敲定缩放比后通过 实际分辨率/缩放比 反推出画布分辨率——> 所有数据确定,Rect进行设置,UI效果呈现。

expand会保证一定会显示所有UI画面内容,但是会多出多余分辨率,可能导致黑边(画布分辨率两边都大于实际分辨率)。shrink会保证UI填满屏幕,但是会裁切部分内容(画布分辨率一边小于实际分辨率,但是长和宽之一会等于实际分辨率)。Match则是混合版本,match值为0和1可以分别保证宽和高一定等同标准分辨率(即宽和高部分一定不会丢失UI信息)。

总的来说,该模式应该是游戏开发里面最常用的模式

CanvasScaler——恒定物理模式

知识点

CanvasScaler——3D模式

知识点

第4节: 六大基础组件—Graphic Raycaster图形射线投射器

Graphic Raycater 图形射线投射器组件

知识点

第5节: 六大基础组件—EventSystem和Standalone Input Module

EventSystem和Standalone Input Module

知识点

第6节: 六大基础组件—RectTransform

RectTransform 矩形变化

知识点

代码

可以用this.transform as RectTransform直接获取组件

博主总结

1.重点在于对于父物体上的锚点和本体上的轴心点的理解。

2.rect中的四个参数会根据父物体锚的状态变化。

父物体的4个锚集于一点的时候才是锚点,这时候对物体而言:posx和posy代表轴心点基于锚点的距离,width和height代表物体本身的长和宽。(该模式注重于分辨率变化时,让物体的位置变化更加准确,具体可以去查看我之前记录的九宫格理论)

父物体的四个锚全部分开的时候代表了一个范围,这时对物体而言:left,top,right,button分别代表了物体的四条边和范围矩形的四条边的距离。(该模式注重于分辨率变化时,让物体的大小变化更加准确,因为大小和分辨率四条边息息相关)

同时四个锚还可以出现x之间为范围,y为点的情况。还有y为点,x为范围的情况。这两种情况下,功能和命名都只会变化2个参数,可以看自己的需求选择对锚点的安排。

一个坑

在上图的图一里,在面板变动其中一个边的值时候,但是另一条边长度会自动变化,但是值却不变,而如果在场景里面直接拉,你会发现另一条边长度明明不变,但是面板里面的值却变化了?这似乎有点反直觉?这是为什么呢?

重点在于scale不为1。

经过我测试,当x或y的scale不为1的时候,如果四个轴为范围的话 ,有时候会发现改变面板上的其中一条边的时候会导致另一条边同步变化,这实际上是反直觉的。

首先我需要强调的是,这取决于轴心点是否位于两条边的中间范围。如果四个轴为范围的话,四个参数还是以scale为1为参照值,不会随着scale的值改变。

所以,当轴心点位于中间的时候,scale变化的时候是两边是同时进行等量变化的,如果变回去1的话,两边也会同时减少相同的长度,为了保证面板上一条边的值变化的时候,另一条边的值不改变,唯有拉长同样的距离,才能保证另一半的值不变,这样就会导致这种反直觉的事情出现了。

第3章: UGUI基础——三大基础控件

Image 图像控件

知识点一 Image是什么?

Image是图像组件

是UGUI中用于显示精灵图片的关键组件

除了背景图等大图,一般都使用Image来显示UI中的图片元素

知识点二 Image参数

知识点三 代码控制

Image img = this.GetComponent

();

img.sprite = Resources.Load(“ui_TY_fanhui_01”);

(transform as RectTransform).sizeDelta = new Vector2(200, 200);

img.raycastTarget = false;

img.color = Color.red;

Text 文本控件

知识点一 Text是什么

Text是文本组件

是UGUI中用于显示文本的关键组件

知识点二 Text参数相关

知识点三 富文本

勾选Rich Text开启,可以参考html语言

知识点四 边缘线和阴影

通过再text中添加下面的组件,可以实现对应的效果。

边缘线组件 outline

阴影组件 Shadow

知识点五 代码控制

Text txt = this.GetComponent();

txt.text = “唐老狮 哈哈哈哈哈”;

RawImage 原始图像控件

知识点一 RawImage是什么

RawImage是原始图像组件

是UGUI中用于显示任何纹理图片的关键组件

它和Image的区别是 一般RawImage用于显示大图(背景图,不需要打入图集的图片,网络下载的图等等)

知识点二 RawIamge参数

知识点三 代码控制

RawImage raw = this.GetComponent();

raw.texture = Resources.Load(“ui_TY_lvseshuzi_08”);

raw.uvRect = new Rect(0, 0, 1, 1);

第4章: UGUI基础——组合控件

第1节: Button 按钮控件

Button 按钮控件

知识点一 Button是什么

Button是按钮组件

是UGUI中用于处理玩家按钮相关交互的关键组件

默认创建的Button由2个对象组成

父对象——Button组件依附对象 同时挂载了一个Image组件 作为按钮背景图

子对象——按钮文本(可选)

知识点二 Button参数

知识点三 代码控制

Button btn = this.GetComponent();

btn.interactable = true;

btn.transition = Selectable.Transition.None;

Image img = this.GetComponent

();

知识点四 监听点击事件的两种方式

点击事件 是 在按钮区域抬起按下一次 就算一次点击

1.拖脚本

2.代码添加

btn.onClick.AddListener(ClickBtn2);

btn.onClick.AddListener(() => {

print(“123123123”);

});

btn.onClick.RemoveListener(ClickBtn2);

btn.onClick.RemoveAllListeners();

注意

拖入的监听事件无法通过代码移除。下文所有组合控件同理。

第2节: Toggle 开关控件

Toggle 开关控件

知识点一 Toggle是什么

Toggle是开关组件

是UGUI中用于处理玩家单选框多选框相关交互的关键组件

开关组件 默认是多选框

可以通过配合ToggleGroup组件制作为单选框

默认创建的Toggle由4个对象组成

父对象——Toggle组件依附

子对象——背景图(必备)、选中图(必备)、说明文字(可选)

知识点二 Toggle参数

知识点三 代码控制

Toggle tog = this.GetComponent();

tog.isOn = true;

print(tog.isOn);

ToggleGroup togGroup = this.GetComponent();

togGroup.allowSwitchOff = false;

可以遍历提供的迭代器 得到当前处于选中状态的 Toggle

foreach (Toggle item in togGroup.ActiveToggles())

{

print(item.name + " " + item.isOn);

}

知识点四 监听事件的两种方式

1.拖脚本(如果是老版本,必须选择上面的动态函数(不带参数),新版本只需要注意参数对应即可)

2.代码添加

tog.onValueChanged.AddListener(ChangeValue2);

tog.onValueChanged.AddListener((b) =>

{

print(“代码监听 状态改变” + b);

});

第3节: InputField 文本输入控件

InputField 文本输入控件

知识点一 InputField是什么

InputField是输入字段组件

是UGUI中用于处理玩家文本输入相关交互的关键组件

默认创建的InputField由3个对象组成

父对象——InputField组件依附对象 以及 同时在其上挂载了一个Image作为背景图

子对象——文本显示组件(必备)、默认显示文本组件(必备)

知识点二 InputField参数

知识点三 代码控制

InputField input = this.GetComponent();

print(input.text);

input.text = “123123123123”;

知识点四 监听事件的两种方式

1.拖脚本

2.代码添加

input.onValueChanged.AddListener((str) =>

{

print(“代码监听 改变” + str);

});

input.onEndEdit.AddListener((str) =>

{

print(“代码监听 结束输入” + str);

});

注意

在面板上进行带参数的事件绑定时,必须选择上方的动态方法,静态方法只会固定你写入的值。下文的类似的内容同理。

第4节: Slider 滑动条控件

Slider 滑动条控件

知识点一 Slider是什么

Slider是滑动条组件

是UGUI中用于处理滑动条相关交互的关键组件

默认创建的Slider由4组对象组成

父对象——Slider组件依附的对象

子对象——背景图、进度图、滑动块三组对象

知识点二 Slider参数

知识点三 代码控制

Slider s = this.GetComponent();

print(s.value);

知识点四 监听事件的两种方式

1.拖脚本

2.代码添加

s.onValueChanged.AddListener((v) =>

{

print(“代码添加的监听” + v);

});

第5节: ScrollBar 滚动条控件

ScrollBar 滚动条

知识点一 Scrollbar是什么

Scrollbar是滚动条组件

是UGUI中用于处理滚动条相关交互的关键组件

默认创建的Scrollbar由2组对象组成

父对象——Scrollbar组件依附的对象

子对象——滚动块对象

一般情况下我们不会单独使用滚动条

都是配合ScrollView滚动视图来使用

知识点二 Scrollbar参数

知识点三 代码控制

Scrollbar sb = this.GetComponent();

print(sb.value);

print(sb.size);

知识点四 监听事件的两种方式

1.拖脚本

2.代码添加

sb.onValueChanged.AddListener((v) => {

print(“代码监听的函数” + v);

});

第6节: ScrollView 滚动视图控件

ScrollView 滚动视图

知识点一 ScrollRect是什么

ScrollRect是滚动视图组件

是UGUI中用于处理滚动视图相关交互的关键组件

默认创建的ScrollRect由4组对象组成

父对象——ScrollRect组件依附的对象 还有一个Image组件 最为背景图

子对象

Viewport控制滚动视图可视范围和内容显示

Scrollbar Horizontal 水平滚动条

Scrollbar Vertical 垂直滚动条

知识点二 ScrollRect参数

知识点三 代码控制

ScrollRect sr = this.GetComponent();

改变内容的大小 具体可以拖动多少 都是根据它的尺寸来的

sr.content.sizeDelta = new Vector2(200, 200);

sr.normalizedPosition = new Vector2(0, 0.5f);

知识点四 监听事件的两种方式

1.拖脚本

2.代码添加

sr.onValueChanged.AddListener((vec) =>

{

print(vec);

});

第7节: Dropdown 下拉列表控件

知识点一 DropDown是什么

DropDown是下拉列表(下拉选单)组件

是UGUI中用于处理下拉列表相关交互的关键组件

默认创建的DropDown由4组对象组成

父对象

DropDown组件依附的对象 还有一个Image组件 作为背景图

子对象

Label是当前选项描述

Arrow右侧小箭头

Template下拉列表选单

知识点二 DropDown参数

知识点三 代码控制

Dropdown dd = this.GetComponent();

print(dd.value);

print(dd.options[dd.value].text);

dd.options.Add(new Dropdown.OptionData(“123123123”));

知识点四 监听事件的两种方式

1.拖脚本

2.代码添加

dd.onValueChanged.AddListener((index) => {

print(index);

});

注意

如果要给选项加图片,绑定的时候需要先在场景对应位置创建好图片组件(下拉框Template对象默认是失活,需要进行激活),然后绑定图片组件对象。如下:

此外,下拉框的大小位置修改也可以直接对对应的修改即可,Template可以当中下拉框的对应复制模板。

第5章: UGUI基础——图集制作

图集制作

知识点一 为什么要打图集

UGUI和NGUI使用上最大的不同是 NGUI使用前就要打图集

UGUI可以再之后再打图集

打图集的目的就是减少DrawCall 提高性能

具体DrawCall是什么在NGUI课程中已经详细讲解

该节课是免费课 即使没有购买 也可以前往观看

简单回顾DrawCall

DC就是CPU通知GPU进行一次渲染的命令

如果DC次数较多会导致游戏卡顿

我们可以通过打图集,将小图合并成大图,将本应n次的DC变成1次DC来提高性能

知识点二 在Unity中打开自带的打图集功能

在工程设置面板中打开功能

Edit——>Project Setting——>Editor

Sprite Packer(精灵包装器,可以通过Unity自带图集工具生成图集)

Disabled:默认设置,不会打包图集

Enabled For Builds(Legacy Sprite Packer):Unity仅在构建时打包图集,在编辑模式下不会打包图集

Always Enabled(Legacy Sprite Packer):Unity在构建时打包图集,在编辑模式下运行前会打包图集

Legacy Sprite Packer传统打包模式 相对下面两种模式来说 多了一个设置图片之间的间隔距离

Padding Power:选择打包算法在计算打包的精灵之间以及精灵与生成的图集边缘之间的间隔距离

这里的数字 代表2的n次方

Enabled For Build:Unity进在构建时打包图集,在编辑器模式下不会打包

Always Enabled:Unity在构建时打包图集,在编辑模式下运行前会打包图集

知识点三 打图集参数注意

注意:需要有2Dsprite包

右键创建 Sprite Atlas后可进行打图集,其中Allow Rotation和Tight Packing选项建议取消勾选(前者一定要取消否则大概率bug,后者看是否有bug决定,可以节省一定内存空间)

知识点四 代码加载

加载图集 注意:需要引用命名空间

SpriteAtlas sa = Resources.Load(“MyAlas”);

从图集中加载指定名字的小图

sa.GetSprite(“bk”);

第6章: UGUI进阶

引入:RectTransform中localPosition与anchoredPosition的区别(博主加)

  1. Inspector面板中Transform的位置信息显示的是localPosition

  2. Inspector面板中RectTransform的位置信息显示的是anchoredPosition3D(但是依然可以在debug模式查看localPosition)

2.1 localPosition表示的是子物体的pivot相对于父物体的pivot的坐标

2.2 anchoredPosition3D表示的是子物体的pivot相对于锚点的坐标,当anchor不是一个点时表示前3个元素(left,top,z)(rect面板上直接显示的坐标)

2.3 当子物体的锚点与父物体的pivot坐标重合时anchoredPosition3D与localPosition相等

重点:

pivot坐标:指的是RectTransform的中心点,即UI上的pivot中心点。

可以看出,RectTransform并不会改动localpositon的算法

anchoredPosition3D是相对于锚点的坐标,localpositon就是相对于父类pivot的坐标。

原文链接:(17条消息) LookRotation用法详解 Unity3d Quaternion.LookRotation实现原理_unity lookrotation_刺子的博客-CSDN博客

第1节: UI事件监听接口

知识点一 事件接口是用来解决什么问题的

目前所有的控件都只提供了常用的事件监听列表

如果想做一些类似长按,双击,拖拽等功能是无法制作的

或者想让Image和Text,RawImage三大基础控件能够响应玩家输入也是无法制作的

而事件接口就是用来处理类似问题

让所有控件都能够添加更多的事件监听来处理对应的逻辑

知识点二 有哪些事件接口

常用事件接口

IPointerEnterHandler - OnPointerEnter - 当指针进入对象时调用 (鼠标进入)

IPointerExitHandler - OnPointerExit - 当指针退出对象时调用 (鼠标离开)

IPointerDownHandler - OnPointerDown - 在对象上按下指针时调用 (按下)

IPointerUpHandler - OnPointerUp - 松开指针时调用(在指针正在点击的游戏对象上调用)(抬起)

IPointerClickHandler - OnPointerClick - 在同一对象上按下再松开指针时调用 (点击)

IBeginDragHandler - OnBeginDrag - 即将开始拖动时在拖动对象上调用 (开始拖拽)

IDragHandler - OnDrag - 发生拖动时在拖动对象上调用 (拖拽中)

IEndDragHandler - OnEndDrag - 拖动完成时在拖动对象上调用 (结束拖拽)

不常用事件接口 了解即可

IInitializePotentialDragHandler - OnInitializePotentialDrag - 在找到拖动目标时调用,可用于初始化值

IDropHandler - OnDrop - 在拖动目标对象上调用

IScrollHandler - OnScroll - 当鼠标滚轮滚动时调用

IUpdateSelectedHandler - OnUpdateSelected - 每次勾选时在选定对象上调用

ISelectHandler - OnSelect - 当对象成为选定对象时调用

IDeselectHandler - OnDeselect - 取消选择选定对象时调用

导航相关

IMoveHandler - OnMove - 发生移动事件(上、下、左、右等)时调用

ISubmitHandler - OnSubmit - 按下 Submit 按钮时调用

ICancelHandler - OnCancel - 按下 Cancel 按钮时调用

知识点三 使用事件接口

1.继承MonoBehavior的脚本继承对应的事件接口,引用命名空间

2.实现接口中的内容

3.将该脚本挂载到想要监听自定义事件的UI控件上

知识点四 PointerEventData参数的关键内容

父类:BaseEventData

pointerId: 鼠标左右中键点击鼠标的ID 通过它可以判断右键点击

position:当前指针位置(屏幕坐标系)

pressPosition:按下的时候指针的位置

delta:指针移动增量

clickCount:连击次数

clickTime:点击时间

pressEventCamera:最后一个OnPointerPress按下事件关联的摄像机

enterEvetnCamera:最后一个OnPointerEnter进入事件关联的摄像机

实例代码

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
public class Lesson18 : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerDownHandler, IPointerUpHandler, IDragHandler { public void OnDrag(PointerEventData eventData) { print(eventData.delta); } public void OnPointerDown(PointerEventData eventData) { print("鼠标(触碰)按下"); print(eventData.pointerId); print(eventData.position); } public void OnPointerEnter(PointerEventData eventData) { //鼠标进入 在移动设备上 是不存在 因为不存在 进入的概念 print("鼠标进入"); } public void OnPointerExit(PointerEventData eventData) { //鼠标离开 在移动设备上 是不存在 因为不存在 进入的概念 print("鼠标离开"); } public void OnPointerUp(PointerEventData eventData) { print("鼠标(触碰)抬起"); } }

总结

好处:

需要监听自定义事件的控件挂载继承实现了接口的脚本就可以监听到一些特殊事件

可以通过它实现一些长按,双击拖拽等功能

坏处:

不方便管理,需要自己写脚本继承接口挂载到对应控件上,比较麻烦

第2节: EventTrigger事件触发器

EventTrigger 事件触发器

知识点一 事件触发器是什么

事件触发器是EventTrigger组件(请手动加到你要影响的单个UI组件上,然后建议在面板上控制所有UI)

它是一个集成了上节课中学习的所有事件接口的脚本

它可以让我们更方便的为控件添加事件监听

知识点二 如何使用事件触发器

1.拖曳脚本进行关联

2.代码添加

申明一个希望监听的事件对象

EventTrigger.Entry entry = new EventTrigger.Entry();

申明 事件的类型

entry.eventID = EventTriggerType.Drag;

监听函数关联

entry.callback.AddListener((data) =>

{

print(“抬起”);

});

把申明好的 事件对象 加入到 EventTrigger当中

et.triggers.Add(entry);

entry = new EventTrigger.Entry();

申明 事件的类型

entry.eventID = EventTriggerType.BeginDrag;

监听函数关联

entry.callback.AddListener((data) =>

{

print(“抬起”);

});

et.triggers.Add(entry);

entry = new EventTrigger.Entry();

申明 事件的类型

entry.eventID = EventTriggerType.BeginDrag;

监听函数关联

entry.callback.AddListener((data) =>

{

print(“抬起”);

});

et.triggers.Add(entry);

总结

EventTrigger可以让我们写更少的代码

可以在面板类中处理面板控件的事件逻辑,更加的面向对象,便于管理

警告:组件内的大部分参数要求为BaseEventData,此为PointerEventData 的父类,记得进行转换。

第3节: 屏幕坐标转UI相对坐标

屏幕坐标转UI相对坐标

知识点一 RectTransformUtility类

RectTransformUtility 公共类是一个RectTransform的辅助类

主要用于进行一些 坐标的转换等等操作

其中对于我们目前来说 最重要的函数是 将屏幕空间上的点,转换成UI本地坐标下的点

知识点二 将屏幕坐标转换为UI本地坐标系下的点

方法:

RectTransformUtility.ScreenPointToLocalPointInRectangle

参数一:相对父对象(非常重要的参数,毕竟你要求的矩形坐标localpositon是相对父坐标的Pivot坐标(中心点坐标))

参数二:屏幕点

参数三:摄像机

参数四:最终得到的点

一般配合拖拽事件使用

Vector2 nowPos;

RectTransformUtility.ScreenPointToLocalPointInRectangle(

parent,

eventData.position,

eventData.enterEventCamera,

out nowPos );

this.transform.localPosition = nowPos;

this.transform.position += new Vector3(eventData.delta.x, eventData.delta.y, 0);

注意

请结合RectTransform中localPosition与anchoredPosition的区别来进行理解。

ScreenPointToLocalPointInRectangle得到的nowPos 应该理解为物体相对父坐标的locapositon(定义类似Pivot坐标(中心点坐标)下中子物体相对于父物体的localposition,不受锚点影响,受到子类中心点影响),而不是上文的anchoredPosition(即rect面板直接显示的值,受到锚点影响)。

第4节: 遮罩Mask

Mask遮罩

知识点一 遮罩是什么

在不改变图片的情况下

让图片在游戏中只显示其中的一部分

知识点二 遮罩如何使用

实现遮罩效果的关键组件时Mask组件

通过在父对象上添加Mask组件即可遮罩其子对象

注意

1.想要被遮罩的Image需要勾选Maskable

2.只要父对象添加了Mask组件,那么所有的UI子对象都会被遮罩

3.遮罩父对象图片的制作,不透明的地方显示,透明的地方被遮罩

4.Scroll View的Viewport就是利用了遮罩的功能。(甚至其子物体content可以去掉,当然还是建议把内容当作content的子物体用来方便管理)

第5节: 模型和粒子显示在UI之前

模型和粒子显示在UI之前

知识点一 模型显示在UI之前

方法一:直接用摄像机渲染3D物体

Canvas的渲染模式要不是覆盖模式

摄像机模式 和 世界(3D)模式都可以让模型显示在UI之前(Z轴在UI元素之前即可)

注意:

1.摄像机模式时建议用专门的摄像机渲染UI相关

2.面板上的3D物体建议也用UI摄像机进行渲染

方法二:将3D物体渲染在图片上,通过图片显示

专门使用一个摄像机渲染3D模型,将其渲染内容输出到Render Texture上

类似小地图的制作方式

再将渲染的图显示在UI上

该方式 不管Canvas的渲染模式是哪种都可以使用

知识点二 粒子特效显示在UI之前

粒子特效的显示和3D物体类似,使用知识点一的两种方法即可

注意点:

在摄像机模式下时

可以在粒子组件的Renderer相关参数中改变排序层 让粒子特效始终显示在其之前不受Z轴影响

第6节: 异形按钮

异形按钮

知识点一 什么是异形按钮

图片形状不是传统矩形的按钮

知识点二 如何让异形按钮能够准确点击

方法一 通过添加子对象的形式

按钮之所以能够响应点击,主要是根据图片矩形范围进行判断的

它的范围判断是自下而上的,意思是如果有子对象图片,子对象图片的范围也会算为可点击范围

那么我们就可以用多个透明图拼凑不规则图形作为按钮子对象用于进行射线检测

方法二 通过代码改变图片的透明度响应阈值

1.第一步:修改图片参数 开启Read/Write Enabled开关

2.第二步:通过代码修改图片的响应阈值

该参数含义:指定一个像素必须具有的最小alpha值,以变能够认为射线命中了图片

说人话:当像素点alpha值小于了 该值 就不会被射线检测了

img.alphaHitTestMinimumThreshold = 0.1f;

实际操作参考文章

(17条消息) UGUI-- 异形按钮_ugui 异形按钮_Go_Accepted的博客-CSDN博客

第7节: 自动布局组件

自动布局组件

知识点一 自动布局是什么

虽然UGUI的RectTransform已经非常方便的可以帮助我们快速布局

但UGUI中还提供了很多可以帮助我们对UI控件进行自动布局的组件

他们可以帮助我们自动的设置UI控件的位置和大小等

自动布局的工作方式一般是

自动布局控制组件 + 布局元素 = 自动布局

自动布局控制组件:Unity提供了很多用于自动布局的管理性质的组件用于布局

布局元素:具备布局属性的对象们,这里主要是指具备RectTransform的UI组件

知识点二 布局元素的布局属性

要参与自动布局的布局元素必须包含布局属性

布局属性主要有以下几条

Minmum width:该布局元素应具有的最小宽度

Minmum height:该布局元素应具有的最小高度

Preferred width:在分配额外可用宽度之前,此布局元素应具有的宽度

Preferred height:在分配额外可用高度之前,此布局元素应具有的高度。

Flexible width:此布局元素应相对于其同级而填充的额外可用宽度的相对量

Flexible height:此布局元素应相对于其同级而填充的额外可用宽度的相对量

在进行自动布局时 都会通过计算布局元素中的这6个属性得到控件的大小位置

在布局时,布局元素大小设置的基本规则是

1.首先分配最小大小Minmum width和Minmum height

2.如果父类容器中有足够的可用空间,则分配Preferred width和Preferred height

3.如果上面两条分配完成后还有额外空间,则分配Flexible width和Flexible height

一般情况下布局元素的这些属性都是0

但是特定的UI组件依附的对象布局属性会被改变,比如Image和Text

一般情况下我们不会去手动修改他们,但是如果你有这些需求

可以手动添加一个LayoutElement组件 可以修改这些布局属性

知识点三 水平垂直布局组件

水平垂直布局组件

将子对象并排或者竖直的放在一起

组件名:Horizontal Layout Group 和 Vertical Layout Group

参数相关:

Padding:左右上下边缘偏移位置

Spacing:子对象之间的间距

ChildAlignment:九宫格对其方式

Control Child Size:是否控制子对象的宽高

Use Child Scale:在设置子对象大小和布局时,是否考虑子对象的缩放

Child Force Expand:是否强制子对象拓展以填充额外可用空间

知识点四 网格布局组件

网格布局组件

将子对象当成一个个的格子设置他们的大小和位置

组件名:Grid Layout Group

参数相关:

Padding:左右上下边缘偏移位置

Cell Size:每个格子的大小

Spacing:格子间隔

Start Corner:第一个元素所在位置(4个角)

Start Axis:沿哪个轴放置元素;Horizontal水平放置满换行,Vertical竖直放置满换列

Child Alignment:格子对其方式(9宫格)

Constraint:行列约束

Flexible:灵活模式,根据容器大小自动适应

Fixed Column Count:固定列数

Fixed Row Count:固定行数

知识点五 内容大小适配器(重点)

内容大小适配器

它可以自动的调整RectTransform的长宽来让组件自动设置大小

一般在Text上使用 或者 配合其它布局组件一起使用

组件名:Content Size Fitter

参数相关

Horizontal Fit:如何控制宽度

Vertical Fit:如何控制高度

可选:

Unconstrained:不根据布局元素伸展

Min Size:根据布局元素的最小宽高度来伸展

Preferred Size:根据布局元素的偏好宽度来伸展宽度。

注意:设置后,对于的宽和高将变灰,不再允许开发者调整

知识点六 宽高比适配器

宽高比适配器

1.让布局元素按照一定比例来调整自己的大小

2.使布局元素在父对象内部根据父对象大小进行适配

组件名:Aspect Ratio Fitter

参数相关:

Aspect Mode:适配模式,如果调整矩形大小来实施宽高比

None:不让矩形适应宽高比

Width Controls Height:根据宽度自动调整高度

Height Controls Width:根据高度自动调整宽度

Fit In Parent:自动调整宽度、高度、位置和锚点,使矩形适应父项的矩形,同时保持宽高比,会出现“黑边”

Envelope Parent:自动调整宽度、高度、位置和锚点,使矩形覆盖父项的整个区域,同时保持宽高比,会出现“裁剪”

Aspect Ratio:宽高比;宽除以高的比值

第8节: 画布组Canvas Group

Canvas Group

知识点一 问题:如何整体控制一个面板的淡入淡出等

如果我们想要整体控制一个面板的淡入淡出 或者 整体禁用

使用目前学习的知识点 是无法方便快捷的设置的

知识点二 解决方案:Canvas Group

为面板父对象添加 CanvasGroup组件 即可整体控制

参数相关:

Alpha:整体透明度控制

Interactable:整体启用禁用设置

Blocks Raycasts:整体射线检测设置

Ignore Parent Groups:是否忽略父级CanvasGroup的作用

第7章: 总结

总结

实战总结

尽量自主的完成了一个标准的服务器登陆系统。

难点代码展示

难点代码如下:

只有选服面板有点难

服务器选服初始化面板

guage-csharp
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class ServerPanel : BasePanel { public GameObject leftCotent; //获取左侧内容区脚本 public GameObject rightCotent; //获取右侧内容区脚本 private List<rightserver> rightservers = new List<rightserver>(); //存储所有的rightserver组件,方便销毁 public override void Showme() { base.Showme(); //todo 这里应该进行初始化,比如上次选择的服务器之类,但是懒得写了 } public override void Init() { base.Init(); //初始化左侧服务器栏 for (int i = 1; i < DataManger.Instance.ServerInfo.Count; i = i + 5) { //获取预制体,初始化并添加到左侧浏览区 GameObject leftserver = GameObject.Instantiate(Resources.Load<GameObject>("SmallUi/leftserver")); leftserver.transform.SetParent(leftCotent.transform, false); //说明是结尾服务器 if (i + 5 > DataManger.Instance.ServerInfo.Count - 1) { leftserver.GetComponent<leftserver>().setext(i, DataManger.Instance.ServerInfo.Count - 1); //为左边按钮添加点击功能(在这里实现是为了right和left之间无法互相各自调用,干脆让面板当桥梁) //todo 这里的处理方式有点铸币,根据老师的写法,你直接可以从leftpanel获取此面板进行调用,没必要写什么回调函数,为了纪念,不改了。 leftserver.GetComponent<leftserver>().leftevent = addleftserver; } //说明是中间服务器 else { leftserver.GetComponent<leftserver>().setext(i, i + 4); //为左边按钮添加点击功能(在这里实现是为了right和left之间无法互相各自调用,干脆让面板当桥梁) //todo 这里的处理方式有点铸币,根据老师的写法,你直接可以从leftpanel获取此面板进行调用,没必要写什么回调函数,为了纪念,不改了。 leftserver.GetComponent<leftserver>().leftevent = addleftserver; //todo 我在这里遭遇了闭包陷阱 } ((leftserver.transform) as RectTransform).anchoredPosition = new Vector3(0, 100 + (i / 5) * -70); if (i == 1) { leftserver.GetComponent<Button>().onClick.Invoke(); } } //进来先把右侧的1-5服务器显示出来 //todo 这里我用来打算考button的OnSubmit方法来实现(loginpanel这么干没问题),但是无效,具体原因有待未来细究 //todo 根据研究,是我使用start的原因,很可能在点击的时候按钮还没有初始化完成,使用awake即可 } //用来加载右面版组件 public void addleftserver(int start, int end) { //销毁原来的右面版组件 if (rightservers.Count > 0) { foreach (rightserver onerightserver in rightservers) { GameObject.Destroy(onerightserver.gameObject); } } rightservers = new List<rightserver>(); for (int i = start; i <= end; i++) { //获取预制体,初始化并添加到右侧浏览区 GameObject rightserver = GameObject.Instantiate(Resources.Load<GameObject>("SmallUi/rightserver")); rightserver.transform.SetParent(rightCotent.transform, false); rightserver.GetComponent<rightserver>().setext(i); ((rightserver.transform) as RectTransform).anchoredPosition = new Vector3(-120 + (i - start) % 2 * 250, 100 - 80 * ((i - start) / 2)); rightservers.Add(rightserver.GetComponent<rightserver>()); } } }

左服务器小组件

guage-csharp
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; public class leftserver : MonoBehaviour { public Button btn; public UnityEngine.UI.Text text; private int startserver; //首服务器id private int endserver; //尾服务器id public UnityAction<int, int> leftevent; //使用回调函数,方便ServerPanel调用,来对left和right的server进行点击显示的设计 void Awake() { //todo 点击后提示面板 切换显示的服务器组 btn.onClick.AddListener(() => { leftevent?.Invoke(startserver, endserver); } ) ; } //初始化 public void setext(int startserver, int endserver) { this.startserver = startserver; this.endserver = endserver; text.text = startserver + "区-" + endserver + "区"; } }

右服务器小组件

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; public class rightserver : MonoBehaviour { public Button btn; public UnityEngine.UI.Text text; public Image isnew; public Image status; private int serverid; //初始化存储服务器id供按钮使用 void Start() { //点击后更换上次登陆服务器,返回进入游戏页面 btn.onClick.AddListener(() => { DataManger.Instance.savelastServerData(serverid); UIManger.Instance.HidePanel<ServerPanel>(); UIManger.Instance.ShowPanel<ChosePanel>(); } ) ; } //初始化外形(传入服务器id,服务器是否为新,服务器状态) public void setext(int serverid) { this.serverid = serverid; text.text = serverid + "区" + " " + DataManger.Instance.ServerInfo[serverid].name; //如果不是新服,隐藏标签 if (!DataManger.Instance.ServerInfo[serverid].isNew) { this.isnew.gameObject.SetActive(false); } //根据状态选取对应图片 switch (DataManger.Instance.ServerInfo[serverid].state) { case 0: this.status.sprite = Resources.Load<Sprite>("status0"); break; case 1: this.status.sprite = Resources.Load<Sprite>("status1"); break; case 2: this.status.sprite = Resources.Load<Sprite>("status2"); break; case 3: this.status.sprite = Resources.Load<Sprite>("status3"); break; } } }

展示:

UI管理器脚本

guage-csharp
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class UIManger { private static UIManger instance = new UIManger(); public static UIManger Instance => instance; public Dictionary<String, BasePanel> panelDic = new Dictionary<string, BasePanel>(); private Transform canvasTrans; //应该一开始 就得到我们的 Canvas对象,对它进行管理,防止切换场景后失联 private UIManger() { //得到场景上创建好的 Canvas对象 canvasTrans = GameObject.Find("Canvas").transform; //让 Canvas对象 过场景 不移除 //我们都是通过 动态创建 和 动态删除 来显示 隐藏面板的 所以不删除它 影响不大 GameObject.DontDestroyOnLoad(canvasTrans.gameObject); } public T ShowPanel<T>() where T : BasePanel { String panelname = typeof(T).Name; //如果场景上已经存在此对象,直接抽出来丢给开发者 if (panelDic.ContainsKey(panelname)) { Debug.Log("你试图二次调用" + panelname); return panelDic[panelname] as T; } //否则,按照步骤创造对象 else { //创建预制体 GameObject panel = GameObject.Instantiate(Resources.Load<GameObject>("UI/" + panelname)); panel.transform.SetParent(canvasTrans, false); //获取预制体上的脚本 T panelscript = panel.GetComponent<T>(); panelDic.Add(panelname, panelscript); panelscript.Showme(); return panelscript; } } public void HidePanel<T>() where T : BasePanel { String panelname = typeof(T).Name; if (panelDic.ContainsKey(panelname)) { T panel = panelDic[panelname] as T; panel.Hideme(() => { GameObject.Destroy(panel.gameObject); } ); //将字典内的删除 panelDic.Remove(panelname); } //否则,吐槽不存在 else { Debug.Log("你要找的这玩意没创建?检查一下!"); } } //获得面板 public T GetPanel<T>() where T : BasePanel { string panelName = typeof(T).Name; if (panelDic.ContainsKey(panelName)) return panelDic[panelName] as T; //如果没有 直接返回空 return null; } }

BasePanel(所有panel的父类)

guage-csharp
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
using System; using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.Events; public abstract class BasePanel : MonoBehaviour { private CanvasGroup canvasGroup; //获取显隐画布 private bool ishow = true; //判断是否进行显隐 private float changetime = 0.3f; //显隐时间 private float nowtime; //当前显隐时间 //当自己淡出成功时 要执行的委托函数 private UnityAction hideCallBack; private void Start() { Init(); } private void Awake() { nowtime = changetime; canvasGroup = this.gameObject.GetComponent<CanvasGroup>(); if (canvasGroup == null) { canvasGroup = this.gameObject.AddComponent<CanvasGroup>(); } } private void Update() { //处理淡入淡出 //淡入 if (ishow == true && nowtime < changetime) { nowtime += Time.deltaTime; this.GetComponent<CanvasGroup>().alpha = Mathf.Lerp(0, 1, nowtime / changetime); if (nowtime >= changetime) { canvasGroup.alpha = 1; } } //淡出 if (ishow == false && nowtime < changetime) { nowtime += Time.deltaTime; this.GetComponent<CanvasGroup>().alpha = Mathf.Lerp(1, 0, nowtime / changetime); if (nowtime >= changetime) { canvasGroup.alpha = 0; hideCallBack?.Invoke(); } } } public virtual void Init() { } public virtual void Showme() { ishow = true; canvasGroup.alpha = 0; nowtime = 0; gameObject.SetActive(true); } //UI管理器传入委托hideCallBack,决定消失后干什么 public virtual void Hideme(UnityAction hideCallBack) { canvasGroup.alpha = 1; nowtime = 0; ishow = false; this.hideCallBack = hideCallBack; } }

踩的坑

1.在for循环里面使用匿名函数的时候一定要慎重!小心闭包陷阱,规避的办法是使用外部先设置好的变量而不是for循环的变量

2.当你试图初始化自身的时候建议在awake里面,调用其他函数的时候在start里面,这样可以很大的概率防止初始化未完成就被调用的后果!