Unity基础笔记(3)—— Unity UI系统

Unity UI系统

一、UI系统介绍

1. Unity2D 和 UI的区别

Unity2D:主要基于 SpriteRenderer 和 2D物理系统等组成。

UI:由 Canvas 以及 各种 UI 组件组成。

UI 的显示不基于 SpriteRenderer,且UI界面是完全贴合屏幕的,不会随相机的移动而移动。

2. UI系统的组成

  • Canvas:画布,UI 的根节点,尺寸大小和画面分辨率一致;
  • EventSystem:事件系统,与Canvas绑定,只有基于事件系统才能使按钮可以点击;
  • Image / Text等组件:具体的功能组件,显示一张图、制作一个按钮等。

二、Image 组件

1. Image 组件

UI 中除了文本,最多的就是图片、按钮。Image 组件主要复制图片的显示。

Image 游戏物体必须放在 Canvas 游戏物体下才能生效。

主要属性:

  • SourceImage:源图片,在脚本中叫 Sprite;
  • Color:图片颜色;
  • RaycastTarget:是否可以作为射线目标,后续点击、拖拽等事件需要使用;
  • ImageType:显示模式;
    • Simple:普通模式
    • Sliced:切片,需要图片九宫格,要在资源层面处理
    • Tiled:平铺
    • Filled:填充

主要功能:

  • Set Native Size:设置为图片的原始尺寸。

2. 图片资源

在项目管理器中点击一个图片资源,然后在检查器面板中可以看到资源的相关属性设置。

  1. TextureType:Unity中大多数情况下我们都需要选择为 Sprite
  2. Sprite Model:精灵模式,精灵是 Image 组件持有的实际图片。
    • Single:一个图片一个精灵
    • Multiple:一个图片拆成多个精灵
  3. Pixels Per Unit:像素单位,会影响实际在游戏中图片的尺寸,数值越大游戏中越小
  4. Pivot:图片的中心
  5. Sprite Editor按钮:编辑精灵

3. SpriteEditor

  • PackageManager:
    • 插件/功能管理器,Window -> PackageManager
    • Unity 中有很多官方开发好的功能,但是不见得每个游戏项目都需要,所以我们可以在插件管理器中选择自己需要的功能来安装,SpriteEditor 需要 2D Sprite插件
  • 精灵编辑
    • 设置 Pivot 轴心
    • Revert:恢复设置
    • Apply:确认操作
    • 蓝色外框是精灵的九宫格

4. SpriteEditor-Slice 图片切片

图片切片:实际开发中很多时候我们并不会一个按钮一个图片,而是选择打包在一起,所以需要在 Unity 中进行分割。

  1. Automatic:自动分割,实际是根据不同游戏物体之间的透明通道进行;
  2. Grid By Cell Size:根据网格像素分割,根据一个宽高(像素)自动分割;
  3. Grid By Cell Count:根据网格数量分割,就是将一个图片等分拆成几行几列;
  4. Pivot:分割时每一个精灵的轴心;
  5. 拖拽鼠标:手动分割;
  6. 编辑过程中白色的框是一个精灵,点击后可以设置这个精灵的细节。

如果需要用脚本操作UI相关操作,必须引入UI的命名空间 System.UnityEngine.UI

三、Text 组件

1. Text 组件介绍

Text 组件就是用来显示文本的,比如等级、属性值、角色名称等等。

2. Text 组件常用属性

  1. Text:显示的文本内容;
  2. Font:字体;
  3. Font Style:字体样式,主要是加粗、斜体;
  4. Font Size:字体大小;
  5. Line Spacing:行间距;
  6. RichText:富文本,有html相关经验的可以用,没有可以无视;
  7. Color:字体颜色;
  8. Raycast Target:是否作为射线检测的目标,也就是射线可以检测到,意味着后续是否可以点击等。

3. Text 组件对齐属性

  1. Alignment:文本的对齐方式;

  2. Horizontal Overflow:水平溢出

    (1)Wrap:文本将自动换行,达到水平边界。

    (2)Overflow:文本可以超出水平边界,继续显示。

  3. Vertical Overflow:垂直溢出

    (1)Truncate:文本不显示超出垂直边界的部分。

    (2)Overflow:文本可以超出垂直边界,继续显示。

  4. Best Fit:尽可能匹配边缘

    (1)Min Size:字体的最小大小;

    (2)Max Size:字体的最大大小。

四、Button 组件

1. Button 的构成

Button:Image组件、Button组件

