Unity UI笔记
查看不同分辨率要么选不同Aspect要么选Free aspect拖Game窗口选一个非Free的Aspect拖窗口没有用。UGUICanvasCanvas没有适配屏幕时检查什么1.画布Render Mode和Render Camera2.画布子物体的Scale是不是13.面板是4锚点上下左右间距都是0Scroll RectContent指向的是能用鼠标拖动的物体。Viewport指向的是限定Content能移到的范围的物体。Content指向的物体还必须是Scroll Rect所属物体的子物体才能拖动如果Viewport指向的物体完全包围了Content则拖动后松开鼠标Content会自动回到Viewport的中心。设计者的意图是显示区域已经完全够显示Content了所以无需拖动而且此时下面说的Viewport对Content移动范围的限制会失效只要不松鼠标能一直拖直到鼠标到屏幕边界。Content最往下能移到Content的上边缘和Viewport的上边缘对齐其他边缘同理。如下图Content是淡白色区域Viewport是红色块在勾选Horizontal和Vertical的情况下Content的移动范围如图。Content的移动范围其实和Scroll Rect所属物体的区域没有关系。鼠标在Scroll Rect所属物体的区域淡黑色区域或Content内按下都能拖动Content。Mask需要同时挂载一个Image组件才有用只显示Image区域的子物体。Mask不会影响鼠标按下能拖动的区域。在动态生成按钮的面板中作用是隐藏Scroll View之外的格子。以上两个组件中一共出现了3种范围分别是Content可移到的范围鼠标按下可拖动的范围显示Content的范围。一般来说3个范围应该一致使用起来才不感觉奇怪。Vertical Layout Group垂直布局组 (Vertical Layout Group) - Unity 手册控制子对象高度时各子对象的高度不同子对象高度和Text的字体大小有关。看了看官方文档的高度规则评价是写了个几把。这里这个按钮的高度太小就加一个Layout Element调一下最小和偏好高度。别的懒得研究。Grid Layout Group自动制表的组件。会令所有子物体的大小、位置不能调整统一由这个组件调整。Content Size Fitter组件会改变所属物体的长宽依据是它的子物体所占据的区域。和XXX Layout Group配合使用Layout Group把控件排好Content Size Fitter根据子控件占的大小设置Content的大小。Content大于Viewport才能拖动。Image组件ColorColor是和Source Image的RGB相乘所以白色*蓝色蓝色红色*蓝色黑色拖拽控件的注意点脚本继承IDragHandler、IBeginDragHandler、IEndDragHandler处理拖拽开始、过程、结束的操作。拖到另一个面板时被挡住那么拖拽开始时就要把控件转移到hierarchy靠下的一个对象上。拖拽松开时回到原位置首先把父对象设回GridLayoutGroup然后LayoutRebuilder.ForceRebuildLayoutImmediate(gridLayoutGroup.transform as RectTransform);实例化面板预制体时面板的一部分控件消失了预制体实例化的原因把那部分控件拖错了拖到一个会被销毁的GameObject上了。这说明要尽量避免拖错应该尽量使用每部分特有的组件。试图用ToggleGroup做多选一列表遇到的坑因为ToggleGroup自带的多选一特性想用它做查看人物的人物列表或背包物品列表遇到一些坑。要给Toggle加被激活时的回调Toggle是根据数据文件动态创建的结果创建的时候就调用了onValueChanged。要解决这个问题好像也不比几个按钮简单就放弃了这个方案。所以动态创建的按钮即使多选一也不要用ToggleGroup只有写死数量的多选一按钮可以用比如人物的基本信息、武器、圣遗物、天赋按钮。如果一个面板预制体有一组Toggle实例化预制体时不会执行任何Toggle的onValueChanged()。人物查看界面右边多种可能的面板改变查看的面板时不知道前一个查看的是什么面板需要把所有面板都删一次实例化的面板位置大小变了如果实例化面板的位置、大小和预制体不一样了可能是因为先实例化为了根对象又设为画布的子对象。应该实例化时直接为画布的子对象pageObject.Instantiate(UIPrefab,canvas.transform);多选一页签ToggleGroup不如几个Button理由如下多选一toggle按已经选中的toggle时还是会执行回调白白重复了上次点击的工作。button可以代码判断跳过这个多选一toggle组已经不可能把toggle点灭实际上回调onValueChanged里的bool参数已经没用了还要多一步if判断。只需要给按钮加一个选中后高亮的方法维护一个已选中页签变量就可以完成多选一页签的功能。多选一页签的功能包括对表格的更新和选中页签高亮的更新。高亮更新先把选中的页签高亮关闭然后写入把新选中的页签写入再打开选中的页签高亮。public Tab tabGun,tabMag,tabGrenade... Tab tabOn; tabGun.button.onClick.AddListener((){ if(tabOntabGun){ return; } UpdateTabOn(tabGun); ClearCells(); ShowGuns(); }); void UpdateTabOn(TabShop tab){ tabOn.imageSelected.enabled false; tabOntab; tabOn.imageSelected.enabledtrue; }怎么让Image根据一个Text里文本占的大小自动调整大小如果让Image做Text的子对象Image撑满TextText加Content size fitterText可以自动调整大小但是Image会盖住文本。给Slider设置值报空slider不为空但是报空因为设置value时会调用值变化回调此时如果添加了回调但是添加了空就会。DropDown复制出来的下拉菜单比模板短预制体里面Content加Vertical Layout GroupContent Size Fitter然后模板激活一下让布局生效再隐藏保存。主相机、UI相机叠加时显示血条Vector3 screenPos Camera.main.WorldToScreenPoint(barWorldPos); Vector2 pos; RectTransformUtility.ScreenPointToLocalPointInRectangle( canvas.transform as RectTransform, screenPos, UICamera, out pos ); transform.localPosition pos;先WorldToScreenPoint从世界坐标通过主相机到屏幕坐标再用RectTransformUtility.ScreenPointToLocalPointInRectangle从屏幕坐标到血条的局部坐标。UI框架设计UI系统本质架构游戏的数据处理包括从数据持久文件读取数据到数据管理类一般可以在使用数据管理脚本时通过构造方法顺便读取把读出的数据设置系统各组件比如音量把数据从数据管理脚本显示到显示控件接收用户的操作并对数据管理脚本的数据做相应修改同时对相应组件做修改比如音量然后重新显示数据在适当的时间把数据管理脚本存回数据持久文件一般是关闭面板时UI的使命就是1.把用户的操作变成数据修改2.如实反映数据注意不能偷懒在用户操作输入控件时直接修改显示控件比如从背包销毁一个物品时直接把这个格子删掉这样不能保证显示控件如实显示数据显示控件永远只能根据数据管理类的数据显示上面和图里的操作可以分为两类输出和输入。在面板初始化时只用执行输出代码在输入控件响应用户输入时先修改数据再刷新显示刷新显示一般需要把一些已有的控件、物品销毁再生成新的。UI面板的储存和加载UI面板要做预制体。我们知道加载资源的方式有直接引用ResourcesAB包直接引用因为任何面板可能在任何时候需要加载需要一个全局存在、能通过拖入引用对象的对象也就是ScriptableObject。就需要一个作为UI面板总表的ScriptableObject然后这玩意还需要Resources或AB包加载。新加一个面板需要的工作量是总表加一个字段、拖一下。Resources加载需要有路径面板们的路径记在哪一个总表还是各个面板类里又是一个选择。记载总表里面板预制体路径和面板类名是割裂的记在面板类里则可以Resources.Load(PanelXXX.path); PanelXXX.Instance.XXX();预制体路径和面板类是联系起来的。场景里面板互相调用的方法A面板调用B面板时要找到B面板有多种方法可以1.transform.Find(B的名字)2.A脚本里记录一个B脚本变量在编辑器拖如果面板多了各面板互相调用会变成蜘蛛网3.使用FindObjectOfType()在场景里找好像也不错4.每个面板一个单例定义一个泛型单例面板基类面板的显示和消失方式有两种方案1.预制体实例化和销毁如果每个面板一个单例调用一个未显示面板的单例时就要实例化预制体。在哪里记录预制体位置呢可以1.在全局数据脚本里使用路径这种方法除非人工保证面板的类名和路径常量名保持一致否则面板类名和预制体路径常量名之间没有明显关系2.在各面板脚本里用一个public const string path记录实例化时执行Instantiate(Resources.Load(XXXPanel.path));这样实例化和下面调用时都出现了面板类名类名和路径的关系明显3.如果喜欢拖放一个继承Monobehaviour的单例脚本在检查器把面板预制体拖进去这个物体需要DontDestroyOnLoad这个方案的问题1.如果按钮有声音且声源在这个面板预制体内按退出一个面板的按钮时面板会瞬间被销毁声音无法播放。如果声源放在一个全局物体上就要用脚本找到这个声源不可能在预制体编辑界面拖。2.激活和失活。如果每个面板一个单例需要一开始所有面板写入单例后把自己失活。这样的问题是编辑模式所有面板都要打开Scene窗口很乱。打开一个面板时前一个面板怎么处理1.不消失新面板使用一个带碰撞体的全屏透明贴图挡住前一个面板的输入2.前一个面板消失动态生成按钮面板的类型动态生成按钮的面板可能是UI里最复杂的一类面板。这类面板也分几种类型复杂程度不同。无详细信息、点击按钮直接操作型动态生成按钮一般都包括这几步实例化按钮预制体可能还要先得到预制体把它加入按钮列表得到这个数据的信息一般需要同时从配表和用户数据取信息填入按钮的图标、文本等元素按钮添加对应数据的地址可能是一个List里的index添加按钮回调存在闭包的问题动态按钮的回调一般有参数不同按钮的参数不同。动态生成的控件按钮的回调怎么赋值用代码还是拖函数定义在面板脚本还是按钮脚本如果想用拖函数必须写在按钮脚本里这个函数如果有参数参数必须在预制体里就确定所以这个函数不能有参数。不过可以用一个无参函数把真正要调用的可能是其他类的有参数方法封装起来动画事件也可以用类似套路。1.页面脚本生成按钮时添加回调需要把函数写入lambda表达式参数临时声明一个变量记录这里出现了闭包。int serverIdserverInfos[i].id; serverCell.buttonServer.onClick.AddListener( (){serverCell.ServerCellClick(serverId);});选中后显示详细信息、共用操作按钮型按下按钮直接执行操作往往不能让玩家掌握详细信息需要进一步优化成这种框架。这比动态生成按钮列表更复杂一点还需要确定一种显示选中项的方法可能是加框、文本变色、图标替换等按下按钮后更新选中项index把所有按钮设为未选中把index的按钮设为选中所有按钮共用的哪些选项按钮要触发的回调随着选中的index相应变化。下面这个例子选中项的图标会向右。选中显示的详细信息包括销毁枪械、实例化新枪械包括可能的动作变化、刷新右侧的所有信息。右下角按钮回调的内容随选中项的index自然变化。写了几次发现这种面板都是套路或许可以提炼一个模板系统有一个面板脚本和一个格子按钮脚本面板脚本有一个按钮脚本的列表一个int选中项的索引一个对按钮预制体的引用按钮脚本有对自己身上的Button、Image、Text的引用有一个int index记录自己是按钮列表的第几个元素一个批量显示按钮的方法用for循环先把按钮预制体实例化同时设置父级为自动布局组件的transform再得到按钮上的按钮脚本从用户数据和配置数据取得需要的数据把文本、图标写入给按钮的控件把循环变量i写给鼠标进入显示悬浮面板型浮动面板使用IPointerEnterHandler、IPointerExitHandler实例化和销毁。浮动面板有几个坑被其他格子挡住如上图。因为浮动面板的父级设为了所属的格子hierarchy里一般格子下面还有格子。要在hierarchy所有格子的下方建一个锚点对象做浮动面板的父级设置位置可以先实例化为格子父级设置局部位置0再改成锚点的子级。或者设置世界位置和锚点一样。鼠标在格子和浮动面板重叠的区域时浮动面板一直闪因为面板出现时挡住鼠标射线被判定移出格子销毁面板后又判定进入格子。解决方法浮动面板的所有Image和Text不勾选Raycast Target。动态按钮列表记录选中的按钮是用int index还是按钮类用int index好处是得到上一个、下一个按钮很方便用按钮类则需要list.IndexOf()先得到索引。而数据管理脚本读取数据持久文件的方式1.在数据管理脚本构造方法里读取2.我想在数据属性的get方法里读取结果发现这样set方法也应该立即写入文件读取写入文件的频率会很高。不知道开销会不会很大。public SettingsData settingsData{ get{ if(File.Exists(settingsDataPath)){ string jsonStringFile.ReadAllText(settingsDataPath); return JsonUtility.FromJsonSettingsData(jsonString); } else{ return new SettingsData(){ musicOntrue, musicVolume1, soundOntrue, soundVolume1, }; }} }UI材质的stencil模板测试这是默认的设置。缓冲区存有一个Stencil ID当前物体存有一个Stencil ID参考值二者按Stencil Comparison比较得到是否通过通过则渲染当前物体并且Stencil Operation决定后续对缓冲区的操作。Stencil Comparison值名称作用0Disabled禁用模板测试或深度测试不可用时1Never测试永远不通过像素始终被丢弃2Less当(refValue readMask) (bufferValue readMask)时通过测试3Equal当(refValue readMask) (bufferValue readMask)时通过测试4LessEqual当(refValue readMask) (bufferValue readMask)时通过测试5Greater当(refValue readMask) (bufferValue readMask)时通过测试6NotEqual当(refValue readMask) ! (bufferValue readMask)时通过测试7GreaterEqual当(refValue readMask) (bufferValue readMask)时通过测试8Always测试永远通过默认值默认永远通过就是一定渲染当前物体。Stencil Operation0Keep保持模板缓冲区的当前值不变默认操作1Zero将模板缓冲区的值重置为02Replace用参考值Ref替换模板缓冲区的值UGUI Mask组件的默认操作3IncrSat增加模板缓冲区的值上限255超过则保持2554DecrSat减少模板缓冲区的值下限0低于则保持05Invert按位取反模板缓冲区的值如1变为2546IncrWrap增加模板缓冲区的值溢出时从0重新开始255→07DecrWrap减少模板缓冲区的值溢出时从255重新开始0→255默认是渲染后如果通过保持缓冲区不变。比如当前缓冲区Stencil ID2当前物体Stencil ID0渲染当前物体并且缓冲区保持Stencil ID2。Mask的设置一定通过测试渲染渲染后把自己的Stencil ID1写入缓冲区替代之前默认材质的0.Mask的子对象设置ID相等才渲染它的ID和Mask相同和默认材质不同所以只在Mask区域渲染。它不改变缓冲区可以预测再来一个子对象会覆盖它。再来看看【unity小技巧】实现FPS武器的瞄准放大效果UGUI实现反向遮罩全屏遮挡局部镂空效果_unity 开镜-CSDN博客里面瞄准镜的两个材质镂空材质用来在屏幕中心掏一个圆形区域。它使用一个中心是圆形其他是透明的图片。它会通过把缓冲区写成1.背景材质生成大部分的黑色。背景放在镂空后面比它后渲染。它和缓冲区相同时通过读取遮罩是1就是只比较第1位它的ID是2二进制是00000010和默认材质00000000比较第1位能通过覆盖默认材质和镂空00000001比较第1位不通过露出镂空材质。所以它在镂空对象下面也不覆盖它。这里背景的Read Mask写255ID写0也能让它覆盖默认材质露出镂空材质。Stencil Operation是测试是否通过都执行吗资料说还有Pass、Fail参数指定通过和失败时的操作但是材质面板只有一个Stencil Operation失败时是否执行可以做个实验红4强制通过把缓冲区写成4青3强制通过把缓冲区写成3橙4和缓冲区比较Stencil Operation是把缓冲区写成4蓝4也是和缓冲区比较Stencil Operation是把缓冲区写成4。那么橙4在青3的部分未通过如果执行了把缓冲区写4蓝4就应该通过。但蓝4没有通过。证明Stencil Operation只在通过时执行。虽然对于不修改缓冲区的和强制通过的包括了大部分物体这个结论没有意义只有非强制通过修改缓冲区的才有影响。Color Mask限定渲染的颜色为0时不渲染。但会对缓冲区ID做自己的处理。Use Alpha Clip是否对图片的透明区域缓冲区做自己的处理不勾选则处理区域一定是长方形。和渲染的图片是否透明无关。得到当前鼠标是否在某个UI元素上UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject()这个函数返回的好像是上一次点击光标在不在UI上。这一段是正确的public bool IsMouseOverUI() { PointerEventData eventDataCurrentPosition new PointerEventData(EventSystem.current); eventDataCurrentPosition.position new Vector2(Input.mousePosition.x, Input.mousePosition.y); ListRaycastResult results new ListRaycastResult(); EventSystem.current.RaycastAll(eventDataCurrentPosition, results); return results.Count 0; }