目录

一、前言

二、实现

2.1、创建ScrollView

2.2、制作层级预设体BaseLevelPartObj

2.3、设置该预设体的初始化处理方法

2.4、读取Hierarchy的内容并创建UI项

2.5、折叠与展开

2.6、完整的两个代码

三、总结


一、前言

首先,介绍下开发环境,我使用的是Unity2019.4.26。文章末尾有工程文件,如果不想看过程的可以直接拉到最后。效果图如图1所示:该工具主要的亮点是可以自动实现多级菜单展示,目前测试的是三级,如果想实现更多层级只需要在代码里增加多级枚举和处理枚举的逻辑代码,基本上实现的思路是一样的。这个效果有点类似于Unity的Hierarchy视图中的折叠效果,如图2所示,实现这个效果最核心的是要理解Unity的UGUI的ScrollRect控件,这个控件的功能远比我想象的要大,另外还需要了解UGUI的

图1

图2

锚点的概念,比如,如图1所示的效果,如果某个菜单项下面有子菜单项目,点击该菜单项的展开按钮。在计算展开的高度的同时是需要对该项进行移动的,移动的距离就和子项展开后的总高度直接相关。如果该项的子项目能继续展开,除了要处理子项目的展开逻辑,正如前面描述的那样,展开后得到该项目的孙子项目;还需要处理该项目的孙子项目展开后对该项目的影响,孙子项目展开后的高度也是该项目的移动距离。类似于这样层层相关,环环相扣,这也是这个实现逻辑里最难处理的部分。做这样一个内容列表,首先当然要有内容了,我这里直接在Unity的Hierarchy里创建了如图2所示的内容菜单项,然后,去读取该内容项,再创建属于我的UI内容项。读取内容的时候就给每个项目做好层级划分,然后实例化预设体,该预设体即UI项的模板。再该项目中我只用了一个模板来生成不同的层级项。好了,废话BB完了,直接开撸吧。

二、实现

2.1、创建ScrollView

创建一个ScrollView,只保留它有垂直的滚动条;然后在Cotent中的设置如图3所示:增加ContentSizeFitter和VerticalLayoutGroup组件,并设置VerticalLayoutGroup的Padding。

图3

2.2、制作层级预设体BaseLevelPartObj

该预设体中最重要的就是要有和2.1中的ScollView一样的Content子物体,其设置如图4所示:设置其Padding为向左偏移,这样就能保证其子物体会在其下向右偏移的位置。

图4

2.3、设置该预设体的初始化处理方法

这一步要首先将该项的初始位置保存起来,这个初始位置就可以作为最后折叠时该项目的位置,无论该项目的子孙项展开到了什么程度。然后是设置其按钮的显示状态。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;/// <summary>
/// 层级菜单项
/// </summary>
public class BaseLevelPartObj : MonoBehaviour
{public static BaseLevelPartObj CurSelectedBLPO;public Text TextTitle;//标题public Image ImageBtnBg;//背景按钮public Image[] ImageBtnFold;//折叠按钮0时折叠,1是展开public RectTransform M_MySelfRectT { get; private set; }public ContentSizeFitter M_SubObjParentObj { get; private set; }//下级菜单的父物体public BaseLevelPartObj M_MyParetnObj { get; private set; }//我的父物体public PartsLevel M_Level { get; private set; }//我的层级public float M_HeightAllSubItems { get; private set; } = 0;//整个子列表的加起来的高度,包括所有的子孙项public bool M_IsBeSelected { get;set; }private Vector2 PriFloldSizeDelta;//折叠时候的位置public bool M_IsFold { get; private set; } = true;//是否折叠private static float TimeLastClick;//上次点中的时间    private void SetLevelShow(){switch (M_Level){case PartsLevel.First://如果是一级菜单则其父物体为最上面的内容管理器transform.SetParent(UIManger.M_Instance.ParentPartFirstLevel.transform);break;case PartsLevel.Second:break;case PartsLevel.Third:break;}if (null != M_MyParetnObj){transform.SetParent(M_MyParetnObj.M_SubObjParentObj.transform);//  M_MyParetnObj.M_SubObjParentObj.gameObject.SetActive(false); 一开始除了一级菜单都不显示}}
public virtual void Init(string partName,BaseLevelPartObj parent, PartsLevel level){ImageBtnBg.color = UIManger.M_Instance.ColorBtnSelf[0];//初始化背景图M_SubObjParentObj = GetComponentInChildren<ContentSizeFitter>(true);M_MySelfRectT = GetComponent<RectTransform>();PriFloldSizeDelta = M_MySelfRectT.sizeDelta;//保存初始位置M_MyParetnObj = parent;M_Level = level;TextTitle.text = partName;SetLevelShow();ImageBtnFold[0].gameObject.SetActive(false);ImageBtnFold[1].gameObject.SetActive(false);if (null != M_MyParetnObj){M_MyParetnObj.M_SubObjParentObj.gameObject.SetActive(false); 一开始除了一级菜单都不显示M_MyParetnObj.AddAllSubItemHeight(M_MySelfRectT.rect.height);//增加当前物体的父物体子列表高度}}
}

2.4、读取Hierarchy的内容并创建UI项

读取的时候采用了.GetComponentsInChildren<Transform>方法,将目标物体的所有子物体都读取进来,当然也包括它自己,