Text:Text组件

2. Button 样式

Interactable:可交互的,也就是按钮是否有效

Transition:过渡方式,按钮一般分为几种状态,比如鼠标悬浮、点击、不可用

  • Target Graphic:按钮影响的 Image;
  • Normal Color/Sprite:常规;
  • Highlighted Color/Sprite:高亮、鼠标悬浮;
  • Pressed Color/Sprite:按下;
  • Disabled Color/Sprite:禁用;
  • Fade Duration:颜色过渡时间

3. Navigation导航

在游戏中,我们经常使用 WASD 或 上下左右方向键等方式来选择按钮,Unity 中的UGUI 也具备这个功能,并且设置起来十分方便。

4. Button 事件

  1. 通过面板添加点击事件;

    On Click()栏 -> 添加操作对象 -> 添加对象上绑定的方法

  2. 通过代码添加点击事件

    private Button button;
    
    private void Start()
    {
        button = GetComponent<Button>();
        button.onClick.AddListener(ButtionClick2);
    }
    
    public void ButtionClick2()
    {
        Debug.Log("ButtonClick2");
    }
    

五、InputField 输入框组件

1. InputField 结构

  • InputField:Image 组件(背景图片)、InputField 组件
    • Placeholder:Text 组件(玩家完全没输入时显示的内容)
    • Text:Text组件实际输入的承载组件

2. 输入类型

InputField 中 ContentType 可以设置玩家输入的类型,比如邮箱、密码等。

3. InputField 事件

与 Button 事件类似,虽然事件不同,但是操作方法基本一致。

需要注意的是,如果需要手动获取 InputField 中玩家输入的值,要直接使用 InputField.text,而不要去查找子物体 Text 中的组件。

  1. 通过面板添加事件:

    • On Value Changed(String):输入框中值改变时进行同步
    • On End Edit(String):结束输入框编辑进行同步
    • On Submit(String):提交时进行同步
  2. 通过代码添加事件

    private InputField inputField;
    void Start()
    {
        inputField = GetComponent<InputField>();
        // 修改InputField中的内容
        /*inputField.text = "12345";
            print(inputField.text);*/
        // OnValueChanged + OnEndEdit
        inputField.onValueChanged.AddListener(OnValueChanged);
        inputField.onEndEdit.AddListener(onEndEdit);
    }
    
    public void OnValueChanged(string value)
    {
        Debug.Log(value);
    }
    public void onEndEdit(string value)
    {
        Debug.Log(value);
    }
    

六、Toggle 切换组件

1. Toggle 基本使用

Toggle 结构:

  • Toggle:Toggle组件
    • Background:Image组件(背景图)
      • Checkmark:Image组件(打勾)
    • Label:Text组件(文本)
private Toggle toggle;
void Start()
{
    toggle = GetComponent<Toggle>();
    toggle.onValueChanged.AddListener(OnValueChanged);
}
private void Update()
{
    if(Input.GetMouseButtonDown(0))
        print(toggle.isOn);
}
private void OnValueChanged(bool bl)
{
    print("bool: " + bl);
}

2. ToggleGroup

  • 只能在 Add Component 中添加

七、案例:注册与登录

1. 案例介绍

注册:玩家输入账号、密码、重复密码、性别等来完成注册,其中需要对密码与重复密码进行一致性校验。

登录:玩家输入账号、密码后和之前输入的账号密码等信息进行匹配。

玩家输入有误时进行弹窗提示。

2. 案例分析

  1. 主面板:主面板上有两个按钮,注册、登录,点击后切换到对应面板。

  2. 注册面板:

    • 玩家输入账号、密码、重复密码、性别后点击注册,对密码与重复密码进行匹配。

      • 输入正确:弹窗“注册成功,请登录“
      • 输入错误-账号与密码没输入:弹窗“请输入账号或密码”
      • 输入错误-密码和重复密码不一致:弹窗“密码与重复密码不一致”
    • 返回按钮:返回主面板

  3. 登录面板:

    • 玩家输入账号、密码后点击登录:

      • 输入正确:弹窗“登录成功”
      • 输入错误-账号或密码没输入:弹窗“请输入账号或密码”
      • 输入错误-数据库里没有对应的用户:弹窗“用户名或密码错误”
    • 返回按钮:返回主面板

3. 案例实现

// 主面板 MainPanel
public class MainPanelTest : MonoBehaviour
{
    // 单例模式
    public static MainPanelTest instance;

    private Button registerButton;
    private Button loginButton;


