Unity UI笔记

查看不同分辨率

要么选不同Aspect,要么选Free aspect拖Game窗口,选一个非Free的Aspect拖窗口没有用。

UGUI

Canvas

Canvas没有适配屏幕时检查什么:

1.画布Render Mode和Render Camera;

2.画布子物体的Scale是不是1;

3.面板是4锚点,上下左右间距都是0;

Scroll Rect组件

  • Content指向的是能用鼠标拖动的物体。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个范围应该一致,使用起来才不感觉奇怪。

Grid Layout Group组件

自动制表的组件。会令所有子物体的大小、位置不能调整,统一由这个组件调整。

Content Size Fitter组件

会改变所属物体的长宽,依据是它的子物体所占据的区域。作用是让Scroll View正确设置可滑动区域。

Image组件

Color

Color是和Source Image的RGB相乘,所以白色*蓝色=蓝色,红色*蓝色=黑色

拖拽控件的注意点

脚本继承IDragHandler、IBeginDragHandler、IEndDragHandler处理拖拽开始、过程、结束的操作。

拖到另一个面板时被挡住

那么拖拽开始时就要把控件转移到hierarchy靠下的一个对象上。

拖拽松开时回到原位置

首先把父对象设回GridLayoutGroup,然后

LayoutRebuilder.ForceRebuildLayoutImmediate(gridLayoutGroup.transform as RectTransform);

Material

UI/Default着色器、模板测试

带stencil是模板测试相关的参数。

模板测试的参考文章:

UNITY的Mask的替代用法 - 知乎 (zhihu.com)

技术美术百人计划 | 《3.1深度测试与模板测试 》笔记_unity 模板测试-优快云博客

UI元素的渲染顺序是Hierarchy从上到下,模板测试阶段会拿这个元素材质的Stencil ID和Stencil缓冲区里的值比较,根据Stencil Comparison给出的判断标准判断模板测试是否通过。

这篇文章里【unity小技巧】实现FPS武器的瞄准放大效果(UGUI实现反向遮罩,全屏遮挡,局部镂空效果)_unity 开镜-优快云博客

定义了一个镂空材质:

一个背景材质:

并把镂空放在上面:

RectTransform几个字段的含义

anchorMax和anchorMin

选择中间的9种单锚点模式时,anchorMax和anchorMin一样,是此锚点在父物体里的归一化坐标。选择6种双锚点模式和4锚点模式时,anchorMin是偏左下的锚点的归一化坐标,anchorMax是偏右上的锚点的归一化坐标。

如选中间的anchorMax和anchorMin都是(0.5,0.5),选左上都是(0,1)

sizeDelta

x是面板的宽度和锚点之间x距离的差值。对于单锚点,锚点之间x距离是0,sizeDelta.x就是面板宽度;对于左右双锚点,如果锚点在左右边缘,那么面板和屏幕同宽的时候,sizeDelta.x是0,屏幕宽1920,面板左边和屏幕左边对齐,右边在屏幕右边480,那么sizeDelta.x是480.y同理。

实例化面板预制体时面板的一部分控件消失了

预制体:

实例化的:

原因:把那部分控件拖错了,拖到一个会被销毁的GameObject上了。这说明要尽量避免拖错,应该尽量使用每部分特有的组件。

试图用ToggleGroup做多选一列表遇到的坑

因为ToggleGroup自带的多选一特性,想用它做查看人物的人物列表或背包物品列表,遇到一些坑。要给Toggle加被激活时的回调,Toggle是根据数据文件动态创建的,结果创建的时候就调用了onValueChanged。要解决这个问题好像也不比几个按钮简单,就放弃了这个方案。所以动态创建的按钮即使多选一也不要用ToggleGroup,只有写死数量的多选一按钮可以用,比如人物的基本信息、武器、圣遗物、天赋按钮。如果一个面板预制体有一组Toggle,实例化预制体时不会执行任何Toggle的onValueChanged()。

人物查看界面右边多种可能的面板,改变查看的面板时不知道前一个查看的是什么面板,需要把所有面板都删一次

GUI

看不见控件

可能是Rect的长宽都是0.

开启GUI Style后看不见控件

GUI.DrawTexture()画不出来

原因:对齐设置了Right down,但是Texture的Pivot总是在左上角。图放到屏幕右下方了。

UI框架设计

UI系统本质架构

UI系统的功能包括

1.从数据持久文件读取数据到数据管理脚本(一般可以在使用数据管理脚本时通过构造方法顺便读取);

2.把读出的数据设置系统各组件(比如音量);

