UGUI-常用组件

基础元素

注意:Raycast Target复选框,如果UI元素不需要点击事件,一定不要勾选它。因为UGUI的事件系统会遍历出所有自带Raycast Target的组件,这会带来一些不必要的开销。

描边和阴影

OutLine 组件:文本的描边

Shadow 组件:文本的阴影

注意:描边的原理就是在原来Text组件上在上、下、左、右各多画一遍,所以它的效率很低。阴影会比描边好很多,因为只需要多画一遍,所以能用阴影就不要用描边。

Image组件

Image组件用来显示图片。

其中图片有4种格式:

  • Simple:直接显示图片

  • Sliced:通过九宫格方式显示图片

  • Tiled:平铺图片

  • Filled:像技能CD一样,可以旋转图片

Raw Image组件

Image组件只显示Sprite,Raw Image组件即可以显示任意Texture,也可以使用Sprite。

Button 组件

public Button button;
void Start()
{
    button.Onclick.AddListener(()=>{ Debug.log("Click") });
}

Slider组件

  • Min Value:滑块所能取到的最小值。

  • Max Value:滑块所能取到的最大值。

  • Whole Numbers:如果勾选,则滑块只能选择整数值。

  • Direction:滑块的方向,可以是水平或垂直。

  • Handle Rect:滑块的手柄部分,通常是一个子对象,用来表示滑块的位置。

  • On Value Changed (Event Trigger):当滑块的值发生变化时触发的事件。

using UnityEngine;
using UnityEngine.UI;

public class SliderExample : MonoBehaviour
{
    public Slider mySlider;

    void Start()
    {
        // 设置滑块的初始值
        mySlider.value = 0.5f;
        // 添加监听器以响应滑块值的变化
        mySlider.onValueChanged.AddListener(OnSliderValueChanged);
    }

    void OnSliderValueChanged(float value)
    {
        Debug.Log("Slider value: " + value);
    }
}

Toggle组件

  • Is On:当前切换的状态,是否被选中。

  • Transition:定义了如何从一种状态过渡到另一种状态,可以是颜色变化、动画或其他自定义效果。

  • Graphic:可选,指定一个图形组件,其颜色会根据 Toggle 的状态改变。

  • OnValueChanged (Event Trigger):当 Toggle 状态改变时触发的事件。

using UnityEngine;
using UnityEngine.UI;

public class ToggleExample : MonoBehaviour
{
    public Toggle myToggle;
    void Start()
    {
        // 设置默认状态为未选中
        myToggle.isOn = false;
        // 添加监听器以响应切换状态的变化
        myToggle.onValueChanged.AddListener(OnToggleValueChanged);
    }

    void OnToggleValueChanged(bool isOn)
    {
        if (isOn)
        {
            Debug.Log("Toggle is ON");
        }
        else
        {
            Debug.Log("Toggle is OFF");
        }
    }
}

ScrollerView滚动视图

该对象中有一个ScrollerRect滚动视图组件

结构如下

Scroller Rect父对象

  • Viewport:控制滚动视图可视范围的内容显示

    • Content:可视内容的区域

      • 可以填充内容

  • Scrollbar Horizontal:水平滚动条(可以删除)

  • Scrollbar Vertical:垂直滚动条(可以删除)

组件参数

  • Content:控制视图滚动显示内容的父对象,它的尺寸由多大,滚动视图就能拖多远

  • Horizontal:启用水平滚动

  • Vertical:启用垂直滚动

  • Movement Type:滚动视图元素的运动类型。主要控制拖动时的反馈效果

    • Unrestricted(一般不使用):不受限制,随便拖动

    • Elastic:回弹效果,当滚出边缘后,会弹回边界,有一个参数Elasticity回弹系数,值越大回弹的越慢

    • Clamped:夹紧效果,时钟限制在范围内,没有回弹效果

  • Inertia:移动惯性,如果开启,松开鼠标后会有一定的移动惯性。有一个参数Deceleration Rate减速率(0~1),0没有惯性,1则不会停止。

  • Scroll Sensitivity:滚动事件的敏感性(鼠标中间),值越大,滚的越多。

  • ViewPort:关联是滚动视图内容视口对象

  • Horizontal Scrollbar:关联水平滚动条

  • Visibility:是否在不需要时自动隐藏

    • Permanent:一直显示滚动条

    • Auto Hide:自动隐藏滚动条

    • Auto Hide And Expand ViewPort:自动隐藏滚动条并自动扩展内容视口

  • Spacing:滚动条和视口之间的间隔空间

  • OnValueChanged:滚动视图位置改变时执行的函数列表