    private void Awake()
    {
        instance = this;
    }

    void Start()
    {
        registerButton = transform.Find("RegisterButton").GetComponent<Button>();
        loginButton = transform.Find("LoginButton").GetComponent<Button>();

        registerButton.onClick.AddListener(RegisterButtonClick);
        loginButton.onClick.AddListener(LoginButtonClick);
    }

    private void RegisterButtonClick()
    {
        // 打开注册面板
        RegisterPanelTest.instance.Show();
        gameObject.SetActive(false);
    }
    
    private void LoginButtonClick()
    {
        // 打开登录面板
        LoginPanelTest.instance.Show();
        gameObject.SetActive(false);

    }

    public void Show()
    {
        gameObject.SetActive(true);
    }
}
// 注册面板 RegisterPanel
public class RegisterPanelTest : MonoBehaviour
{
    // 单例模式
    public static RegisterPanelTest instance;

    private InputField UserName;
    private InputField Password;
    private InputField RePassword;

    private Toggle IsMale;

    private Button BackButton;
    private Button OKButton;

    private void Awake()
    {
        instance = this;

        UserName = transform.Find("UserName/InputField").GetComponent<InputField>();
        Password = transform.Find("Password/InputField").GetComponent<InputField>();
        RePassword = transform.Find("RePassword/InputField").GetComponent<InputField>();

        IsMale = transform.Find("GenderGroup/Male").GetComponent<Toggle>();

        BackButton = transform.Find("BackButton").GetComponent<Button>();
        OKButton = transform.Find("OKButton").GetComponent<Button>();

        BackButton.onClick.AddListener(BackButtonClick);
        OKButton.onClick.AddListener(OKButtonClick);

        gameObject.SetActive(false);
    }

    private void BackButtonClick()
    {
        // 返回主面板
        MainPanelTest.instance.Show();
        gameObject.SetActive(false);
    }
    private void OKButtonClick()
    {
        // 确定注册
        // 输入检测
        if (string.IsNullOrEmpty(UserName.text) ||
            string.IsNullOrEmpty(Password.text) ||
            string.IsNullOrEmpty(RePassword.text))
        {
            FloatWindowTest.instance.ShowInfo("请输入账号或密码");
        }
        else if(Password.text != RePassword.text)
        {
            FloatWindowTest.instance.ShowInfo("密码和重复密码不一致");
        }
        else
        {
            // 当前用户已存在
            if(GameManager.Instance.GetUserInfo(UserName.text) != null)
            {
                FloatWindowTest.instance.ShowInfo("请勿重复注册");
            }
            else
            {
                UserInfo userInfo = new UserInfo(UserName.text, Password.text, IsMale.isOn);
                // 保存用户信息
                GameManager.Instance.SaveUserInfo(userInfo);
                FloatWindowTest.instance.ShowInfo("注册成功");
            }
        }

    }

    public void Show()
    {
        gameObject.SetActive(true);
        UserName.text = "";
        Password.text = "";
        RePassword.text = "";
    }
}
// 登录面板 LoginPanel
public class LoginPanelTest : MonoBehaviour
{
    // 单例模式
    public static LoginPanelTest instance;

    private InputField UserName;
    private InputField Password;

    private Button BackButton;
    private Button OKButton;

    private void Awake()
    {
        instance = this;

        UserName = transform.Find("UserName/InputField").GetComponent<InputField>();
        Password = transform.Find("Password/InputField").GetComponent<InputField>();
        
        BackButton = transform.Find("BackButton").GetComponent<Button>();
        OKButton = transform.Find("OKButton").GetComponent<Button>();

        BackButton.onClick.AddListener(BackButtonClick);
        OKButton.onClick.AddListener(OKButtonClick);

        gameObject.SetActive(false);
    }

    private void BackButtonClick()
    {
        // 返回主面板
        MainPanelTest.instance.Show();
        gameObject.SetActive(false);
    }

    public void OKButtonClick()
    {
        // 输入检测
        if (string.IsNullOrEmpty(UserName.text) ||
            string.IsNullOrEmpty(Password.text))
        {
            FloatWindowTest.instance.ShowInfo("请输入账号或密码");
        }
        else
        {
            // 登录用户不存在
            UserInfo user = GameManager.Instance.GetUserInfo(UserName.text);
            if (user == null)
            {
                FloatWindowTest.instance.ShowInfo("用户不存在");
            }
            else if (user.Password != Password.text)
            {
                FloatWindowTest.instance.ShowInfo("用户名或密码错误");
            }
            else if (user.Password == Password.text)
            {
                FloatWindowTest.instance.ShowInfo("登录成功");
            }
        }
    }