3.把数据从数据管理脚本显示到显示控件;

4.接收用户的操作并对数据管理脚本的数据做相应修改;

5.同时对相应组件做修改(比如音量)

6.然后重新显示数据;

7.在适当的时间把数据管理脚本存回数据持久文件(一般是关闭面板时);

注意:不能偷懒在用户操作输入控件时直接修改显示控件(比如从背包销毁一个物品时直接把这个格子删掉)!这样不能保证显示控件如实显示数据!显示控件永远只能根据数据管理脚本的数据显示!

上面和图里的操作可以分为两类:输出和输入。在面板初始化时只用执行输出代码,在输入控件响应用户输入时先修改数据,再刷新显示,刷新显示一般需要把一些已有的控件、物品销毁,再生成新的。

什么时候读入内存?什么时候存入硬盘?

1.可以在数据管理器的构造函数里把它的所有数据从硬盘读入,这样占用更多内存;

2.在当前打开的UI需要时再读入,关闭UI时把数据存入硬盘并把内存的数据清除,如果数据管理器只有一个总的,就是把相应的数据字段赋值null,但是也可以不赋空,下次需要时接着用;

数据管理器脚本的设计:集中、分布

上面一个问题的方案2引出了数据管理器脚本的设计问题。是

1.整个游戏只有一个;还是

2.每个UI的数据都有一个脚本,可以就用这个UI脚本,打开UI时从硬盘读取,关闭UI时存入硬盘并清除内存;

但是如果有多个面板同时需要一组数据,就要决定把数据放在哪个面板的脚本,其他面板打开时这个面板也不能关闭,其他面板要盖在这个面板上。

加载主场景时,需要读取当前设置的站台人物、音量设置,但是此时音量设置面板并没有显示,这个音量数据如果放在设置面板脚本,初始化主场景时还要另读取一次,设置后抛弃。

数据管理器分布的方案可能也并不是那么好。

场景里面板互相调用的方法

A面板调用B面板时要找到B面板有多种方法,可以

1.transform.Find(B的名字);

2.A脚本里记录一个B脚本变量,在编辑器拖;(如果面板多了,各面板互相调用,会变成蜘蛛网)

3.使用FindObjectOfType<>()在场景里找;(好像也不错)

4.每个面板一个单例,定义一个泛型单例面板基类;

全局数据在各个场景都要访问,应该做成不继承Monobehaviour的单例;

面板的显示和消失方式

有两种方案:

1.预制体实例化和销毁;如果每个面板一个单例,调用一个未显示面板的单例时就要实例化预制体。在哪里记录预制体位置呢?可以1.在全局数据脚本里使用路径(这种方法除非人工保证面板的类名和路径常量名保持一致,否则面板类名和预制体路径常量名之间没有明显关系);2.在各面板脚本里用一个public const string path记录,实例化时执行:

Instantiate(Resources.Load(XXXPanel.path));

这样实例化和下面调用时都出现了面板类名,类名和路径的关系明显;3.如果喜欢拖,放一个继承Monobehaviour的单例脚本,在检查器把面板预制体拖进去,这个物体需要DontDestroyOnLoad

这个方案的问题:1.如果按钮有声音,且声源在这个面板预制体内,按退出一个面板的按钮时,面板会瞬间被销毁,声音无法播放。如果声源放在一个全局物体上,就要用脚本找到这个声源,不可能在预制体编辑界面拖。

2.激活和失活。如果每个面板一个单例,需要一开始所有面板写入单例后把自己失活。这样的问题是编辑模式所有面板都要打开,Scene窗口很乱。

打开一个面板时前一个面板怎么处理

1.不消失,新面板使用一个带碰撞体的全屏透明贴图挡住前一个面板的输入;

2.前一个面板消失;

动态生成按钮面板的类型

动态生成按钮的面板可能是UI里最复杂的一类面板。这类面板也分几种类型,复杂程度不同。

无详细信息、点击按钮直接操作型 

动态生成按钮一般都包括这几步:

  1. 实例化按钮预制体(可能还要先得到预制体),把它加入按钮列表;
  2. 得到这个数据的信息,一般需要同时从配表和用户数据取信息,填入按钮的图标、文本等元素;
  3. 按钮添加对应数据的地址,可能是一个List里的index;
  4. 添加按钮回调(存在闭包的问题);

动态按钮的回调一般有参数,不同按钮的参数不同。动态生成的控件按钮的回调怎么赋值?用代码还是拖?函数定义在面板脚本还是按钮脚本?

