Unity3D-UGUI应用篇使用UGUI实现层级菜单

Posted 恬静的小魔龙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity3D-UGUI应用篇使用UGUI实现层级菜单相关的知识,希望对你有一定的参考价值。

推荐阅读

大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。

一、前言

前段时间分享了UGUI的每个组件的属性和使用方法,比如Text、Button、Image、Toggle、InputField、ScrollView等等。

接着分享了UGUI的一些原理,比如说UGUI的渲染模式、UGUI的缩放计算、UGUI的描点定位、UGUI的自动布局等等。

相信大家看完后会对UGUI有一个比较全面的认识了。

下面,就继续分享UGUI的UI组件进行应用的实例。

这是本系列文章的第三篇:
【Unity3D-UGUI应用篇】(一)使用Text实现进度等待动画
【Unity3D-UGUI应用篇】(二)使用Image实现进度条动画
【Unity3D-UGUI应用篇】(三)使用UGUI实现层级菜单
【Unity3D-UGUI应用篇】(四)使用UGUI弹窗显示模型及弹窗模型交互
【Unity3D-UGUI应用篇】(五)使用Button完成鼠标移动到UI上面显示文字功能
【Unity3D-UGUI应用篇】(六)屏幕自适应(多分配率适配)
【Unity3D-UGUI应用篇】(七)UGUI实现窗口的自由拖拽
【Unity3D-UGUI应用篇】(八)Image实现画线、画三角形、画正方形、画圆

二、介绍及效果图

层级菜单在Unity中用到的并不多,主要是做分类的时候用的比较多,今天就给大家分享几个层级代码,扩充一下,写成插件也是不错的。

首先看一下效果吧。

第一种效果:

第二种效果:

第三种效果:


第四种效果:

第五种效果:

源文件:

源文件下载

Github下载地址:
LayerMenu.unitypackage

三、实现

第一种实现效果


实现原理:

这个是用系统自带的UGUI Scroll View组件,脚本控制创建父物体,父物体身上挂载有初始化子物体的脚本

优缺点:
优点是实现简单,不需要多与的插件,代码量也不大,控制比较方便
缺点是只能实现两个层级的显示

实现过程:

1、新建一个Scrpll View

2、制作预制体

界面就这么设计就行:

名字改一下
Content:父节点容器
ParentMenu:父节点
TextParent:父节点文本
ChildMenu:子节点容器
item:子节点
TextChild:子节点文本

然后将父节点改名字叫parentMenu,做成预制体:

预制体放到Resources文件夹中:

将子物体也制作成预制体:

3、编写脚本ParentMenu.cs
这个脚本主要是作用是创建子物体:

using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class ParentMenu : MonoBehaviour
{
    private GameObject childMenu;//子菜单的parent
    private RectTransform[] childs;//所有子菜单的rect
    private RectTransform itemRect;//子菜单的prefab
    private Vector3 offset;//单个子菜单的高度
    private int count;//子菜单的个数
    public bool isOpening { get; private set; }//父菜单是否展开
    public bool isCanClick { get; set; }//父菜单是否可以点击

    public void Init(RectTransform rect, int count)
    {
        //找到子节点
        childMenu = transform.Find("childMenu").gameObject;
        itemRect = rect;
        this.count = count;
        childs = new RectTransform[this.count];
        offset = new Vector3(0, itemRect.rect.height);
        for (int i = 0; i < this.count; i++)
        {
            childs[i] = Instantiate(itemRect, childMenu.transform);
        }
        childMenu.gameObject.SetActive(false);
        isOpening = false;
        isCanClick = true;
        GetComponent<Button>().onClick.AddListener(OnButtonClick);
    }

    void OnButtonClick()
    {
        if (!isCanClick) return;
        if (!isOpening)
            StartCoroutine(ShowChildMenu());
        else
            StartCoroutine(HideChildMenu());
    }

    IEnumerator ShowChildMenu()
    {
        childMenu.gameObject.SetActive(true);
        for (int i = 0; i < count; i++)
        {
            childs[i].localPosition -= i * offset;
            yield return new WaitForSeconds(0.1f);
        }
        isCanClick = true;
        isOpening = true;
    }

    IEnumerator HideChildMenu()
    {
        for (int i = count - 1; i >= 0; i--)
        {
            childs[i].localPosition += i * offset;
            yield return new WaitForSeconds(0.1f);
        }
        childMenu.gameObject.SetActive(false);
        isCanClick = true;
        isOpening = false;
    }
}