using UnityEngine;
using UnityEngine.UI;

public class ScrollViewController : MonoBehaviour
{
    public ScrollRect scrollRect; // 在Inspector中分配或通过代码获取

    void Start()
    {
        if (scrollRect == null)
        {
            // 如果没有在Inspector中分配,尝试通过GetComponent获取
            scrollRect = GetComponent<ScrollRect>();
        }
    }

    void SetScrollPosition(Vector2 position)
    {
        if (scrollRect != null)
        {
            scrollRect.normalizedPosition = position;
        }
    }

    // 示例:滚动到顶部
    void ScrollToTop()
    {
        SetScrollPosition(new Vector2(0, 1));
    }

    // 示例:滚动到底部
    void ScrollToBottom()
    {
        SetScrollPosition(new Vector2(0, 0));
    }

    /*   动态添加或移除内容  */


    public GameObject contentPrefab; // 要添加的内容预制体
    public Transform contentTransform; // ScrollView的Content对象

    void AddContentItem()
    {
        if (contentPrefab != null && contentTransform != null)
        {
            // 实例化预制体并设置为Content的子对象
            GameObject newItem = Instantiate(contentPrefab, contentTransform);
            // 根据需要调整新项的位置或属性
        }
    }

    void RemoveContentItem(int index)
    {
        if (contentTransform != null && contentTransform.childCount > index)
        {
            // 移除指定索引处的子对象
            Destroy(contentTransform.GetChild(index).gameObject);
        }
    }
}

Canvas组件

我们可以将Canvas理解为画画的纸。所有UI元素都必须在Canvas下面,并且它支持嵌套。

Canvas在两个模式下(Overlay和Camera)会覆盖整个屏幕,所做屏幕UI显示时,只需要处理好Canvas中的显示即可。

注意:所有UI元素必须建立在Canvas下,否则会出现不显示的问题!!!

在Scence场景中选择第5个,选择2d,切换视图,就能够更好的操作。

🎉️ 场景中允许有多个Canvas对象,可以分别管理不同画布的渲染方式,分辨率适应方式等等参数

Canvas组件公共参数

  • Additional Shader Channels:允许开发者指定哪些额外的顶点属性应该被包含在由 Canvas 生成的网格中,以便这些属性可以被自定义着色器使用。这为创建更加复杂和高级的视觉效果提供了灵活性。

    • None: 这是默认选项,意味着不添加任何额外的顶点数据。只有位置、颜色和UV0会被传递给着色器。

    • TexCoord1, TexCoord2, TexCoord3: 这些选项允许你启用额外的纹理坐标集。对于需要多纹理贴图或更复杂的UV映射的着色器来说非常有用。例如,如果你正在实现一个带有法线贴图的UI元素,你可能需要 TexCoord1 来提供第二个UV集用于采样法线贴图

    • Normal: 启用此选项后,法线信息将被包含在顶点数据中。这对于那些依赖于光照计算或需要基于表面方向的效果的着色器至关重要。比如,当你想要在UI上应用光照效果时,就需要确保法线数据可用

    • Tangent: 切线信息同样会被包含进来。切线空间通常用于法线贴图和其他与表面相关的计算。当你的UI着色器涉及到法线贴图或其他类型的切线空间操作时,应该启用这一选项

  • Vertex Color Always In Grama Color Space:顶点颜色(Vertex Color)通常用于为3D模型或UI元素提供额外的颜色信息。然而,当涉及到颜色空间的处理时,特别是在线性工作流(Linear Color Space Workflow)中,如何正确地处理顶点颜色变得尤为重要。默认情况下,Unity中的顶点颜色被认为是处于伽马空间(Gamma Color Space),这意味着它们在被传递给着色器之前需要进行伽马到线性的转换,以确保最终渲染结果的准确性。当使用线性颜色空间工作流时,Unity会自动对带有sRGB标签的纹理执行伽马到线性的转换。但是,对于顶点颜色,默认情况下并不会自动进行这样的转换。这是因为顶点颜色不像纹理那样有明确的标记来指示它们是否处于伽马空间。因此,如果你直接在模型上设置了顶点颜色,并且这些颜色是在伽马空间中定义的,那么在使用线性颜色空间工作流时,你可能会注意到颜色看起来比预期的要暗。为了应对这一问题,Unity提供了一个名为 Canvas.vertexColorAlwaysGammaSpace 的属性。这个属性的作用是确保Canvas上的顶点颜色始终以伽马空间的形式传递给UI着色器。这样做可以在着色器内部完成伽马到线性的转换,从而提高颜色精度,特别是对于较暗的颜色。内置的UI着色器已经包含了从伽马空间到线性空间的转换逻辑,因此启用这个选项可以帮助保持颜色的一致性和准确性

  • Pixel Perfect:让边缘清晰不模糊

  • Sorting Order :画布的深度,指定了相机的渲染顺序