  Transform[] tempTransObj = ReadObj.GetComponentsInChildren<Transform>(true);

然后采用一次遍历的方式创建所有的实体,并且区分开每个读取项的层级。这里有个非常重要的知识点需要掌握,通过GetComponentsInChildren<Transform>读取出来的数组其顺序时有规律的,前面的项是穷尽读取完每个层级之后才会去读下个层级的项。例如,如图5所示,读取的tempTransObj数组的前面几项分别是0:GameObjects、1:一级菜单1、2:二级菜单101、3:三级菜单10101、.....18:二级菜单101 (1)。也即,一定是在一级菜单1中穷尽完“二级菜单101”的所有子项后,才会进入“二级菜单101 (1)”的读取,完美的实现了从上之下的索引顺序。

图5

鉴于此规律,我们就可以采用一次遍历的方式来实现每个项的初始化,也即每个子项目在创建的时候它的父项是肯定已经出现并创建的。基于这个规律我们在遍历的时候就可以存储那些临时的一级、二级、菜单项,等到下个项被遍历的时候先判断其父物体是不是已经存在了,然后直接将其父物体设置成可以展开的姿态。当然,如果存储的一级、二级项,如果一直没有子物体就会保持默认的没有折叠或展开的按钮。

在遍历之前我们还需要一个判断当前读取的Transform是在第几个层级的方法,代码如下:采用递归读取的方式穷尽其父物体,然后得出其所在层级的数字,第几级就为数字几。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class UIManger : MonoBehaviour
{public BaseLevelPartObj PrefabsPartObj;public Transform ReadObj;public Color[] ColorBtnSelf;public static UIManger M_Instance{get{if (null == instance){instance = FindObjectOfType<UIManger>();}return instance;}}public ContentSizeFitter ParentPartFirstLevel;//一级菜单的父物体private List<BaseLevelPartObj> ListAllPartObjs = new List<BaseLevelPartObj>();private static UIManger instance;// Start is called before the first frame updatevoid Start(){//先缓存一级和二级的菜单项Dictionary<string, BaseLevelPartObj> tempDicFirstLevelObj = new Dictionary<string, BaseLevelPartObj>();Dictionary<string, BaseLevelPartObj> tempDicSecLevelObj = new Dictionary<string, BaseLevelPartObj>();Transform[] tempTransObj = ReadObj.GetComponentsInChildren<Transform>(true);//采用一次遍历创建完所有的层级菜单项foreach (Transform item in tempTransObj){BaseLevelPartObj tempBLPObj = Instantiate(PrefabsPartObj);PartsLevel tempLevel = (PartsLevel)(GetLevel(item) - 1);//  Debug.Log(item.name + ":" + GetLevel(item) + ":p:"+(item.parent==null?"null":item.parent.name));switch (tempLevel){case PartsLevel.None:break;case PartsLevel.First:tempBLPObj.Init(item.name, null, tempLevel);tempDicFirstLevelObj.Add(item.name, tempBLPObj);break;case PartsLevel.Second://如果临时一级菜单中有包含当前物体的父物体if (tempDicFirstLevelObj.ContainsKey(item.parent.name)){BaseLevelPartObj tempParentObj = tempDicFirstLevelObj[item.parent.name];tempParentObj.HasSubObj();tempBLPObj.Init(item.name, tempParentObj, tempLevel);tempDicSecLevelObj.Add(item.name, tempBLPObj);}break;case PartsLevel.Third://如果临时的二级菜单中有包含当前物体的父物体if (tempDicSecLevelObj.ContainsKey(item.parent.name)){BaseLevelPartObj tempParentObj = tempDicSecLevelObj[item.parent.name];tempParentObj.HasSubObj();tempBLPObj.Init(item.name, tempParentObj, tempLevel);}break;}}}// Update is called once per framevoid Update(){}//获取当前的层级public int GetLevel(Transform transfor){int tempLevel = 1;if(transfor.parent!=null){return tempLevel+ GetLevel(transfor.parent);}return tempLevel;}
}
public enum PartsLevel
{None = 0,First = 1,Second,Third,
}

读取层级之后,需要转换成枚举,因为,读取的Transform数组的第一个不作为层级,因此减去1就可以等到所在的层级了。

2.5、折叠与展开

1)自身展开和折叠逻辑