如果想用拖,函数必须写在按钮脚本里,这个函数如果有参数,参数必须在预制体里就确定,所以这个函数不能有参数。不过可以用一个无参函数把真正要调用的,可能是其他类的有参数方法封装起来(动画事件也可以用类似套路)。

1.页面脚本生成按钮时添加回调,需要把函数写入lambda表达式,参数临时声明一个变量记录,这里出现了闭包。

int serverId=serverInfos[i].id;
serverCell.buttonServer.onClick.AddListener(
    ()=>{serverCell.ServerCellClick(serverId);});

选中后显示详细信息、共用操作按钮型

按下按钮直接执行操作往往不能让玩家掌握详细信息,需要进一步优化成这种框架。

这比动态生成按钮列表更复杂一点,还需要:

  1. 确定一种显示选中项的方法,可能是加框、文本变色、图标替换等;
  2. 按下按钮后,更新选中项index,把所有按钮设为未选中,把index的按钮设为选中;
  3. 所有按钮共用的哪些选项按钮要触发的回调随着选中的index相应变化。

下面这个例子:选中项的图标会向右。选中显示的详细信息包括销毁枪械、实例化新枪械(包括可能的动作变化)、刷新右侧的所有信息。右下角按钮回调的内容随选中项的index自然变化。

写了几次发现这种面板都是套路,或许可以提炼一个模板:

  • 系统有一个面板脚本和一个格子按钮脚本;
  • 面板脚本有一个按钮脚本的列表,一个int选中项的索引,一个对按钮预制体的引用;
  • 按钮脚本有对自己身上的Button、Image、Text的引用,有一个int index记录自己是按钮列表的第几个元素;
  • 一个批量显示按钮的方法,用for循环,先把按钮预制体实例化,同时设置父级为自动布局组件的transform,再得到按钮上的按钮脚本,从用户数据和配置数据取得需要的数据,把文本、图标写入给按钮的控件,把循环变量i写给;

鼠标进入显示悬浮面板型

浮动面板使用IPointerEnterHandler、IPointerExitHandler实例化和销毁。浮动面板有几个坑:

被其他格子挡住

如上图。因为浮动面板的父级设为了所属的格子,hierarchy里一般格子下面还有格子。要在hierarchy所有格子的下方建一个锚点对象做浮动面板的父级,设置位置可以先实例化为格子父级,设置局部位置0,再改成锚点的子级。或者设置世界位置和锚点一样。

鼠标在格子和浮动面板重叠的区域时浮动面板一直闪

因为面板出现时挡住鼠标射线被判定移出格子,销毁面板后又判定进入格子。解决方法:浮动面板的所有Image和Text不勾选Raycast Target。

难点:多个面板里的多类物品的相似又不同的格子按钮脚本的设计

现在我们要做一个类似地铁逃生的背包、仓库界面。里面有两个面板,两个面板都要放弹匣、药品、手榴弹等不同类物品。要设计几种格子脚本?脚本间的继承关系是?

多用户游戏的数据系统

如果一个游戏要做登录注册界面,那么它就是一个多用户游戏,意味着它有一个记录用户名和密码的文件,而其他大部分数据(玩家拥有的人物、武器、物品、音量)都因用户而异,需要在每个用户注册时建一个文件夹,把这些用户数据文件都放在里面。

这样DataManager在构建的时候就不能加载所有的数据,因为还没登录,不知道用户名,也就不知道这些数据文件的路径。所以需要一个UserManager,开始运行时构建,负责获取上次登录用户、注册新用户、判断登录是否成功。加载登入用户数据的DataManager在登录成功后构建,它读取数据的路径依赖登入的用户名。

 数据管理脚本读取数据持久文件的方式

1.在数据管理脚本构造方法里读取

2.我想在数据属性的get方法里读取,结果发现这样set方法也应该立即写入文件,读取写入文件的频率会很高。不知道开销会不会很大。

public SettingsData settingsData{
        get{
            if(File.Exists(settingsDataPath)){
                string jsonString=File.ReadAllText(settingsDataPath);
                return JsonUtility.FromJson<SettingsData>(jsonString);
            }
            else{
                return new SettingsData(){
                    musicOn=true,
                    musicVolume=1,
                    soundOn=true,
                    soundVolume=1,
                };
        }}
    }

场景管理

需要有一个《场景转换图》列出所有的场景和哪些场景。一般需要登录场景、主界面场景、若干游戏场景。登录场景和主界面场景应该分开,因为主界面场景可能从登录场景或游戏场景进入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值