    public void Show()
    {
        gameObject.SetActive(true);
        UserName.text = "";
        Password.text = "";
    }
}
// 弹窗 FloatWindow
public class FloatWindowTest : MonoBehaviour
{
    // 单例模式
    public static FloatWindowTest instance;

    private Text infoText;
    private Button okButton;

    private void Awake()
    {
        instance = this;
        infoText = transform.Find("Info").GetComponent<Text>();
        okButton = transform.Find("OKButton").GetComponent<Button>();
        // 确认按钮点击事件
        okButton.onClick.AddListener(OKButtonClick);
        gameObject.SetActive(false);
    }

    public void ShowInfo(string info)
    {
        gameObject.SetActive(true);
        infoText.text = info;
    }

    public void OKButtonClick()
    {
        gameObject.SetActive(false);
    }
}
// 存储用户信息(实际中不这么使用)
public class UserInfo
{
    public string UserName;
    public string Password;
    public bool IsMale;

    public UserInfo(string userName, string password, bool isMale)
    {
        UserName = userName;
        Password = password;
        IsMale = isMale;
    }
}

public class GameManager
{
    private static GameManager instance;

    // 注册后的用户信息
    public List<UserInfo> UserInfos = new List<UserInfo>();

    public static GameManager Instance 
    {
        get
        {
            if (instance == null)
                instance = new GameManager();
            return instance;
        }
    }

    public UserInfo GetUserInfo(string userName)
    {
        for(int i = 0; i < UserInfos.Count; i++)
        {
            if(userName == UserInfos[i].UserName)
            {
                return UserInfos[i];
            }
        }

        return null;
    }

    public void SaveUserInfo(UserInfo userInfo)
    {
        UserInfos.Add(userInfo);
    }
}

八、Slider 组件