完成了上述几个步骤之后,我们就可以生成包含所有层级的UI项了,接下来就是最重要的步骤,怎么折叠和展开。在BaseLevelPartObj的初始化函数里就已经使用了增加子项目高度的方法,其实现内容为:就一句话

    public void AddAllSubItemHeight(float height){M_HeightAllSubItems += height;}

其调用的代码为:在其父物体不为空的时候,给其父物体增加子项目高度,注意这里还只是处理了一级子项目的高度,还未处理二级子项目展开时候的高度,这个后面再讲。

        if (null != M_MyParetnObj){M_MyParetnObj.M_SubObjParentObj.gameObject.SetActive(false); 一开始除了一级菜单都不显示M_MyParetnObj.AddAllSubItemHeight(M_MySelfRectT.rect.height);//增加当前物体的父物体子列表高度}

然后是处理展开按钮的逻辑代码:这里有个很有意思的处理过程,也即将存储该项子项目的ContentSizeFitter这个组件需要先失活,然后再位置都变动好了之后再激活,这么操作的目的是为了刷新。我也是很佩服我自己居然找出了这样的奇葩刷新方式,也是

    /// <summary>/// 是否折叠子列表/// </summary>/// <param name="isFold"></param>public void Btn_FoldSubList(bool isFold){M_IsFold = isFold;ContentSizeFitter tempSubItemsParent = M_MyParetnObj == null ? UIManger.M_Instance.ParentPartFirstLevel : M_MyParetnObj.M_SubObjParentObj;tempSubItemsParent.enabled = false;M_SubObjParentObj.gameObject.SetActive(!isFold);SetFoldImage(isFold);float tempSign = isFold ? -1 : 1;if (isFold){M_MySelfRectT.sizeDelta = PriFloldSizeDelta;}else{M_MySelfRectT.sizeDelta = PriFloldSizeDelta + new Vector2(0, M_HeightAllSubItems);}tempSubItemsParent.enabled = true;}

基于一次偶然的操作才发现,不知道这算不算Unity的问题还是我没找到更好的处理方法的问题。为了说明这个问题,我做一个简单的测试你就会明白了,我就先将处理这个ContentSizeFitter组件失活、激活的代码注释掉,然后运行我的工程文件,你就发现效果是这样的,如图6所示,在展开一级菜单的时候,明显发生了错位的现象,然后我手动的失活、激活一级菜单的父物体上的ContentSizeFitter组件后就会让一级菜单回归到展开后应该的位置处。这个奇葩刷新过程,真的是让人很无语,我尝试过使用

图6
        Canvas.ForceUpdateCanvases();

方法也不行,只好先这么着吧,处理的时候会相对来说麻烦一点。

2)子项展开和折叠逻辑