Canvas的三种渲染模式

Overlay 模式:Canvas会贴在相机镜头上,所有的UI元素始终都会在屏幕上显示(不管摄像机的位置怎么改变)

Camera 模式:3D物品显示在UI的前面

World Space 模式:在这个模式下,可以将Canvas理解为一个3d物体。它在屏幕上的显示就跟3d物体在屏幕上的显示一样。

双相机叠加

1. 建立UI Camera

2. 将UI拖拽给Canvas

3. 将UI的相机设置为使用深度值填充,UI相机的深度要高于主相机

4. 将主相机不渲染UI元素,将UI相机只渲染UI元素(Culling Mask)

注意点❤❤❤

这个搞了我1个小时,才发现的问题。

我们通过代码获取Canvas组件的信息不能够在awake中获取,只能够在start中获取

比如,我想要得到画布的缩放系数

private void Start()
{
    // 这段代码不能够写在awake中
    scaleX = canvas.transform.localScale.x;
}

Canvas Scaler 组件

控制UI画布的放大、缩放的比例。

  • 屏幕分辨率:它会参与分辨率自适应的计算。可以在Game窗口下的Stats统计数据窗口看拿到。

  • 画布大小和缩放系数:选中Cavas对象后在RectTransform组件中可以看到宽高和缩放

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

  • Reference Pixels Per Unity:每个Unity单元有多少个像素。

  • Scale Factor:缩放因素(将图像中的大小在屏幕中缩放为原来的几倍。但是本身的宽高并不改变。)

Canvas Scaler的3种适配模式

决定了UI元素如何适应屏幕

1. 恒定像素模式Constant Pixel Size

在此模式下,UI元素的像素大小保持不变,无论屏幕大小如何变化。这可能会导致UI元素在某些情况下看起来过大或过小。

  • Scale Factor: 缩放系数,此系数缩放画布中的所有UI元素

  • Reference Pixel Per Unit:每个Unity单元有多少个像素(默认为一个单位100个像素),而且图片设置中的Pixel Per Unit也会与该参数进行计算。

  • UI元素尺寸 = \frac{图片大小(像素)}{Pixels Per Unit} * Reference Pixels PerUnit

👀️ 我们假设一张精灵的大小为 2048 932,Pixels Per Unit为100,则它的 Unit大小为 20.48 9.32,我们把它放到画布中,它的实际大小为 (20.48 9.32) Reference Pixels Per Unit 的值,如果Reference Pixels Per Unit为100,这精灵的原始大小为 2048 * 932

Reference Pixels Per Unit为50,这精灵的原始大小为 1024 * 476

2. 缩放模式Scale With Screen Size

在此模式下,UI元素将根据屏幕大小进行缩放。这可以确保UI元素始终填充屏幕,但可能会改变其原始的纵横比。

Reference Resolution:参考分辨率(美术同学出图的标准分辨率)

👀️通常情况下PC端是1920 * 1080

Screen Match Mode:屏幕匹配方式

  • Expand:扩展模式。

无论你怎么对屏幕进行缩放,图片都能够被显示出来。

👀️ 假设一张图片是 2222 1080的图片,而屏幕分辨率是1920 1080。为了保留图片的所有细节,让图片的宽2222缩小为1920(缩放因子是1.15),相对的图片的高1080也会相对缩小(1080÷1.15=939),则会发现屏幕的高会出现(1080 - 939=141像素的)黑边

  • Shrink:收缩模式

让整个UI元素填满画布

👀️ 假设一张图片是 2222 1080的图片,而屏幕分辨率是1920 1080,为了让图片填满画面,就需要将图片的2222×1080裁剪为1920×1080

  • Match Width Or Hight:以宽高或者二者的平均值参考来缩放画布区域,表现效果:主要用于只有横屏模式或者竖屏模式的游戏

竖屏游戏:Match=0,将画布宽度设置为参考分辨率的宽度,无论怎么改变窗口的高度,画布的宽度是不变的

横屏游戏:Match=1

3. 恒定物理模式Constant Physical Size

UI元素的物理大小保持不变。这意味着UI元素的实际大小(以厘米或英寸为单位)将保持不变,无论屏幕大小如何变化。这可以确保UI元素在所有屏幕上都具有一致的大小和比例,但可能会导致某些屏幕上的UI元素看起来过大或过小。