4、编写脚本FoldableMenu.cs
这个脚本主要是为了创建父物体,以及控制折叠菜单

using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class FoldableMenu : MonoBehaviour
{
    private RectTransform content;//父物体的parent
    private TextAsset textAsset;//所有菜单信息
    private RectTransform parentRect;//父菜单的prefab
    private RectTransform[] parentArr;//所有父菜单的数组
    private RectTransform childRect;//子菜单的prefab
    private Vector3 parentOffset;//单个父菜单的高度
    private Vector3 childOffset;//单个父菜单的高度
    private int[] cntArr;//所有父菜单拥有的子菜单个数

    void Awake()
    {
        Init();
    }

    void Init()
    {
        //获取到父节点
        content = transform.Find("Viewport/Content").GetComponent<RectTransform>();
        //获取到menuinfo里面的信息  3 3 4 4 5 5
        textAsset = Resources.Load<TextAsset>("menuInfo");
        //获取到父物体 设置父物体的高度
        parentRect = Resources.Load<RectTransform>("parentMenu");
        parentOffset = new Vector3(0, parentRect.rect.height);
        //获取到子物体 设置子物体的高度
        childRect = Resources.Load<RectTransform>("item");
        childOffset = new Vector3(0, childRect.rect.height);
        //分割字符串 info = 3 3 4 4 5 5
        var info = textAsset.text.Split(',');//获取子菜单个数信息
        //数组的长度
        cntArr = new int[info.Length];
        //父菜单的数组
        parentArr = new RectTransform[info.Length];
        //初始化content高度 宽度不变 高度是所有父物体的总长
        content.sizeDelta = new Vector2(content.rect.width, parentArr.Length * parentRect.rect.height);
        //i = 6
        for (int i = 0; i < cntArr.Length; i++)
        {
            //创建服务器
            parentArr[i] = Instantiate(parentRect, content.transform);
            //坐标为 上一个父物体的宽度
            parentArr[i].localPosition -= i * parentOffset;
            //赋值
            cntArr[i] = int.Parse(info[i]);
            //父物体上面加载子物体 子物体数量为3 3 4 4 5 5
            parentArr[i].GetComponent<ParentMenu>().Init(childRect, cntArr[i]);
            int j = i;
            //父物体上面的button绑定事件
            parentArr[i].GetComponent<Button>().onClick.AddListener(() => { OnButtonClick(j); });
        }
    }

    void OnButtonClick(int i)
    {
        //如果iscanclick为false则return  因为已经点击过了 不能再点击了 除非升起来的时候将isCanClick改为true
        if (!parentArr[i].GetComponent<ParentMenu>().isCanClick)
            return;
        parentArr[i].GetComponent<ParentMenu>().isCanClick = false;
        //isopening 为true 执行 menuup 为flase执行menuDown
        if (!parentArr[i].GetComponent<ParentMenu>().isOpening)
            StartCoroutine(MenuDown(i));
        else
            StartCoroutine(MenuUp(i));
    }

    IEnumerator MenuDown(int index)
    {       
        for (int i = 0; i < cntArr[index]; i++)
        {
            //更新content高度
            content.sizeDelta = new Vector2(content.rect.width,
                content.rect.height + childOffset.y);
            for (int j = index + 1; j < parentArr.Length; j++)
            {
                parentArr[j].localPosition -= childOffset;
            }
            yield return new WaitForSeconds(0.1f);
        }     
    }

    IEnumerator MenuUp(int index)
    {
        for (int i = 0; i < cntArr[index]; i++)
        {
            //更新content高度
            content.sizeDelta = new Vector2(content.rect.width,
                content.rect.height - childOffset.y);
            for (int j = index + 1; j < parentArr.Length; j++)
            {
                parentArr[j].localPosition += childOffset;
            }
            yield return new WaitForSeconds(0.1f);
        }
    }
}