上面处理了自身展开后的逻辑,但是,子项目展开后的效果是这样的,如图7所示:子项目展开后显示了一堆的孙子项目,孙子项目的高度直接超过了该项当前的高度,所以就溢出到了下面的层级中,这个肯定不是我们想要的结果,为此需要专门处理

图7

子菜单展开后该项目的位置和高度关系以及ContentSizeFitter的失活和激活带来的刷新。新增子项目展开带来的高度处理逻辑,然后在折叠展开按钮的方法中添加调用方法。记住一定要处理展开和折叠带来的高度差数值符号的变换,展开是正的,折叠是负号

    /// <summary>/// 子菜单展开带来的高度增加/// </summary>public void SubItemFoldAddHeight(float height){AddAllSubItemHeight(height);ContentSizeFitter tempSubItemsParent = M_MyParetnObj == null ? UIManger.M_Instance.ParentPartFirstLevel : M_MyParetnObj.M_SubObjParentObj;//如果是一级列表则是最上面的那个父物体tempSubItemsParent.enabled = false;M_MySelfRectT.sizeDelta =PriFloldSizeDelta+ new Vector2(0, M_HeightAllSubItems);tempSubItemsParent.enabled = true;}/// <summary>/// 是否折叠子列表/// </summary>/// <param name="isFold"></param>public void Btn_FoldSubList(bool isFold){....//给父物体也增加高度并且刷新if (null != M_MyParetnObj){M_MyParetnObj.SubItemFoldAddHeight(tempSign*M_HeightAllSubItems);}}

2.6、完整的两个代码