Graphic Raycaster组件

Graphic Raycaster用来检测UI输入事件的射线发射器。主要负责通过射线检测玩家和UI元素的交互

判断是否点击了UI元素

  • Ignore Reversed Graphic: 忽略反转图片(如果勾选它,那么只要有旋转的图片,就不能够触发点击事件。)

  • Blocking Objects:射线被哪些类型的碰撞器阻挡(覆盖模式无效)

    • 默认为None

    • 如果是Two D,那么2d物体的碰撞体就会阻挡事件响应

    • 如果是Tree D, 那么3d物体的碰撞体就会阻挡事件响应

  • Blocking Mask: 默认每一个层级都会触发点击响应。勾选的可以触发点击响应,没有勾选的不能触发点击响应。(覆盖模式无效)

CanvasGroup组件的使用

| 属性 | 描述 |

| ------------------- | -------------------------------- |

| Alhpa | 该对象及其子对象是否设置透明度 |

| Interactable | 是否可交互 |

| Ignore Parent Group | 是否忽略父级的Canvas Group的影响 |

## 自动布局组件

### 1. 自动布局属性

要参与自动布局的布局元素必须包含布局属性,在进行自动布局时 都会通过计算布局元素中的这6个属性得到控件的大小位置。

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

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

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

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

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

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

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

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

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

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

一般情况下布局元素的这些属性都是0,但是特定的UI组件依附的对象布局属性会被改变,比如Image和Text

### 2. 水平垂直布局

组件名:Horizontal Layout Group 和 Vertical Layout Group

参数相关

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

* Spacing:子对象之间的间距

* ChildAlignment:九宫格对其方式

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

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

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

### 3. 网格布局组件

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

组件名: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:固定行数

### 4. 内容大小适配器

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

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

组件名:Content Size Fitter

参数相关

* Horizontal Fit:如何控制宽度

* Vertical Fit:如何控制高度

* Unconstrained:不根据布局元素伸展

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

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

### 5. 宽高比适配器

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

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

组件名:Aspect Ratio Fitter

参数相关:

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

* None:不让矩形适应宽高比

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

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

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

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

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

## Rect Transfom

### 1. 组件参数解释

![image-dkcv.png](/upload/image-dkcv.png)

### 2. 锚点和中心点快速显示

![image-mugr.png](/upload/image-mugr.png)

### 3. 通过代码获取UI的高度和宽度

```

RectTransform rt = this.transform as RectTransform;

// 这里的rt.rect指的是panel的宽高

// panel就是把UI的背景去掉,最后的那个框框就是panel

rt.rect.width;

rt.rect.height;

// 获得图片的尺寸

rt.sizeDelta;

```

### 4. RectTransformUtils

```

// 将屏幕坐标转换为UI本地坐标系下的点

// RectTransformUtility.ScreenPointToLocalPointInRectangle

// 参数一:相对父对象

// 参数二:屏幕点

// 参数三:摄像机

// 参数四:最终得到的点

// 一般配合拖拽事件使用

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);

```

## 渲染层级

以Hierarchy参考

* 下方物体在上方物体前显示

* 子物体在父物体前显示

* 下方物体永远在前显示,无论上方的层次结构

## Atlas图集

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

### 1. Draw Call

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

![image.png](/upload/2022/11/image-5b0739811e8d4a3ca7bfd7d58448078a.png)屏幕的显示流程:CPU->加载数据->内存->显存->显卡->显示器

Batchs,就是当前渲染屏幕所有内容所需要的绘制调用数

每一张独立的UI图,会产生一个DrawCall

### 2. 启用打包图集

Unity默认是不会打包图集,所以我们需要在设置中开启图集。

在Edit->Project Setting->Editor->Sprite Packer

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

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

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

### 3. 打包图集注意事项

我们在使用图集的时候,图集里面的**渲染顺序一定是连续**的。不然无法降低DrawCall。

### 4. 代码加载

```

SpriteAtlas sa = Resources.Load<SpriteAtlas>("MyAlas"); // 读取某张图集

sa.GetSprite("bankground"); // 读取图集中的某张图片

```

# UGUI实例与练习

## 案例:长按UI按钮蓄力

【需求】:长按一个UI按钮0.2s后开始蓄力,松开按钮后结束蓄力。蓄力满后HP+10,如果没有松开按钮 继续蓄力。

【思路】

* 有一个蓄力值 0-10

* 长按按钮:鼠标按下和鼠标抬起的接口(搭配可实现长按)