将这个脚本挂载到Scroll View上面:

运行,搞定!

第二种实现效果


实现原理:
这个也是用UGUI做的,不一样的是不需要容器组件,主要是寻找父节点,然后保存父节点的信息,下一个节点以父节点为目标进行偏移,或者以父节点为目标做子节点

优缺点:
优点是代码清晰,可扩展性强,不需要设计UGUI
缺点结构比较简单,没有实现多层级的功能

实现过程:
1、创建预制体

结构比较简单,两个Image,箭头的图片带Button组件(可以下拉和合并)
然后将预制体放到Resources文件夹中:

2、编写PublicData.cs脚本
一个数据类

using UnityEngine;

public class PublicData
{
    public static Transform parent;
    public static Vector2 currentPosition;
}

3、编写脚本Options_Triangle.cs
初始化子节点的函数

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Options_Triangle : MonoBehaviour
{
    public Button triangle;
    public Image content;
    public List<Transform> children = new List<Transform>();
    void Start()
    {
        triangle.onClick.AddListener(() =>
        {
            if (triangle.transform.eulerAngles.z == 270)
            {
                triangle.transform.eulerAngles = Vector3.zero;
                for (int i = 0; i < children.Count; i++)
                {
                    children[i].gameObject.SetActive(false);
                }
            }
            else
            {
                triangle.transform.eulerAngles = new Vector3(0, 0, -90);
                for (int i = 0; i < children.Count; i++)
                {
                    children[i].gameObject.SetActive(true);
                }
            }
        });

    }
}

4、编写ClickEvent.cs
这是一个层级菜单的编辑功能的脚本

using UnityEngine;
using UnityEngine.UI;

public class ClickEvent : MonoBehaviour
{
    public Button add_options_t;
    public Button add_options;
    public Transform canvasParent;

    void Start()
    {
        add_options_t.onClick.AddListener(() =>
        {
            if (PublicData.parent == null)
            {
                PublicData.parent = Instantiate(Resources.Load<Transform>("Options_triangle"));
                PublicData.parent.transform.SetParent(canvasParent, false);
                PublicData.currentPosition = new Vector2(PublicData.parent.position.x, PublicData.parent.position.y);
            }
            else
            {
                Transform option = Instantiate(Resources.Load<Transform>("Options_triangle"));
                option.transform.SetParent(PublicData.parent, false);
                option.transform.position = new Vector3(PublicData.currentPosition.x + 30, PublicData.currentPosition.y - 32, 0);
                PublicData.parent.GetComponent<Options_Triangle>().triangle.transform.eulerAngles = new Vector3(0, 0, -90);
                PublicData.parent.GetComponent<Options_Triangle>().children.Add(option);
                PublicData.parent = option;
                PublicData.currentPosition = new Vector2(PublicData.parent.position.x, PublicData.parent.position.y);
            }

        });
        add_options.onClick.AddListener(() =>
        {
            if (PublicData.parent == null)
            {
                return;
            }
            PublicData.parent.GetComponent<Options_Triangle>().triangle.transform.eulerAngles = new Vector3(0, 0, -90);
            Transform content = Instantiate(Resources.Load<Transform>("Options"));
            content.transform.SetParent(PublicData.parent, false);
            content.transform.position = new Vector3(PublicData.currentPosition.x + 16, PublicData.currentPosition.y - 32, 0);
            PublicData.parent.GetComponent以上是关于Unity3D-UGUI应用篇使用UGUI实现层级菜单的主要内容,如果未能解决你的问题,请参考以下文章

Unity3D-UGUI应用篇UGUI实现窗口的自由拖拽

Unity3D-UGUI应用篇使用Image实现进度条动画

Unity3D-UGUI应用篇使用Text实现进度等待动画

Unity3D-UGUI应用篇Image实现画线画三角形画正方形画圆

Unity3D-UGUI应用篇使用UGUI弹窗显示模型及弹窗模型交互

Unity3D-UGUI应用篇屏幕自适应(多分配率适配)