层级预设体再加上处理鼠标悬浮在其上的背景变化和按下时候的颜色变化逻辑后完整的代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;/// <summary>
/// 层级菜单项
/// </summary>
public class BaseLevelPartObj : MonoBehaviour
{public static BaseLevelPartObj CurSelectedBLPO;public Text TextTitle;//标题public Image ImageBtnBg;//背景按钮public Image[] ImageBtnFold;//折叠按钮0时折叠,1是展开public RectTransform M_MySelfRectT { get; private set; }public ContentSizeFitter M_SubObjParentObj { get; private set; }//下级菜单的父物体public BaseLevelPartObj M_MyParetnObj { get; private set; }//我的父物体public PartsLevel M_Level { get; private set; }//我的层级public float M_HeightAllSubItems { get; private set; } = 0;//整个子列表的加起来的高度,包括所有的子孙项public bool M_IsBeSelected { get;set; }private Vector2 PriFloldSizeDelta;//折叠时候的位置public bool M_IsFold { get; private set; } = true;//是否折叠private static float TimeLastClick;//上次点中的时间// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){}private void SetFoldImage(bool isFold){if (M_Level == PartsLevel.Third) return;ImageBtnFold[0].gameObject.SetActive(!isFold);ImageBtnFold[1].gameObject.SetActive(isFold);}public void AddAllSubItemHeight(float height){M_HeightAllSubItems += height;}/// <summary>/// 是否折叠子列表/// </summary>/// <param name="isFold"></param>public void Btn_FoldSubList(bool isFold){M_IsFold = isFold;ContentSizeFitter tempSubItemsParent = M_MyParetnObj == null ? UIManger.M_Instance.ParentPartFirstLevel : M_MyParetnObj.M_SubObjParentObj;tempSubItemsParent.enabled = false;M_SubObjParentObj.gameObject.SetActive(!isFold);SetFoldImage(isFold);float tempSign = isFold ? -1 : 1;if (isFold){M_MySelfRectT.sizeDelta = PriFloldSizeDelta;}else{M_MySelfRectT.sizeDelta = PriFloldSizeDelta + new Vector2(0, M_HeightAllSubItems);}tempSubItemsParent.enabled = true;//给父物体也增加高度并且刷新if (null != M_MyParetnObj){M_MyParetnObj.SubItemFoldAddHeight(tempSign*M_HeightAllSubItems);}}//搜索项选中的时候自动展开其所有父类public bool SearcherSelectedUnFlod(){bool isOver = false;if (null != M_MyParetnObj){if (M_MyParetnObj.M_IsFold) M_MyParetnObj.Btn_FoldSubList(!M_MyParetnObj.M_IsFold);return M_MyParetnObj.SearcherSelectedUnFlod();}if (M_IsFold) Btn_FoldSubList(!M_IsFold);return isOver;}/// <summary>/// 子菜单展开带来的高度增加/// </summary>public void SubItemFoldAddHeight(float height){AddAllSubItemHeight(height);ContentSizeFitter tempSubItemsParent = M_MyParetnObj == null ? UIManger.M_Instance.ParentPartFirstLevel : M_MyParetnObj.M_SubObjParentObj;//如果是一级列表则是最上面的那个父物体tempSubItemsParent.enabled = false;M_MySelfRectT.sizeDelta =PriFloldSizeDelta+ new Vector2(0, M_HeightAllSubItems);tempSubItemsParent.enabled = true;}public virtual void Btn_PointEnter(){if (M_IsBeSelected) return;ImageBtnBg.color = UIManger.M_Instance.ColorBtnSelf[1];}public virtual void Btn_PointExit(){if (M_IsBeSelected) return;ImageBtnBg.color = UIManger.M_Instance.ColorBtnSelf[0];}public virtual void Btn_PointDown(){if(null!= CurSelectedBLPO&& CurSelectedBLPO!=this){CurSelectedBLPO.M_IsBeSelected = false;CurSelectedBLPO.ImageBtnBg.color= UIManger.M_Instance.ColorBtnSelf[0];}CurSelectedBLPO = this;//   UIManger.M_Instance.SelectedBLPobj(this);M_IsBeSelected = true;ImageBtnBg.color = UIManger.M_Instance.ColorBtnSelf[2];}//搜索被选中public void SearcherBeSelected(){M_IsBeSelected = true;ImageBtnBg.color = UIManger.M_Instance.ColorBtnSelf[3];//if (null != M_MyParetnObj)//{//    M_MyParetnObj.Btn_FoldSubList(false);//}}public virtual void Btn_PointClick(){if(M_IsBeSelected){if(Time.time-TimeLastClick<0.2f){//   Debug.Log("点击了一次!");}TimeLastClick = Time.time;ImageBtnBg.color = UIManger.M_Instance.ColorBtnSelf[3];}}//有子物体了,设置其折叠按钮public void HasSubObj(){SetFoldImage(true); }/// <summary>/// 是否是搜索的时候显示/// </summary>/// <param name="isSercher"></param>public void SearcherShow(bool isSercher){gameObject.SetActive(true);if(isSercher){ImageBtnFold[0].gameObject.SetActive(false);ImageBtnFold[1].gameObject.SetActive(false);}else{SetLevelShow();}}private void SetLevelShow(){switch (M_Level){case PartsLevel.First://如果是一级菜单则其父物体为最上面的内容管理器transform.SetParent(UIManger.M_Instance.ParentPartFirstLevel.transform);break;case PartsLevel.Second:break;case PartsLevel.Third:break;}if (null != M_MyParetnObj){transform.SetParent(M_MyParetnObj.M_SubObjParentObj.transform);//  M_MyParetnObj.M_SubObjParentObj.gameObject.SetActive(false); 一开始除了一级菜单都不显示}}public virtual void Init(string partName,BaseLevelPartObj parent, PartsLevel level){ImageBtnBg.color = UIManger.M_Instance.ColorBtnSelf[0];//初始化背景图M_SubObjParentObj = GetComponentInChildren<ContentSizeFitter>(true);M_MySelfRectT = GetComponent<RectTransform>();PriFloldSizeDelta = M_MySelfRectT.sizeDelta;M_MyParetnObj = parent;M_Level = level;TextTitle.text = partName;SetLevelShow();ImageBtnFold[0].gameObject.SetActive(false);ImageBtnFold[1].gameObject.SetActive(false);if (null != M_MyParetnObj){M_MyParetnObj.M_SubObjParentObj.gameObject.SetActive(false); 一开始除了一级菜单都不显示M_MyParetnObj.AddAllSubItemHeight(M_MySelfRectT.rect.height);//增加当前物体的父物体子列表高度}}
}