```

using UnityEngine;

using UnityEngine.EventSystems;

namespace DY.UGUIDemo

{

public class Demo1 : MonoBehaviour,IPointerDownHandler,IPointerUpHandler

{

public float energyValue = 0; // 当前状态的蓄力值

public float MaxEnergy = 10; // 最大蓄力值

public float energyPerSecond = 1; // 每秒蓄力多少

public float Hp = 10;

public float startTime = 0;

private bool pressState; // 按下的状态

public void OnPointerDown(PointerEventData eventData)

{

pressState = true; // 表示鼠标按下了

}

public void OnPointerUp(PointerEventData eventData)

{

pressState = false; // 表示鼠标抬起了

startTime = 0; // 清空计时器

}

// Update is called once per frame

void Update()

{

// 鼠标按下,记录时间

if(pressState)

{

startTime += Time.deltaTime;

if(startTime > 0.2f)

{

// 开始蓄力

energyValue += Time.deltaTime * energyPerSecond;

if(energyValue > MaxEnergy)

{

energyValue = MaxEnergy;

Hp += Time.deltaTime * 10;

}

}

}

if( !pressState && energyValue != 0)

{

energyValue-=Time.deltaTime;

if(energyValue <= 0)

{

energyValue = 0;

}

}

}

}

}

```

## 案例:摇杆实现物体移动

【需求】:制作一个UGUI摇杆可以控制场景上的对象移动

【思路】

* 摇杆跟随鼠标移动:使用事件触发器来拖拽事件

* 拖拽结束后回到中心点:使用endDraw事件结束拖拽,使用锚点来回到中心点

* 限制拖拽范围:做一个向量长度判断即可

* 对象移动:先让对象正前方与摇杆方向一致(转身),然后在前进

```

using UnityEngine;

using UnityEngine.EventSystems;

namespace DY.UGUIDemo

{

public class Demo2 : MonoBehaviour

{

public EventTrigger et;

public RectTransform imgJoy;

public GameObject player;

Vector3 movementDir; // 物体的移动方向

private void Start()

{

// 拖动中

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

en.eventID = EventTriggerType.Drag;

en.callback.AddListener(JoyDrag);

et.triggers.Add(en);

// 结束拖拽

en = new EventTrigger.Entry();

en.eventID = EventTriggerType.EndDrag;

en.callback.AddListener(EndJoyDrag);

et.triggers.Add(en);

}

// 跟随摇杆

public void JoyDrag(BaseEventData data)

{

PointerEventData eventData = data as PointerEventData;

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

if(imgJoy.anchoredPosition.magnitude > 40)

{

imgJoy.anchoredPosition = imgJoy.anchoredPosition.normalized * 40;

}

if (imgJoy.anchoredPosition.magnitude >= 1f) // 设置阈值

{

Vector3 dir = imgJoy.anchoredPosition;

movementDir.x = dir.x;

movementDir.y = 0;

movementDir.z = dir.y;

player.transform.LookAt(player.transform.position + movementDir.normalized);

player.transform.Translate(movementDir * Time.deltaTime);

}

}

public void EndJoyDrag(BaseEventData data)

{

imgJoy.anchoredPosition = Vector2.zero;

}

}

}

```

## 案例:拖动UI图像

问题:拖拽过程中,遇到的问题?

![image.png](/upload/2022/10/image-a165c80b08464e5d80b18f9b27b32d47.png)

```language

using UnityEngine;

using UnityEngine.EventSystems;

public class DragObject : MonoBehaviour, IPointerDownHandler, IDragHandler, IEndDragHandler

{

RectTransform rect;

Vector3 worldPoint;

Vector3 offset; // 偏移量

private void Start()

{

rect = GetComponent<RectTransform>();

}

public void OnPointerDown(PointerEventData eventData)

{

RectTransformUtility.ScreenPointToWorldPointInRectangle(rect, eventData.position, null, out worldPoint);

offset = transform.position - worldPoint;

}

public void OnDrag1(PointerEventData eventData)

{

// 将屏幕坐标系上的点,转化为世界坐标系的点,然后移动

RectTransformUtility.ScreenPointToWorldPointInRectangle(rect, eventData.position, null, out worldPoint);

transform.position = worldPoint + offset;

}

// 更加简单的方式实现拖拽

public void OnDrag(PointerEventData eventData)

{

rect.anchoredPosition += eventData.delta;

}

public void OnEndDrag(PointerEventData eventData){}

}

```

### 图片置灰

### 粒子特效和UI的排序

### Mask & RectMask2D 裁切

### 粒子的裁切

### 粒子自适应

### 滑动列表嵌套