1. 组件功能

  1. 组件结构

    • Slider:Slider组件
      • Background:Image组件(背景图)
      • Fill Area:填充区域
        • Fill:Image组件(填充物)
      • Handle Slider Area:触摸区域
        • Handle:Image组件(触摸物体实际图片
  2. 组件使用

    private Slider slider;
    void Start()
    {
        slider = GetComponent<Slider>();
    
        slider.onValueChanged.AddListener(SliderOnValueChanged);
    }
    
    void SliderOnValueChanged(float value)
    {
        print(value);
    }
    

九、ScrollBar 组件

1. 组件结构

  • Scrollbar:Scrollbar组件
    • Sliding Area:拖拽区域
      • Handle:Image组件(拖拽物体的实际显示)

与 Slider 组件的比较:

  1. Value 值:Slider组件可以自由设定最大最小值,Scrollbar的Value只能从0到1;
  2. Size 大小:Scrollbar有一个Size属性用来控制滚动条的大小,而Slider组件不需要这个值;
  3. Number Of Steps步数:Scrollbar组件中滚动条可移动的步数(0和1没有意义,2表示滚动条只能移动到两个位置即最左和最右),而Slider组件中没有这个属性。
  4. Slider组件一般用于音量等数值的调节,Scrollbar组件一般用于页面的上下左右拉条。

十、DropDown 组件

1. 下拉组件结构

  • DropDown:DropDown组件
    • Label 显示选项
    • Arrow 下拉箭头
    • Template 模板:一般不显示,用于创建下拉框中的内容,在下拉框中点击选项时是新创建一个游戏物体来显示的。

DropDown组件:

  • Caption Text:下拉选择改动的文本
  • Caption Image:下拉选择改动的图片
  • Options:用于修改选择的内容

十一、Scroll View 组件

1. Mask 遮罩组件

遮罩组件,是一种可以掩盖其他元素的控件。常用于修改其他元素的外观,或限制元素的形状,Scroll View 或是圆头像都有用到遮罩功能。

Mask 遮罩组件一般添加在父物体上,它将子物体限制为父物体的形状。

2. Scroll View 滚动视图组件

  • Scroll View:Scroll Rect 组件
    • Viewport 视界,用到了遮罩组件,用于显示滚动视图中的内容
    • Scrollbar Horizontal:水平滚动条
    • Scrollbar Vertical:垂直滚动条

十二、表格布局组件

1. 表格布局组件概述

类似背包这种想要排列整齐的时候,一般就会使用 Grid Layout Group 表格布局组件。

Grid Layout Group 表格布局组件,会自动管理其下方所在其他 UI 元素的大小、位置等信息。

在层级面板中,点击右键并没有表格布局组件,想要创建表格布局,必须先创建一个空的游戏物体,然后再手动添加 Grid Layout Group.

这样只是创建了表格布局,要实现表格,需要在该游戏物体下方创建其他游戏物体来填充表格,形成类似背包的表格。

2. 主要属性

Grid Layout Group 表格布局组件

  • Padding:整个表格的边距
  • Cell Size :一个单元格的大小。
  • Spacing:单元格间距,x代表横向间距,y代表竖向间距。
  • Start Corner:行/列,第一个成员从什么位置开始
  • Start Axis:横着开始还是竖着开始
  • Child Alignment:子对象对齐,如果布局元素未填满所有可用空间,则应用这个对齐方式。
  • Constraint:将表格限制为固定数量的行或列。

十三、UI布局

1. RectTransform 常用属性

  • Pos:UI 游戏物体的 X、Y、Z坐标,但是Z轴很少使用
  • Width Height:UI 游戏物体的宽和高;
  • Anchors:锚点,可以让游戏物体相对于父物体来定位
  • Pivot:中心点,0~1
  • Rotation:旋转
  • Scale:缩放

2. 锚点定位

锚点用于固定子物体在父物体中的位置,无论父物体怎样改变,子物体相对于父物体的位置都不变。

  1. 相对于父物体什么位置进行定位;
  2. 相对于父物体进行怎样的弹性拉升。

十四、Canvas 画布

1. 画布

UI 的根节点就是 Canvas,意味着 UI 游戏物体都要放在 Canvas 下,才可以得到正确的显示。

一个场景中 Canvas 是可以存在多个的,并且也可以同时生效。

Canvas 游戏物体是由三个组件组成的:

  • Canvas:画布
  • Canvas Scaler:画布比例
  • Graphic Raycaster:射线检测

2. 相关组件

  1. Canvas 组件

    Render Mode:渲染模式

    • Screen Space-Overlay模式:Canvas 将置于屏幕最上层,自动填充屏幕,不会被其他模式的 Canvas 或 2D/3D 物体遮挡。
    • Screen Space-Camera模式:Canvas 将置于相机前方,此时在 Canvas 和相机中间的2D/3D物体将显示在UI上面,利用这一点,可以实现在UI界面展示3D模型。
      • Render Camera:对应的渲染相机,也就是该 Canvas 显示在哪个 Camera 前面
      • Plane Distance:Canvas 与 Camera 的距离
    • World Space模式:Canvas 将作为一个游戏对象显示在3D场景内,用于显示怪物血条等。
      • Event Camera:接收 UI 事件的 Camera.

    Pixel Perfect:完美像素,边缘更加清晰

    Sort Order:画布排序,场景中具备多个 Canvas 时才有意义。

  2. Canva Scaler 组件

    Canvas Scaler 也是屏幕适配的主要方式,一般通过该组件就可以完成适配。

    UI Scale Mode 缩放模式:

    • Constant Pixel Size 模式:固定像素大小,不论屏幕分辨率尺寸大小如何变化,像素保持原有大小不变。
      • Scale Factor:缩放倍数
    • Scale With Screen Size模式:屏幕自适应常用方式
      • Reference Resolution:参考分辨率,进行屏幕适配,自动缩放 UI 大小时,将以此作为参考。

十五、UI事件

1. 事件介绍

在 Button 中,按钮的点击就是事件,但是在实际项目中,UI 并不是只有点击这一种事件,比如鼠标悬浮、鼠标按下、鼠标弹起、鼠标拖拽等等,而且 UI 本身可能也并不是一个按钮,这些事件我们通过给游戏物体创建脚本来实现。

2. 事件接口

脚本中添加 using UnityEngine.EventSystems 事件相关命名空间。

脚本需要继承以下事件接口,并实现对应方法即可。

鼠标事件接口:

  • IPointerClickHandler:鼠标点击
  • IPointerDownHandler:鼠标按下
  • IPointerUpHandler:鼠标弹起
  • IPointerEnterHandler:鼠标进入
  • IPointerExitHandler:鼠标退出

拖拽事件接口

  • IBeginDragHandler:开始拖拽
  • IDragHandler:拖拽中
  • IEndDragHandler:停止拖拽

猜你喜欢

转载自blog.csdn.net/Dukenone/article/details/126904592