Unity-进阶知识

ooowl
  • 游戏开发
  • unity
About 29 min

Unity-进阶知识

IMGUI

组件核心

IMGUIopen in new window(Immediate Mode GUI即时模式图形用户界面)一般简称GUI。是一个代码驱动的UI系统,不要用它为玩家制作UI功能,GUI主要用于开发和调试阶段创建游戏内调试工具在运行时检查和调整游戏状态;制作Unity的拓展工具和编辑器UI。比如地图编辑器,技能编辑器,资源打包自定义等。
在 MonoBehaviuor中有一个特殊的函数void OnGUI在里面写GUI相关的代码,类似于生命周期每帧执行,在OnDisable和LateUpdate之间执行,这意味着Inspector修改参数会有即时反馈。
Texture的缩放比例会始终保持一致,一般把这东西放出来都是为了编辑,权限public就行。 GUI的画布原点(0,0)是在左上角
所有控件必传的是位置信息内容

  • 使用 Rect 参数来指定控件的位置和尺寸。Rect 包含 xy 位置以及 widthheight 尺寸
  • 内容可以点进函数里面看,大部分都接受GUIContent比如下面的btncontent
  • 选传 GUIStyle,可以先声明一个GUIStyle对象,就可以编辑CSS啦😄(哈哈兜兜转转又回到web 按钮只有在按钮内部进行一次按下和点击才算点击。只能按一次,但是RepeatButton可以长按一直触发(竟然用if来判断我为什么感觉这方式有点沙币
    使用GUIStyle 中的 FixedWidth FixedHeight去修改图片的大小和响应区域,不要改Rect
    Normal(false)/OnNormal(true)修改是否选中的状态,单选可以自己做出来,注意看在一个循环返回中怎么处理的
    窗口必须 赋值在函数中DragWindow才能拖动!!!DragWindow可以确定可以拖动的范围
Click to see more
public class IMGUI : MonoBehaviour  
{  
    // 测试文本  
    public GUIContent content;  
    public Rect rect;  
    public Rect rect1;  
    public Texture tex;  
    // 测试按钮  
    public Rect btnrect=new Rect(600,800,100,100);  
    public GUIContent btncontent;  
  
    // toggle选项框  
    private bool isSealed0 = false;  
    private bool isSealed1 = false;  
    private int isSealed3 = 1;  
    public GUIStyle style1;  
    //  文字和密码  
    public string inputString="请接收输入";  
    public string inputStringPasswd="输入密码";  
    //  滑动条  
    public float nowValue=5f,minVlaue=0f,maxVlaue=10f;  
    // 直接画个图  
    public Texture2D demoTexture;  
    //  多选工具栏(按钮)  
    public int toolbarIndex = 0;  
    private string[] toolbarNames=new string[]{"选项一","选项二","选项三"};  
    public int gridIndex = 0;  
    private string[] gridNames=new string[]{"选项一","选项二","选项三","选项2一","选项2二","选项2三"};  
    private string[] demogride =new string[]{"选项一","选项二","选项三","选项2一","选项2二","选项2三","选项3一","选项3二","选项3三","选项4一","选项4二","选项4三"};  
    //  遮罩窗口  
    public Rect groupos = new Rect(50, 660, 200, 200);  
    //  滑动条窗口  
    public Rect scPos=new Rect();  
    public Vector2 nowPos=new Vector2();  
    public Rect showPos=new Rect();  
    //  窗口相关  
    public int windowUid = 1;  
    public Rect windowPos=new Rect(50,800,200,200);  
    private void OnGUI()  
    {   //  
        GUI.Label(new Rect(0,0,100,20),"烤全羊啊嗯"); //  使用Label绘制  
        GUI.Label(rect,tex);  
        content.text = "打组一块显示";  
        content.image = tex;  
        content.tooltip = "中嘞!"; //  鼠标移动上去显示  
        GUI.Label(rect1,content); // 可以给GUI组件设置tooltip  
        // 鼠标移上去的时候就可以直接显示tooltip,默认是空字符串,他会一直打印很烦  
        if(GUI.tooltip!="") Debug.Log(GUI.tooltip);  
        btncontent.text = "按钮哎";  
        if (GUI.Button(btnrect, btncontent)) // 这按钮只能按一次,但是RepeatButton可以长按一直触发  
        {  
            print("你们这是什么按钮啊,你们这个按钮害人不浅!");  
        }  
        // toggle选项框  
        isSealed0 = GUI.Toggle(new Rect(0, 0, 100, 30),isSealed0, "效果开关0");  
        isSealed1 = GUI.Toggle(new Rect(300, 300, 100, 30),isSealed1, "效果开关1",style1); // rect 的宽高是toggle的响应位置  
  
        if(GUI.Toggle(new Rect(50, 80, 100, 30),isSealed3==1, "效果开关3-0"))isSealed3 = 1;  
        if(GUI.Toggle(new Rect(50, 100, 100, 30),isSealed3==2, "效果开关3-1"))isSealed3 = 2;  
        if(GUI.Toggle(new Rect(50, 120, 100, 30),isSealed3==3, "效果开关3-2"))isSealed3 = 3;  
        if(GUI.Toggle(new Rect(50, 140, 100, 30),isSealed3==4, "效果开关3-3"))isSealed3 = 4;  
  
        //  文字和密码  
        inputString = GUI.TextField(new Rect(50, 160, 100, 30), inputString);  
        inputStringPasswd = GUI.PasswordField(new Rect(50, 200, 100, 30), inputStringPasswd,'星');// 接收密码字段  
        //  滑动条  
        nowValue = GUI.HorizontalSlider(new Rect(50, 250, 100, 30), nowValue, minVlaue, maxVlaue);  
        // 直接画个图  
        // 后面是 ScaleToFit 计算宽高,等比例缩放不裁剪 StretchToFill 无视比例拉伸 ScaleAndCrop 等比例缩放会裁剪  
        // imageAspect 自定义缩放宽高比  
        GUI.DrawTexture(new Rect(50, 350, 100, 100),demoTexture,scaleMode:ScaleMode.StretchToFill,alphaBlend:false,imageAspect: 10);  
        GUI.Box(new Rect(50, 400, 100, 100),"这是个盒子,一个非常单纯的盒子");  
        //  多选工具栏(按钮)  
        toolbarIndex=GUI.Toolbar(new Rect(50, 500, 200, 50),toolbarIndex,toolbarNames); // 需要不断赋值,可以使用switch case赋值  
        gridIndex=GUI.SelectionGrid(new Rect(50, 560, 200, 50), gridIndex, gridNames,3); // 就是满了会自动换行  
        //  相当于html里的遮罩,只显示窗口位置  
        GUI.BeginGroup(groupos); // Rect  
        GUI.Button(new Rect(0, 0, 80, 60),"你们这个按钮也害人不浅啊");  
        GUI.EndGroup();  
  
        //  Rect position组件的位置大小 Vector2 scrollPosition滑动到哪了 Rect viewRect 内容的范围大小  
        nowPos=GUI.BeginScrollView(scPos,nowPos,showPos);  
        GUI.SelectionGrid(new Rect(0, 0, 200, 50), 0, demogride,4); // 注意位置已经是相对位置了  
        GUI.EndScrollView();  
  
        //  窗口相关,必须 赋值 且 在函数中DragWindow才能拖动!!!  
        windowPos=GUI.Window(windowUid, windowPos, DrawWindow,"窗口标题"); // 会调用函数里面的东西进行绘制  
        GUI.ModalWindow(windowUid+1, windowPos, DrawWindow,"窗口标题"); // 相当于获取了焦点的窗口,相当于alert了  
    }  
    private void DrawWindow(int windowID) //  实际上这东西绑在了一个委托上  
    {  
        //  可以通过windowID来判断不同窗口的处理逻辑  
        GUI.RepeatButton(new Rect(10, 10, 40, 50), "按钮按钮");  
        if (windowUid == 1) // 给第一个窗口用的  
        {   // 允许拖动这一步也是必须的  
            GUI.DragWindow(new Rect(0,0,1000,20)); // 允许拖动哪一块范围,默认全能拖  
        }  
    }   
}

📌Tip

GUI控制显示隐藏,可以写在一个脚本中,用变量控制启用;或者挂在对象上,控制脚本是否启用;或者控制对象是是否启用。
一般是控制对象,高级一点的UI框架都是这么封装的

GUILayout

GUILayout 可以用来写顶栏工具和Inspector不太适合直接写UI 设置全局颜色GUI.color然后再设置文本颜色GUI.contentColorGUI.backgroundColor这两个颜色会相乘好怪,就这仨颜色。 颜色会跨脚本影响看执行顺序
右键创建GUI Skin 可以直接读取预设的主题,使用GUISkin skin来创建,赋值创建的GUISkin文件就可以了。绘制时使用了GUIStyle会优先使用指定的样式。

Click to see more
private void OnGUI()  
{  
    //  全局颜色  
    GUI.color = new Color(1f,0f,0f,1f); // RGBA颜色  
    GUI.Label(new Rect(150,100,100,100),"This is red text");  
    // GUI.color = Color.white; // 白色重置颜色  
    GUI.skin = null; // 规定接下来绘制使用GUISkin  
    // GUILayout 可以用来写顶栏工具和Inspector  
    GUILayout.Button("按钮1哦",GUILayout.ExpandWidth(false)); // GUILayout.Width(300);在此控件上失效  
    GUILayout.Button("按钮2哦");  
    GUILayout.Button("按钮3哦");  
    GUILayout.BeginVertical(); // 布局控制  
    GUILayout.EndVertical();  
    //  GUILayoutOption提供的选项  
    // 控件的固定宽高  
    GUILayout.Width(300);  
    GUILayout.Height(200);  
    // 允许控件的最小宽高  
    GUILayout.MinWidth(50);  
    GUILayout.MinHeight(50);  
    // 允许控件的最大宽高  
    GUILayout.MaxWidth(100);  
    GUILayout.MaxHeight(100);  
    // 允许或禁止水平的样式  
    GUILayout.ExpandWidth(true); // 允许水平  
    GUILayout.ExpandHeight(false); // 禁止高度  
}

IMGUI缺点是 代码控制繁琐,运行时才能看效果,不能分辨率自适应在mono类加特性[ExecuteAlways]可以让UI所见即所得 (其他的脚本其实也能用

UI血条跟随屏幕动,使用IMGUI的小框架,因为屏幕的Y轴是从左下角算起的,而IMGUI原点是从右上角算的,注意怎么转换

public Texture maxHpBG;  
public Texture hpBG;  
public Rect maxHpRect;  
public Rect hpRect;  
  
private void OnGUI()  
{  
    Vector3 screenPos = Camera.main.WorldToScreenPoint(this.transform.position);  
    screenPos.y = Screen.height - screenPos.y; // 因为屏幕的Y轴是从左下角算起的,而IMGUI原点是从右上角算的,注意怎么转换  
  
    maxHpRect.x = screenPos.x - 50;  
    maxHpRect.y = screenPos.y - 50;  
    maxHpRect.width = 100;  
    maxHpRect.height = 15;  
    // 画血条背景
    GUI.DrawTexture(maxHpRect, maxHpBG);  
  
    hpRect.x = screenPos.x - 50;  
    hpRect.y = screenPos.y - 50;  
    hpRect.width = (float)Hp / maxHP * 100f;  
    hpRect.height = 15;  
    // 画血条
    GUI.DrawTexture(hpRect, hpBG);  
  
}

UGUI

NGUI太老了,听劝学UGUI
Unity内置的右键Create->UI创建 一旦创建UI组件会自动创建Canvas和EventSystem。在 Game窗口->Stats(Statistics)->Batches就是DrawCall的数量。

组件核心

Canvas 负责渲染所有的子UI,不在Canvas的UI渲染不了,场景上允许有多个Canvas以设置不同的渲染和分辨率适应,但是一般情况只有一个。参数如下:
RenderMode渲染模式

  • 默认ScreenSpaceOverlay屏幕空间,UI始终在前。WorldSpace世界空间3D模式(VRAR常用,游戏不常用)
    • PixelPerfect无锯齿精确渲染,性能换效果(图集够清楚就不用)
    • SortOrder控制多个Canvas的渲染顺序,越小的在越前
  • ScreenSpaceCamera屏幕空间摄像机模式,3D物体可以显示在UI之前。
    • RenderCamera使用屏幕空间时的参数。
      • 一般 不推荐使用主摄像机,使用单独UI摄像机ClearFlags->DepthOnly,CullingMask->UI,去掉音频监听,主摄像机CullingMask不渲染UI层,以便控制UI和3D物体先后顺序
      • 3D物体和粒子想要显示在前面就直接扔UI摄像机下面,改变大小,粒子有单独的OrderInLayer。或者使用右键Create->RenderTexture,设置好摄像机,挂载到RawImage。
    • PlaneDistance控制UI层离摄像机远近
    • SortingLayer+SortingOrder层和层内Order对多个Canvas排序(回忆一下层排序的方法
  • WorldSpace 作为一个对象存在场景内,很多时候在世界中做跟随角色血条用
    • 使用世界空间的时候,既处理元素与其他元素的位置,又要处理相对摄像机视角位置,不同设备分辨率又不一样很麻烦

CanvasScaler 用于画布缩放分辨率自适应。注意他不负责位置由RectTransform去负责。选中指定Canvas之后在RectTransform中可以看到宽高缩放系数,符合公式 宽高x缩放系数=分辨率
更改这个组件本质上来说就是屏幕分辨率参考分辨率经过不同算法计算得出缩放系数然后参与RectTransform计算
UIScaleMode

  • ConstantPixelSize默认UI始终固定大小(用的少,除非用代码计算
    • ScaleFactor 手动指定RectTransform的缩放系数
    • ReferencePixelsPerUnit参考单位像素 符合公式 UI原始尺寸=图片大小(像素)/(PixelsPerUnit/ReferencePixelsPerUnit)
  • ScaleWithScreenSize缩放模式
    • ReferenceResolution参与分辨率自适应的计算(美术出图的标准分辨率
    • ScreenMatchMode
      • Expand:拓展画布的宽或高使其高于参考分辨率,可能产生黑边。 缩放系数=Mathf.Min(屏幕宽/参考分辨率宽,屏幕高/参考分辨率高); 画布尺寸=屏幕尺寸/缩放系数
      • Shrink:裁剪画布区域根据宽高比放大画布,可能产生裁剪。 缩放系数=Mathf.Max(屏幕宽/参考分辨率宽,屏幕高/参考分辨率高); 画布尺寸=屏幕尺寸/缩放系数
      • MatchWidthOrHeight:以宽高或者二者的平均值作为参考来缩放画布区域(使用对数计算)复习到的时候最好还是看看效果
        • Match竖屏游戏=0 保证宽度优先,高了上下会有黑边,矮了左右会被裁剪,但是保持UI缩放大小不变
        • Match横屏游戏=1 保证高度优先,宽了左右会有黑边,窄了左右会被裁剪,但是保持UI缩放大小不变
  • ConstantPhysicalSizeUI始终保持相同物理大小
    • PhysicalUnit使用什么单位计算
    • 后面三个都是DPI的设置FallbackScreenDPI获取不到系统DPI的回滚值,DefaultSpriteDPI美术出图的参考DPI ReferencePixelsPerUnit单位参考像素
      • 新单位参考像素=单位参考像素xPhysicalUnit/DefaultSpriteDPI 然后再用这个新单位参考像素执行ConstantPixelSize的计算
      • 和ConstantPixelSize相比都不会进行缩放有多大显示多大,会根据DPI调整大小以保持物理像素一样
  • 使用建议一般使用ScaleWithScreenSize,存在横竖屏切换就Expand或Shrink,否则就MatchWidthOrHeight

GraphicRaycaster 检测UI输入事件的射线发射器,是通过图形不是通过刚体碰撞器完成的。屏幕空间摄像机模式下面参数才能起效

  • IgnoreReversedGraphics是否忽略反转图形,如果元素XY被反转过,能不能被响应点击。
  • BlockingObjects 会被哪些碰撞器挡住射线
  • BlockingMask 和第二个配合,可以指定某些层的挡住射线,其他不能

EventSystem 事件系统,管理输入事件都会被它轮询检测并分发给UI控件

  • First Selected:一开始就的焦点,可以设置到哪个对象上
  • SendNavigationEvents:是否允许导航事件键盘wasd在元素间移动焦点
  • DragThreshold:拖拽操作的阈值(移动多少像素算拖拽)

StandaloneInputModule 一般不会进行修改,相当于UGUI和InputManager对接的模块。注意下面参数

  • InputActionsPerSecond每秒允许持续输入的数量
  • RepeatDelay触发持续输入生效的时间,持续摁下多久才会被认为是持续输入

RectTransform 继承了Transform每个UI组件上都有

Anchor锚点,默认为(0.5,0.5),PosX和PosY的距离是中心点Pivot相对于锚点Anchor的距离,左边的更改也是更改锚点的预设。
当我们进行分辨率位置自适应的时候,是以Anchor为坐标原点计算的。当Anchor被分开是一个范围的时候,LeftTopRightBottom是UI元素相对于父元素的margin距离,会跟随父矩形拉伸而拉伸,一般只有背景图对齐的时候才会用。
使用快捷设置的时候,按住Shift点击鼠标左键可以同时设置轴心点(相对自身矩形);按住Alt点击鼠标左键可以同时设置位置。
挂载到UI元素上的脚本使用this.transform as RectTransform 使用里氏替换原则父类装子类,类型转换一下就可以了。

UI控件主要学习三个事情:UI控件使用,事件响应,分辨率自适应(大小 位置)
做UI其实就三件事,数据准备->拼界面->写逻辑
在UI中一般都是用一个单独的根对象避免和其他的对象混合管理增加管理难度,命名最好有规范,比如btnBegine一看就知道这是啥,写C#写多了直接大驼峰罢
先分层,业务逻辑和响应解耦

控件

Panel 此组件就挂载了一个Image,基本上就是分组用的,可以设置遮罩射线等。
Image 参数注意 RaycastTarget是否响应射线检测响应,会拦截射线后面的UI元素没有响应了。ImageType使用Sliced最好是在SpriteEditor中先规定九宫格的线,PixelsPerUnitMultiplier可以设置像素对应大小。Tiled经常会用来做填充。Filled经常用来做CD缓冲或者血条效果。

Text 字体过大RectTransform太小会直接消失,BestFit会自动调整字体大小,可以规定字体最大最小。注意 经常出现Text的RectTransform挡住按钮的情况,出问题检查一下。边缘线Outline和阴影Shadow组件可以添加效果。2020某版本后TMP是UGUI默认使用的文本组件

RawImage 一般用作大图,不需要打图集的图片,Texture支持的格式比较多。UVRect是控制图的偏移

Button 挂了一个Button和Image,子对象是Text,如果按钮的图片上已经有字了可以直接是删除Text子对象。修改button的图一般是直接拿Button的Image改Sprite
按钮的点击必须是在按钮内点击并抬起算一次。注意参数

  • Navigation相当于是否参与在元素间移动焦点,点击下面的Visualize可以在场景窗口查看导航连线。
  • Transition
    • TragetGraphic控制的使用哪张图片作为按钮图片,
    • ColorHint剩余参数基本都是控制的是按钮的过渡颜色(选中,焦点,禁用)
    • SpriteSwap就是每种状态使用不同的Sprite
    • Animation没每种状态播放不同的动画,直接AutoGenerateAnimation生成一个状态机,事件也自动绑定,剩下的State绑定AnimationClip就好了

Toggle 单选,多选控件。BackGround是未勾选的图片,勾选的图片是它的子对象CheckMark控制,更改这两个对象上的Image组件就可以。
用一个空对象(任何对对象都可以)挂载ToggleGroup,在Toggle的Group选择此Group可以达到在Group内单选的效果。

InputField 输入框,下面的对象Placeholder和Text,用来提示输入和存储输入的Text

  • TextComponent和Placeholder关联了子对象Placeholder和Text
  • 先看本体挂载的TMP
    • ContentType可以限制输入的东西,比如限制只能输入整数小数密码等等,LineType是不是允许多行等设置。
    • CaretBlinkRate CaretWidth CustomCaretColor光标闪烁频率,宽度,光标颜色

Slider 滑动条下面的子对象有Background背景,Fill填充,Handle滑块 分别挂载了一个image组件组成的。分别绑定了连个React

  • Direction 可以设置滑动条的方向
  • WholeNumbers只能选整数

ScrollBar 一般不单独用,会和ScrollView配合使用。自身挂载的Image是后面图片的背景,Handle是滑块的图。

  • NumberOfSteps 可以改变这个滑块为多少次的滑动而不是平滑的值,0就是平滑滑动
  • Size滑块大小 ScrollView 滚动视图,但挂载组件名字是ScrollRect。子组件由Viewport,ScrollbarHorizontal,ScrollbarVertical三部分组成,后两个是俩ScrollBar。Viewport和ScrollView都挂载了一个Image。
  • Content 默认绑定你在Viewport下的Content,是所有要显示内容的父对象。Viewport多大显示的内容范围就有多大。
  • MovementType能不能拖动,会不会回弹,Elasticity使用回弹的时候控制回弹的系数,越大回弹越慢
  • Inertia使用惯性,DecelerationRate使用惯性拖动完之后会像有惯性一样移动一小点距离
  • ScrollSensitivity 滚动快慢系数
  • 一般 人物装备只能单横或者竖,地图可以左右随便拖
  • 如果不需要横或者竖向滚动,删除对应的Scrollbar后,记得绑定的地方Missing置为None,否则可能出bug Dropdown 下拉列表由一个Label,Arrow,和Template(ScrollView)模板组成,调整模板样式,会自动根据模板样式和Options的内容生成下拉Item。自己挂载的Image可以修改背景图,修改Arrow的Image可以修改箭头,Lable的Text是在Options中规定的
  • Template关联的下拉列表,这下拉列表只负责展示,真正存储内容是在Options中的文本和图片,点击的item传对应的index value,可以通过代码获取value
  • CaptionText 关联的显示Label
  • CaptionImage 也可以关联显示图片
  • ItemText ItemImage 选中时获取当前所选选项的哪个子对象
  • value 当前选项的索引值,和下面的Options列表配合使用。
  • AlphaFadeSpeed 下拉表显示隐藏时,淡入淡出速度

事件接口

可以让组件添加自定义的事件监听,比如给文本控件额外添加点击双击等。UGUI中提供了常用的事件接口。挂载的脚本类需要继承指定接口然后类中实现指定的方法,挂载的控件会自动调用,事件检测

  • PointerEnterHandler接口实现函数OnPointerEnter - 当指针进入对象时调用 (鼠标进入)移动设备上不存在
  • IPointerExitHandler接口实现函数OnPointerExit - 当指针退出对象时调用 (鼠标离开)移动设备上不存在
  • IPointerDownHandler接口实现函数OnPointerDown - 在对象上按下指针时调用 (按下)
  • IPointerUpHandler接口实现函数OnPointerUp - 松开指针时调用(在指针正在点击的游戏对象上调用)(抬起)
  • IPointerClickHandler接口实现函数OnPointerClick - 在同一对象上按下再松开指针时调用 (点击)
  • IBeginDragHandler接口实现函数OnBeginDrag - 即将开始拖动时在拖动对象上调用 (开始拖拽)
  • IDragHandler接口实现函数OnDrag - 发生拖动时在拖动对象上调用 (拖拽中)
  • IEndDragHandler接口实现函数OnEndDrag - 拖动完成时在拖动对象上调用 (结束拖拽)
    携带的参数是继承BaseEventData的PointerEventData注意的参数看代码。
    使用组件EventTrigger挂载到组件上,把需要回调的函数统一声明在Panel的脚本中,拖进去选择自己想要回调的函数,同样能达到效果。但是参数是BaseEventData类型直接as类型转换一下就行。(也可以用代码但是我感觉会比较麻烦不写了
Click to see more

控件和事件API演示

public class Test1 : MonoBehaviour,IPointerClickHandler,IPointerEnterHandler,IPointerExitHandler // 继承事件接口
{
    void Start()
    {
        //Image
        Image image = GetComponent<Image>();  
        image.sprite = Resources.Load<Sprite>("t1");  //  从Resources中加载资源替换掉sprite
        RectTransform rectTrans = this.transform as RectTransform;//  点进去看比transform多了多少属性  
        rectTrans.sizeDelta = new Vector2(80, rectTrans.sizeDelta.y); // 不能直接修改某一个属性,只能一次性赋值  
    
        // Text
        TextMeshProUGUI t = GetComponent<TextMeshProUGUI>();  
        t.text = "test";  
    
        // Button
        Button btn = GetComponent<Button>();  
        //  也可以RemoveListener(FunctionName)  
        //  RemoveAllListener() 无法移除Editor中拖拽的  
        btn.onClick.AddListener(() =>  //  委托绑定
        {  
            Debug.Log("click!");  
        });
    
        // toggle
        Toggle tog = GetComponent<Toggle>();
        tog.isOn = true; //  toggle是否被选中
        tog.onValueChanged.AddListener((bool on)=> // 绑定事件响应
        {
            Debug.Log($"toggle changed to {on}");
        }); 
    
        //  toggleGroup
        ToggleGroup g = GetComponent<ToggleGroup>();
        g.allowSwitchOff = false;
        foreach (Toggle activeToggle in g.ActiveToggles()) //  获得备选中toggle
        {
            Text text = activeToggle.GetComponentInChildren<Text>();
            Debug.Log($" toggle {activeToggle.name} label {text.text} on is {activeToggle.isOn}");
        }
    
        // inputField
        InputField inf = this.GetComponent<InputField>();
        print(inf.text);
        inf.text ="112222222";
        inf.onValueChanged.AddListener(delegate { print(inf.text); }); // 值改变的时候获取内容
        inf.onEndEdit.AddListener(delegate { print($"结束输入 {inf.text}"); }); //  按回车结束输入的时候
    
        // Slider
        Slider sdr = this.GetComponent<Slider>();
        print(sdr.value); // 获取slider当前的值
        sdr.value = 0.5f; //  初始化一个值
        sdr.onValueChanged.AddListener((v) => { print(v); }); // 接收一个float
    
        // ScrollBar  
        Scrollbar scb = this.GetComponent<Scrollbar>();  
        scb.value = 1.0f;  
        scb.size = 0.2f;  
        scb.onValueChanged.AddListener((v) => { scb.value = v; }); // 监听事件
        
        // ScrollView
        ScrollRect srr = this.GetComponent<ScrollRect>();  
        srr.content.sizeDelta = new Vector2(200, 2/00); // 改变content大小,可以拖多少都是根绝它来  
        srr.normalizedPosition = new Vector2(0, 0.5f); // 不需要计算横着没拖,竖着拖了一半的效果  
        srr.onValueChanged.AddListener((xypos)=>{Debug.Log(xypos);}); // 传入的是Vector2,表示横x竖y拖动到了那个地方(百分比
    
        // Dropdown  
        Dropdown dd = GetComponent<Dropdown>();  
        print(dd.value); // 当前选中了哪个  
        print(dd.options[dd.value].text); // 用options获取当前选中的元素  
        dd.options.Add(new Dropdown.OptionData() { text = "OP" }); // 通过代码去添加一个选项。可以传图片文字等  
        dd.onValueChanged.AddListener((int value) => { Debug.Log($"current choose index {value}"); });
    }
    //  事件接口
    public void OnPointerClick(PointerEventData eventData)
    {
        print($"pointer info : " +
              $"{eventData.position} " + //  屏幕座标系鼠标的位置
              $"{eventData.pointerId} " + //  左中右哪个键点击
              $"{eventData.pressPosition} " + // 屏幕座标系鼠标点击的位置
              $"{eventData.delta} " + // 拖动增量
              $"{eventData.clickTime} " + // 点击时系统时间
              $"{eventData.clickCount}"); //  点了几次
        print(eventData.pressEventCamera); // 最后一个点击事件关联的摄像机
        print(eventData.enterEventCamera); // 最后一个鼠标进入事件关联的摄像机
        Debug.Log("pointer click");
    }

    public void OnPointerEnter(PointerEventData eventData)
    {
        Debug.Log("pointer enter");
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        Debug.Log("pointer leave");
    }
}

#todo UGUI P16 P30 P32,有时间P25也做完,再有时间P27也做了 #todo UGUI P24 15min scroll三种模式,content和viewport的关系

零散知识点

图集 设置详细用法在基础SpriteAltas中,UGUI可以不打就能用,NGUI需要先打图集才能用Edit->PorjectSettings->Editor->SpritePacker,Enabled是运行和构建都打,EnabledForBuilds是只构建打。
创建一个SpriteAltas,因为是UI图集取消AllowRotation和TightPacking。相同图集的图堆叠,只会有一次DrawCall但是如果如果AABA这样堆叠打断了批处理,这样就多2次DrawCall,Text等也会打断,插入到中间且重叠会增加DrawCall

坐标转换 使用这函数可以制作摇杆RectTransformUtility.ScreenPointToLocalPointInRectangle,此函数的作用是把屏幕上的点转换为以某元素为原点的点,下例

Click to see more

制作摇杆


遮罩 一般是和Image配合使用,在父对象上挂载Mask组件,想要被遮罩的Image勾上Maskable,子UI对象都会被遮罩。此时带Mask对象的Image组件关联的图片,透明的地方被遮罩,不透明的地方显示。
注意: RectMask是通过RectTransform矩形直接判断停留在应用阶段,消耗低但是只能遮罩RectTransform的矩形部分

异形按钮 不规则的图片。

  • 按钮的Image子对象RectTransform覆盖的区域会把事件传递给父按钮组件。于是我们可以创建一个小的透明的Buttion,让不规则的图完全遮住他,把TargetGraphic关联到需要显示按钮图片的Sprite,然后再在这个按钮下创建多个Image拼出不太精确的此图片的轮廓。节省内存,笨但好用
  • 使用透明度阈值检测,Button修改Sprite,把Sprite的ReadWriteEnable设为打开;设置这个Button->Image组件的img.alphaHitTestMinimumThreshold = 0.1f; 效果就是此图片透明通道必须有值的才能响应检测,而且不会拦截射线

自动布局空间

CanvasGroup 组件,给Panle控件挂载可以用来整体淡入淡出和禁用,主要参数Interactable是否启用不影响射线拦截 BlocksRaycasts 进行射线检测,如果不进行射线检测,那么如果后面有被遮挡的UI就可以接收到。IgnoreParentGroups忽略父级的CanvasGroup对自己的影响。

在UI元素的Inspector中,切换到LayoutProperties,里面的属性就是自动布局使用的属性不懂的问问GPT,其实不太重要,知道有影响就行。主要有两个VerticalLayoutGroupHorizontalLayoutGroup组件参数差不多。

  • padding盒模型不说了,Spacing 子元素间距,ChildAlignment 对齐方式
  • ControlChildSize自动缩放子对象以充满父对象的空间
  • UseChildScale如果不考虑子对象的缩放,一般子对象不会缩放知道就行。
  • ChildForceExpand平均分割空间每个空间固定填充一个元素
  • 在子元素上添加LayoutElement组件可以规定元素跟随缩放的最小最大值
    • 缩放到比Min小or比Preferred的大的时候元素不会继续跟随缩放
      GridLayoutGroup是网格布局组件,说点和上面两个参数不一样的。
  • StartCorner 从哪个角开始排列
  • StartAxis 沿哪个轴排列
  • ChildAlignment 九宫格对齐方式
  • Constraint默认自适应,可以手动规定多少行和列 ContentSizeFitter 内容大小自适应组件,自动调整RectTransform大小适应,一般在Text或者配合其他组件使用,比如搭配ScrollContentView固定宽度然后根据物品多少自动设置高度
  • 默认Unconstrained不调整。
  • Min使用LayoutPorperties中的min宽度
  • Preferred Size 使用LayoutPorperties偏好宽度
    AspectRatioFitter这个了解就行,可以让元素自己根据宽或者高调整大小,可以自定义宽高比;也可以直接适应父对象。

📝Note

UI经验总结

UI动态生成的思路。一个经典的权衡问题,提示框弹出来的时候,用代码控制是一个按钮还是两个按钮,如果是一个按钮就居中两个按钮就排布;或者做两个面板,分别调用。
如果使用动态生成的思路,应该写一个按钮栏父对象+HorizontalLayout,然后写一个按钮类,传入指定的文字图片Action返回按钮Object挂到按钮栏上。


UnityEditor

Uinty自带的编辑器的UI,可以凑活用,做地图技能编辑器什么的。

这里面的API就是在街面上点击按钮操作的API,

EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup group, BuildTarget target); Editor中可以切换平台相当于File->BuildSettings->Platform->Android->SwitchPlatform。

#todo 没时间了,先跟着做用到啥算啥,有时间跟着这个教程open in new window摸个够用就行

odin编辑器UI

广受好评的编辑器UI框架,有时间再摸


常用插件

📌Tip

系统自带的字体
Win: C:\Windows\Fonts
Mac: 系统内置只读字体 /System/Library/Fonts 系统用户共享字体/Library/Fonts 用户字体~/Library/Fonts
Unity支持ttfotf(TrueType字体和OpenType字体)

TMP

使用 Text Life 免费开源文字动画框架_哔哩哔哩_bilibiliopen in new window
GitHub - kuronekoyang/ReflectionTool: C# Reflection Wrapper Generation & Method Hooking Tools For Unityopen in new window

使用静态字符集设置Fallback为动态字体,这样在性能和字符之间找到一个平衡。

过场景的时候单例MonoBehaviour的静态变量也是不会销毁的,在OnDestroy的时候赋值instance = null;销毁

位图字体

文字动画

TextLifeopen in new window

DoTween

UI动画插件

NodeCanvas

对话系统怪物AI的插件

EasySave

用于更方便持久话数据的插件,配合牢唐的持久化的几个封装好的使用很不错。

Cinemachine

摄像机运动的插件

资源和热更

  • Resources不能热更,对资源的更新需要重新打包
  • AB可以热更,不需要对整个游戏重新打包,一般是对非代码资源的热更
  • HybridCLR或者LUA是对脚本的热更

AssetBundle

Unity笔记热更Part1:关于AB包与热更open in new window
这个人open in new window写的都不错。

手游或者需要联网热更的游戏一般不使用Resouce.Load,AB包就是干这个的,AB包不仅可以在本地还可以在网络上远程加载。文档可以花小半天先看一遍open in new window,更好理解在干什么虽然感觉翻译的有点依托。
目前已经不在维护官方建议用Addressables替代,但是公司还在用所以要学。安装看git repoopen in new window版本太高报错就把里面的Test删掉。

没东西打包会报错,在资源的Inspector->底部AssetBundle,第一个是打指定包的包名,第二个是拓展名别动。三个页签栏
Configure: 查看所有待打包的文件,Inspect可以查看打好的文件的基本信息。
Build:

  • ClearFolder这次就会把上次打包的清了不清会攒垃圾但是能增量,CpoyToStreamingAssets就会把打好的包复制一份到StreamingAssets
  • Compress: 不压缩解压快体积大;LZMA体积最小解压最慢使用包中的一个资源会把这个包解压出来;LZ4用什么解压什么。
  • StrictMode:报错了就不打了

Build好的东西会放到AssetBundle(Assets同级)/{指定plantform文件夹}下,打包好的格式是 二进制文件+元信息文件(Assets.manifest),同时会生成一个文件夹同名的主包
同一AB包不能重复加载 否则报错,AB包异步加载完资源后会在下一帧渲染,卸载的时候如果选择不卸载资源,则只会卸载ab包但是已经实例化的资源仍旧会占内存
包间的依赖,某个包内的资源引用其他资源,他们都会被自动打包进这个包里。 如果手动指定了不同包,引用不到了会报错 需要把依赖的ab包先加载了或者把他俩移动到同一个ab包里。
利用主包可以获取包间依赖,挨个加载就是了,他没法获得某个资源依赖了几个包,只能从包级别获取,并且会自动递归返回完整的依赖列表,不需要手动处理多层包引用。
但是 注意AB包不会自动处理循环依赖! A依赖了B,B依赖了C,AC在ab1中,B在ab2中;这样就循环依赖了会报错

Click to see more

AssetBundle基本使用

public class AssetBundleTest : MonoBehaviour  
{  
    private AssetBundle ab;  
    void Start()  
    {        
        //  同步加载  
        ab = AssetBundle.LoadFromFile(Application.streamingAssetsPath+"/"+"testab");  
        AudioClip musica = ab.LoadAsset<AudioClip>("伍佰 & China Blue - 你是我的花朵"); // 使用泛型加载,如果使用名字加载可能会出现同名不同类型的冲突  
        musica = ab.LoadAsset("伍佰 & China Blue - 你是我的花朵",typeof(AudioClip)) as AudioClip; // 使用反射加载,用于在lua中不支持泛型的时候使用  
        AudioSource aus = this.GetComponent<AudioSource>();  
        aus.clip = musica;  
        aus.Play();  
        //  异步加载  
        StartCoroutine(LoadABAsync("testabasync", "Cube"));  
        // 包间依赖处理
        GameObject sph = ab.LoadAsset<GameObject>("Sphere");
        //  使用主包获取依赖先加载,不然就会丢失材质洋红色
        AssetBundle abMain = AssetBundle.LoadFromFile(Application.streamingAssetsPath+"/"+"PC"); // 加载主包
        AssetBundleManifest abManifest = abMain.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
        string[] strs = abManifest.GetAllDependencies("testab");
        for (int i = 0; i < strs.Length; i++)
        {
            AssetBundle.LoadFromFile(Application.streamingAssetsPath+"/"+strs[i]);
        }
        GameObject.Instantiate(sph); // 这样应该就不是红色的
  
    }  
    IEnumerator LoadABAsync(string abname,string resname)  
    {        
        // 异步加载包  
        AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/" + abname);  
        yield return abcr;  
        AssetBundleRequest abq = abcr.assetBundle.LoadAssetAsync(resname,typeof(GameObject)); // 加载资源,我估计后面正式用类型也得一块传进来  
        yield return abq;  
  
        GameObject.Instantiate(abq.asset as GameObject); //  把这东西拿出来  
    }  
  
    IEnumerator UnloadAB(AssetBundle ab_)  
    {        
        Debug.Log("卸载此包");  
        ab_.Unload(true); //  卸载指定的ab包  
        AssetBundle.UnloadAllAssetBundles(false); // 参数是卸载的时候是否把当前使用的资产也卸掉  
        yield break;  
    }  
    void Update()  
    {        if (Input.anyKeyDown)  
        {            
            Debug.Log("检测键盘");  
            StartCoroutine(UnloadAB(ab));    
        }  
    }  
}

Addressables

基于AB包做的热更进一步封装,建议别跳过AB包因为公司用啥我用啥。

YooAsset

很流行的一个资源热更的插件

Xlua

lua热更就是三点 几个Lua热更新框架差异 - Bob的博客open in new window

  1. C# 与 Lua 的互相调用机制
  2. Lua 文件的打包
  3. 网络传输校验 据说新立项的都在用hybirdclr。 github把项目复制下来,我使用的是M1的macos,需要编译出bundle文件,替换掉Plugins下面的xlua.bundle。

交互和交互细节 luabehavior 哪些地方用点哪些地方用冒号调用是有说法的。 lua用起来有点阴间的

HybridCLR

这位也是做代码热更新的。 Unity大厂面试题xLua ILRuntime HybirdCLR热更方案比较open in new window
一小时极速掌握HybridCLR热更新(上)open in new window
一小时极速掌握HybridCLR热更新(下)open in new window

基础优化

对象池

MVC

跨平台打包

跨平台打包和SDK接入

游戏常见算法

非一般的算法,是游戏领域常见的算法,有些是引擎组件的原理有些是特殊的功能

KD树

最近邻

四叉树

碰撞检测

八叉树

服务端地图推送性能优化

A星寻路

此为Unity内置的寻路的算法原理

boids算法

生物集群模拟



Last Edit: 2025-07-21 00:02:10
Loading...