读取和创建UI项的代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class UIManger : MonoBehaviour
{public BaseLevelPartObj PrefabsPartObj;public Transform ReadObj;public Color[] ColorBtnSelf;public static UIManger M_Instance{get{if (null == instance){instance = FindObjectOfType<UIManger>();}return instance;}}public ContentSizeFitter ParentPartFirstLevel;//一级菜单的父物体private List<BaseLevelPartObj> ListAllPartObjs = new List<BaseLevelPartObj>();private static UIManger instance;// Start is called before the first frame updatevoid Start(){//先缓存一级和二级的菜单项Dictionary<string, BaseLevelPartObj> tempDicFirstLevelObj = new Dictionary<string, BaseLevelPartObj>();Dictionary<string, BaseLevelPartObj> tempDicSecLevelObj = new Dictionary<string, BaseLevelPartObj>();Transform[] tempTransObj = ReadObj.GetComponentsInChildren<Transform>(true);//采用一次遍历创建完所有的层级菜单项foreach (Transform item in tempTransObj){BaseLevelPartObj tempBLPObj = Instantiate(PrefabsPartObj);PartsLevel tempLevel = (PartsLevel)(GetLevel(item) - 1);//  Debug.Log(item.name + ":" + GetLevel(item) + ":p:"+(item.parent==null?"null":item.parent.name));switch (tempLevel){case PartsLevel.None:break;case PartsLevel.First:tempBLPObj.Init(item.name, null, tempLevel);tempDicFirstLevelObj.Add(item.name, tempBLPObj);break;case PartsLevel.Second://如果临时一级菜单中有包含当前物体的父物体if (tempDicFirstLevelObj.ContainsKey(item.parent.name)){BaseLevelPartObj tempParentObj = tempDicFirstLevelObj[item.parent.name];tempParentObj.HasSubObj();tempBLPObj.Init(item.name, tempParentObj, tempLevel);tempDicSecLevelObj.Add(item.name, tempBLPObj);}break;case PartsLevel.Third://如果临时的二级菜单中有包含当前物体的父物体if (tempDicSecLevelObj.ContainsKey(item.parent.name)){BaseLevelPartObj tempParentObj = tempDicSecLevelObj[item.parent.name];tempParentObj.HasSubObj();tempBLPObj.Init(item.name, tempParentObj, tempLevel);}break;}}}// Update is called once per framevoid Update(){}//获取当前的层级public int GetLevel(Transform transfor){int tempLevel = 1;if(transfor.parent!=null){return tempLevel+ GetLevel(transfor.parent);}return tempLevel;}
}
public enum PartsLevel
{None = 0,First = 1,Second,Third,
}

三、总结

3.1、各个层级展开后对上面层级的影响的处理要特别注意,因为没处理好就会造成展开不正常、展开后折叠时位置不对一级下下层级展开后位置不对的情况;

3.2、下载地址工程文件

Unity实用小工具或脚本——可折叠伸缩的多级(至少三级)内容列表(类似于Unity的Hierarchy视图中的折叠效果)相关推荐

  1. Unity实用小工具或脚本——可折叠伸缩的多级列表二(带搜索功能)

    一.前言 继上篇可折叠伸缩的多级(至少三级)内容列表实现了类似于Unity的Unity的Hierarchy视图中的折叠效果之后,我寻思的丰富一下该工具的功能,不保证说完全的复制Unity的Hierar ...

  2. Unity实用小工具或脚本—以对象方式访问MySql数据库

    一.前言         以对象方式处理MySql数据库顾名思义就是可以将每个数据库表作为一个类,没一条数据作为一个对象来进行操作,大致思路和我上一篇文章类似,这里不再赘述.文章后有资源下载地址,所使 ...

  3. Unity实用小工具或脚本—3D炫酷UI篇(一)

    一.前言 最近做AR和VR的项目,经常需要用到3D的UI,特意将最近自己捣鼓的这个UI的东西写下来.效果如图所示:主要的动画和素材也是借鉴了 第三方的插件"HoloUIExample&quo ...

  4. Unity实用小工具或脚本——录屏工具

    一.前言 本文要讲的录屏不是使用Unity自带的那个截屏方法,因为unity自带的都只能截取unity程序本身显示的画面内容,至于unity程序之外的内容,如电脑桌面上的其他的程序内容是无法录屏的.本 ...

  5. Unity实用小工具或脚本——读写Json工具

    一.前言       在Unity中读写Json文件已经有非常好的工具,可以将Json文件和结构体数据进行相互转换,如图1所示,在Unity Asset Store中搜JSON.NET可以找到该插件, ...

  6. Unity实用小工具或脚本——智能包住任意多个物体的碰撞体

    一.前言 有些模型是我们在Unity中进行组合得到的一个模块,这个时候不可能再让美术给加碰撞体,需要用代码智能给其加碰撞体,如图1所示:任意的多个物体组成的一个综合物体最后都可以给它加上合适的碰撞体, ...

  7. Unity实用小工具或脚本——XML工具

    一.前言 这篇文章,早在游戏蛮牛的专栏里就写过,一直在使用这个工具,稍微修改了下.在我们的项目中经常会用到XML来存储一些工程中的设置,而对XML的操作无非就是增删改查四个操作,代码虽然不多,但是使用 ...

  8. Unity实用小工具或脚本——3D物体带坐标轴的拖拽

    一.前言 我们最近要做一个线路的规划编辑,并且是在三维场景中,编辑完就立马能用.立马能用还好说,有特别多的轮子可以用,在三维场景中实时编辑就有点意思了.其实功能就是类似于在Unity的编辑界面操作一个 ...

  9. Unity实用小工具—以对象形式操作Sqlite

    一.介绍 1.1.版本说明:使用的Unity版本为个人版2020.3.39,数据库为Sqlite(一个轻量级跨平台数据库),Sqlite的查看管理工具可以在网上下载https://dbeaver.io ...

最新文章

  1. python三层装饰器-python开发学习day16 (三层装饰器;迭代器)
  2. springCloud Zuul 网关fallback
  3. 记录一次Socket的异常:InputStream.read()阻塞问题
  4. Java计算时间差、日期差总结(亲测)
  5. 一步步编写操作系统 49 加载内核2
  6. Bootstrap完美select标签下拉菜单实现
  7. POJ - 2182 Lost Cows【线段树】
  8. kubectl apply -f_新车 | 新款捷豹F-PACE登陆广州车展!内外提升十分明显
  9. 一款好看的提示框-------记录一下地址
  10. vue ---- 指令综合案例
  11. Linux内核的编译方法及如何往内核中增加程序
  12. 05 CardView的基本使用
  13. 全站HTTPS来了!有何优势、与HTTP有何不同
  14. AspectJ中5种类型的增强注解有什么区别?
  15. linux网络防火墙-iptables基础详解
  16. LTE小区搜索过程及SCH/BCH设计
  17. java 调用felix_寻找在动态加载Jar文件中使用Apache Felix并在Java中在运行时实例化类的基本示例...
  18. 打字游戏之主界面实现
  19. 一个手机里登录2个微信号(微信双开)
  20. 可视化大屏设计尺寸_UI设计中大屏可视化设计尺寸指南

热门文章

  1. 在Power BI中用DAX计算净现值NPV
  2. SAP-基于批次特定计量单位的应用-01-产品数量管理
  3. 《涨知识啦30》-太阳能电池基本工作原理
  4. 教你如何查看附近的WiFi密码
  5. python画正方形的代码_python画正方形的代码是什么?
  6. 青少年研学旅行成长平台“青蛙研学”获数百万天使轮融资...
  7. 创客学院9天C语言三
  8. 55. Jump Game(跳跃游戏)
  9. Java课程设计—学生成绩管理系统(54号童欢)
  10. 路由器android-267d3f,这才是真的高级货玩意:NETGEAR EX7700 AC2200三频无线Mesh扩展器使用体验...