Unity-进阶知识

ooowl
  • 游戏开发
  • Unity
About 45 min

Unity-进阶知识

零碎

黑话

  • 微端: 指很小的客户端通常是十几,几十MB,下载就开玩,边玩边下载即时用到的资源(这个概念不是特别严谨的定义)。
  • 首包: 类似于LOL那种,下了10G就可以玩,高模资源等在后台下载。
  • 常见黑话可能对于行业内的属于常识了[1]
不装逼会死吗
游戏类型
AVG文字冒险游戏Adventure Game
SLG模拟策略游戏Simulation Game
RTS及时战略游戏Real Time Strategy Game
MMO大型多人在线游戏Massive Multiplayer Online Game
MOBA多人在线战术竞技游戏Multiplayer Online Battle Area
TBS回合制策略游戏Turn Based Strategy Game
FTG格斗游戏Fighting Game
STG射击游戏Shooting Game
PZL益智游戏Puzzle Game
RCG竞速游戏Racing Game
SPT体育游戏Sports Game
CAG卡牌游戏Card Game
TAB桌面游戏Table Game
MSC音乐游戏Music Game
游戏内名词
PVE玩家VS AI敌人 Player VS Environment
PVP玩家VS玩家 Player VS Player
HOT指持续性治疗效果 Heal Over Time
DOT持续性伤害效果 Damage Over Time
AOE法术场效果 Area of effect
AFK脱手操作,放置型 Away from keyboard
DD直接伤害,非持续性伤害 Direct Damage
DPS每秒造成伤害 Damage per second
BUFF主要指辅助类角色为别人施加的有益状态,通俗的说法就是“加状态”
NPCNon Player Character(非玩家角色)
运营术语
ACUAverage Concurrent Users24小时内每小时平均在线用户
DAUDaily Active User日活跃用户量
PCUPeek Concurrent Users24小时内同时最高在线人数
DNUDaily New User单日新增用户
URRUsers Retention Ratio用户留存率
ARPUAverage Revenue Per User平均每个用户产生的收入
ARPPUAverage Revenue Per Paying User平均每个付费用户产生的收入
DCRDaily Churn Rate日流失率
CPContent Provider内容提供商
SPServer Provider服务提供商
QAQuality Assesment质量测试
QCQuality Control质量控制
CBClose Beta封闭删档测试
OBOpen Beta开放不删档测试
国产原创黑话
大中小R什么都充,活动充,新手礼包充
洗人把玩家从某个游戏拉到另一个游戏
马甲包换皮,稍微改一点点美术变成另一个游戏
冲榜冲击各种游戏榜单
起量流量逐渐上升
爆量流量短期突然上升
买量通过花钱打广告获得流量和用户
换量不用应用内的广告相互推广,我的点进你的,你的进我的
导量通过渠道把用户引入游戏内
卖量渠道把流量卖给应用商
七大类广电总局规定的,七种审核类型。“不涉及政治、军事、民族、宗教等题材内容,
办理周期短且无故事情节或者情节简单的
消除类、跑酷类、飞行类、棋牌类、解谜类、体育类、音乐舞蹈类
互联网通用黑话这个随便看看,反正遍地是
复盘回忆总结之前的工作
闭环一套成体系自洽的方案
脑暴头脑风暴,点子idea
痒点没有也还行,有更好的东西
痛点没有他不太行,但他还真没有的东西
爽点成功给到了他想要的东西
差异化要不同有区别
底层逻辑深层原理
对/碰讨论
对齐意见统一
生态某个领域圈子
颗粒度细致程度
引爆点突然让人关注的地方
背书找有名的人,机构做担保,推荐

指标术语

项目设置

包管理的几种方式 #todo 包管理啊嗯

  • manifest.json定义自动安装
  • Window->PackageManager
  • DLL文件放到Assets/Plugins
  • 直接源码扔Assets 补充注意事项
  • 在dotnet中可以直接使用nuget安装,但是untiy不直接使用dotent的包管理器。两种方式,将对应的DLL文件放到Assets/Plugins下面;或者在Window->PackageManagerAdd package from git URL填入对应的地址。
    • 这里调用的git不会使用shell里的代理,必须git config单独设置代理
  • 自定义package源Edit->ProjectSetting->PackageManager 按照人家给的软件源注册进去,Window->PackageManager->选择repo
  • Window->PackageManager->AddPackageByName可以类似于manifest.json一样引入包
    • 有时PackageManager或者manifest.json装了,但 Unity 并没有生成 .dll 到Library里,在 Unity 编辑器里 Assets->ReimportAll或者删除Library重启 .
  • NuGetForUnityopen in new window照着README安,tools上会出现Nuget栏,后面就可以愉快的使用了。
  • openupmopen in new window是Nodejs的一个库,可以用来安装Unity的包npm install -g openupm-cli就可以使用openupm的命令了
  • 当有A文件编译错误的时候,整个untiy都是未编译的状态,这时用unitypackage导入包B,可能根本没有到编译那一步看起来就和没导入一样
  • 当出现死循环的时候整个unity会卡主,IDE中断点只能断到死循环附近不定那一行,单步执行不定哪一步就进不去了
常用预处理器指令(并非常用)
常用预处理器指令描述
UNITY_EDITOR脚本符号,用于从游戏代码中调用 Unity 编辑器脚本。
UNITY_EDITOR_WINWindows 上编辑器代码的脚本符号。
UNITY_EDITOR_OSXMac OS X 上编辑器代码的脚本符号。
UNITY_EDITOR_LINUXLinux 上编辑器代码的脚本符号。
UNITY_STANDALONE_OSX脚本符号,用于编译或执行专门针对 Mac OS X 的代码(包括通用、PPC 和英特尔架构)。
UNITY_STANDALONE_WIN脚本符号,用于专门为 Windows 独立应用程序编译/执行代码。
UNITY_STANDALONE_LINUX脚本符号,用于专门为 Linux 独立应用程序编译/执行代码。
UNITY_STANDALONE脚本符号,用于为任何独立平台(Mac OS X、Windows 或 Linux)编译/执行代码。
UNITY_WII用于编译/执行 Wii 控制台代码的脚本符号。
UNITY_IOS用于为 iOS 平台编译/执行代码的脚本符号。
UNITY_IPHONE已弃用,改用 UNITY_IOS。
UNITY_ANDROID安卓平台的脚本符号。
UNITY_LUMINMagic Leap OS 平台的脚本符号。您也可以使用 PLATFORM_LUMIN。
UNITY_TIZENTizen 平台的脚本符号。
UNITY_TVOSApple TV 平台的脚本符号。
UNITY_WSA通用 Windows 平台的脚本符号。此外,在针对 .NET Core 编译 C# 文件并使用 .NET 脚本后端时,将定义 NETFX_CORE。
UNITY_WSA_10_0通用 Windows 平台的脚本符号。此外,在针对 .NET Core 编译 C# 文件时,还会定义 WINDOWS_UWP。
UNITY_WEBGLWebGL 的脚本符号。
UNITY_FACEBOOKFacebook 平台的脚本符号(WebGL 或 Windows 独立)。
UNITY_ANALYTICS用于从游戏代码调用 Unity 分析方法的脚本符号,版本 5.2 及更高版本。
UNITY_ASSERTIONS断言控制过程的脚本符号。
UNITY_6464 位平台的脚本符号。

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 使用里氏替换原则父类装子类,类型转换一下就可以了。

控件

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

RawImage 一般用作大图,不需要打图集的图片,Texture支持的格式比较多。UVRect是控制图的偏移,只展示图性能消耗小

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

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类型转换一下就行。(也可以用代码但是我感觉会比较麻烦不写了
控件和事件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,此函数的作用是把屏幕上的点转换为以某元素为原点的点,下例

制作摇杆

遮罩 一般是和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这个了解就行,可以让元素自己根据宽或者高调整大小,可以自定义宽高比;也可以直接适应父对象。

源码简单解析

UGUI源码深度剖析_哔哩哔哩_bilibiliopen in new window

ChatGPTopen in new window

常见Case实现

ViewContent下拉,在Content中使用ContentSizeFitter+LayoutGrop/GrideLayout动态下拉和伸缩 对照目录查漏补缺Fetching Title#hno5open in new window


UI经验总结

📝Note

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

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


UnityEditor

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

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

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

[MenuItem("Tools/MyCustom")] // 加在函数上创建自定义Editor菜单,至少有一个斜杠,只支持静态方法
EditorUtility.DisplayProgressBar("Title", "Message", progress); //  进度条,想要变化那就改变progress(float 0-1f)的值和 标题 信息
EditorUtility.ClearProgressBar();
EditorUtility.DisplayDialog("Title", "Message", "OK", "Cancel");


Object[] selectedAssets = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);// 获取在Project窗口中选中的资源
string assetPath = AssetDatabase.GetAssetPath(asset); // 获取 资源的路径 格式是 Asset/xxx/xxx
AssetDatabase.CopyAsset(assetPath, destinationPath); //  Unity有自带的API可以在Asset中复制文件
AssetDatabase.Refresh(); //  刷新一下Editor界面

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

odin编辑器UI

广受好评的编辑器UI框架,感觉完全是UnityEditor上位替代,有时间再摸 Unity使用Odin完成编辑器开发 【基础知识篇 第一节】中文 分组 颜色 按钮 条件_哔哩哔哩_bilibiliopen in new window


资源和热更

热更新实际上是无感下载,玩家正在玩着就在下载了,重启程序虚拟机或者重启游戏才生效,而不是正在运行着就把内存中的资源替换掉了。一般也不会让Rsource加载一半老的一半新的,这样就乱了。

  • 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中;这样就循环依赖了会报错

📌Tip

在打AB包的时候不能把场景和非场景资源打在同一个AssetBundle里,会报警告。
最佳实践场景单独一个包,其他资源随便分包

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));    
        }  
    }  
}

AB包热更新

客户端的AB包都放在两个路径,一个是streamingAssetsPath中的默认AB包资源(移动端只读),persistentDataPath有最新的(可读写)。在第一版发布的时候,会把AB包都打包进StreamingAssets,后续我可能在服务器上发布新资源,然后每次游戏启动检查对比。(我怎么感觉treamingAssetsPath和persistentDataPath设计的有点🦈臂

  • 不可能纯从streamingAssetsPath中读取,因为这样没法更新。
  • 可以只从persistentDataPath中读取,这样首次启动需要把streamingAssetsPath转存到persistentDataPath一次,浪费空间。
  • 优先读取persistentDataPath没有再找streamingAssetsPath,复杂但是不浪费。但是streamingAssetsPath中的AB包也落后的话那就会一直冗余在那。

之后再整理一下,现在先放这,看希佬的代码,争取的是把长风的AB支持热更新

其实是个数据下载对比的活。 #todo 获取文件MD5 生成资源描述文件 名字 大小 md5。上传服务器,下载描述文件,对比本地,是否转存,哪些删除哪些需要下载哪些不需要下载,注意persistentDataPath和streamingAssetsPath

AB包更新流程图

AB包框架设计

跟着长风学的,这设计真的有点东西。 #todo 把代码梳理出来,加载配置,资源划分,处理,写入二进制。加载AB,GC 资源管理 调用

Addressables

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

YooAsset

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

HybridCLR

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

Xlua

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

  1. C# 与 Lua 的互相调用机制
  2. Lua 文件的打包
  3. 网络传输校验 据说新立项的都在用hybirdclr。

如果是已有项目使用xlua,把官方Assets里面Plugins XLua两个文件夹粘贴进项目里,如果需要热补丁还需要Tools文件夹,纯lua项目不需要
github把官方项目复制下来,我使用的是M1的macos,需要编译出bundle文件,替换掉Plugins下面的xlua.bundle。

这是初始化XLua项目需要的目录,找时间看懂牢唐是怎么做的

Assets
├── Lua
│   ├── JsonUtility.lua # 封装的JSON序列化工具
│   ├── Main.lua # 入口文件
│   ├── Object.lua # 封装的OOP
│   └── SplitTools.lua # 字符串切分方法
└── Scripts
    ├── Main.cs # 游戏主入口
    └── ProjectBase
        ├── ABMgr.cs # AB包管理器
        ├── Base
        │   ├── BaseManager.cs # 
        │   ├── SingletonAutoMono.cs
        │   └── SingletonMono.cs
        └── LuaMgr.cs

Warning

Xlua和AB打包有冲突,需要先清Xlua的ClearGenerateCode再打包

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

xLua/Assets/XLua/Doc/XLua教程.md at master · Tencent/xLua · GitHubopen in new window
xLua/Assets/XLua/Doc/configure.md at master · Tencent/xLua · GitHubopen in new window


网络基础

看一眼最基本的使用收发请求判断响应等。 Unity 事件详解 - CatSevenMillion - 博客园open in new window
【Unity技术栈】《Unity3D网络游戏实战》笔记 | 聪头之家open in new window


基础优化

对象池

MVC

Model:负责增删查改获取界面上需要的数据
View:负责获取控件,更新控件信息,挂载到场景上
Controller:负责业务逻辑处理。 界面事件监听,触发数据更新,触发界面更新
MVC有几个变种,基本上M和V是一定要有的,中间的事件控制解耦可以用不同的方式去做。

MVP

MVC还是会把data传递到View里面,MVP会切断View和Model的耦合,让Presenter处理一切。

MVVM

类似于前端的双向绑定,让ViewModel和V进行双向数据绑定,更新VM等同于更新V。 umvvm 和 GitHub - vovgou/loxodon-framework: An MVVM & Databinding framework that can use C# and Lua to develop gamesopen in new window

MVE

用EventCenter事件中心来分发消息

PureMVC

性能监控

工具的基本使用。 UnityProfiler ProfileAnalyzer MemoryProfiler

日志

上不上报,性能影响等

单元测试

牢唐小框架

做独立游戏够用了,看的是某咩的plus版,唐门!

打包与跨平台

跨平台打包和SDK接入

游戏领域算法

非一般的算法,是游戏领域常见的算法,有些是引擎组件的原理有些是特殊的功能,除非瓶颈一般自己不手搓。

KD树

最近邻

四叉树

碰撞检测

八叉树

服务端地图推送性能优化

A星寻路

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

boids算法

生物集群模拟


常用插件

📌Tip

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

TMP

默认改字体很蠢,需要在软件里面改Editor\Data\Resources\PackageManager\BuiltInPackages\com.unity.ugui\Runtime\UI\Core\Text.cs找代码改。
使用TMP设置系统默认字体Edit->ProjectSettings->TextMeshPro->Settings->DefaultFontAsset选中你生成的字体这样全局就会有字体了

静态字符集,在Window-TextMeshPro->FontAssestCreater。用到静态字符集里没有的字会直接显示框。
使用动态字符集直接右键->TextMeshPro->CreateFontAssest,用到啥他自己会找,但是动态的性能会差一些。
TextMeshPro 的动态字体是通过图集的方式实现的,而它默认生成的图集是一张1024x1024的图, 集满了之后把没有的放到一个MissingCharacterList里,然后用方框代替。
找到生成的Font资产->Inspector->GenerationSettings->MultiAtlasTextures勾上他自己会使用多张图集。

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

位图字体

【保姆级教学】Unity TextMeshPro 位图字体(位图字体使用建议)_unity 位图字体-CSDN博客open in new window

文字动画

TextLifeopen in new window
【Unity】精灵资产——轻松实现文字中插图片_哔哩哔哩_bilibiliopen in new window

DoTween

📌Tip

补间动画
冷知识: UI 动画里常说的 补间动画(tween animation)其实是"tween"的直译
在传统动画里,动画师会先画好关键帧(Keyframe),比如第 1 帧和第 20 帧。但中间的 2~19 帧不可能都由主动画师亲自画,于是交给助理画“过渡帧”,这些过渡帧就叫 in-between frames,简称 tween。
中文翻译过来就是 补间(补充关键帧之间的内容)

UI动画插件,在UnityStore搜索Dotween,添加到我的资源;然后在Window->PackageManager->MyAsset->搜索Dotween,安装引入。 【Unity教程搬运】使用Unity UniTask异步等待实现流畅的硬币收集动画DOTween_哔哩哔哩_bilibiliopen in new window

Tools->Demigiant->DoTweenUtilityPanel->

NodeCanvas

可视化状态机

EasySave

用于更方便持久话数据的插件,配合牢唐的持久化的几个封装好的使用很不错。
依旧是安装找官网open in new window,先用学习版。从Window->EasySave3打开,Settings绝大部分需要改的你都能看懂,Types是支持保存的类型。AutoSave是挂载脚本到想要保存的物体上+场景单例ES3AutoSaveMgr管理,他会保存脚本组件和其他所有的属性。一般OnApplicationQuit里写保存Awake/Start的里写加载。 能够保存大多数的可序列化对象,自定义类型需要加Serializable。保存时同文件内键不允许重复,保存Object类型的时候必须要有EasySave3Manager
有了管理器我们就可以保存一些包含引用的对象,Load的时候实际上是先加载然后把这个GameObject赋值给引用,而不是赋值给现有的引用对象,所以可能引用现在持有的对象会被顶掉,如果找不到就会重新创建这个对象放上去。
如果想在原来的对象上加载数据应用更改,使用LoadInto方法。
场景上的对象和持久化的对象引用关系是EasySave3Manager保存的,所以切换场景的时候可能会丢失

Warning

游戏内的GameObject以及材质等,要慎用Load,一般是保存数据然后LoadInto到新对象上。对复杂对象只保存路径或 ID,不保存直接引用,运行时再实例化
每个UnityEngine.Object在保存时会分配一个内部引用ID,这个refID用来标识这个对象在内存中的唯一性。如果这个Key从A保存,上次加载的时候是加载到B身上,那么再次加载这个Key不显式传递被加载的引用,那么数据就会跑到B身上而不是A。
如果已经引用乱了找EasySave3Manager清空对应的引用

AutoSave用法
为指定游戏对象添加ES3AutoSave脚本,在面板上勾选想要保存的数据,EasySave3Manager会自动保存你勾选的东西。自动保存支持的种类很多。
自己规定的字段必须要声明序列化支持,想被Unity序列化但是不想被ES3序列化的使用ES3NonSerializable,想被ES3序列化但是不想被Unity序列化的使用[ES3Serializable]
在ES3窗口的AutoSave页中可以规定自动保存的全局设置,下面勾选自动保存的对象数据,不用一个一个挂载添加了。在一个Prefab上右键->EasySave3->EnableEasySaveForPrefabs这样对这个prefab的autosave操作就会应用到他所有的实例化对象上
先在AutoSave中把Save和Load设置为None不自动触发,代码中使用ES3AutoSaveMgr.Current.Save();(Load)控制全局的保存和加载。
利用这个我们可以做玩家自定义键盘位置的功能,对每个键盘点Transform进行AutoSave,下次打开加载。
Prefab的自动保存设置之后,子物体不能在Inspector中进行自己单独的配置,需要在AutoSave页的Scene中单独配置 Settings的行为
开启加密不会影响之前已经保存的文件,再次加载还是加载未加密的会报错。
在static中声明全局的ES3Settings,可以更改的东西和面板上差不多一一对应。
保存加载的时候可以传入Path参数,使用自己制定的ES3文件,这样就可以制作多存档了。LoadInto("key0","my/path/es3save.es3",GameObject),不同文件相当于不同的命名空间了,Key可以重复了。
操作文件夹的时候一般习惯推荐以/结尾
Types页 如果发现有类型不支持保存,去Types看一眼是不是没有默认配置上,配置某类型实际上会自动生成一段支持类型的代码,等他重新编译完就好了

ES3的基本API
class ES3SettingTest
{
    public static ES3Settings es3setting = new ES3Settings();

}

[Serializable]
public class Skill
{
    [ES3NonSerializable]
    public string skill_name;
    public int skill_atk;
}

public class TestES : MonoBehaviour
{
    public int hp = 10;
    public List<int> idlist = new List<int> { 1,2,3,4,5};
    public Dictionary<string, int> id_res = new Dictionary<string, int>();
    public List<Skill> skills = new List<Skill>();

    void Start()
    {
        ES3.DirectoryExists("my/path/");// 判断文件夹是不是存在
        ES3.DeleteDirectory("my/path/");// 文件夹 文件 Key都能删除
        ES3.FileExists("SaveFile.es3");// 判断整个存档是不是存在
        if (ES3.KeyExists("hp"))// 不传就是判断默认存档中的hp是不是存在
        {
            hp = ES3.Load<int>("hp",hp); // 泛型加载并给个默认值,非null和default的时候可以帮助省略泛型,直接推断
            //  也可以加载之后as强转
        }
        ES3AutoSaveMgr.Current.Save();
        ES3SettingTest.es3setting.encryptionType = ES3.EncryptionType.AES; //  和在面板上改一样,用的时候参数传递进去
        ES3.CopyFile("SaveFile.es3","backup/SaveFile.es3"); // 也支持CopyDirectory,用来备份存档
    }
    
    void OnApplicationQuit()
    {
        ES3.Save("hp",this.hp); // 任意类型都可以Save(key,object),很强
    }
}

Cinemachine

摄像机运动的插件

TimeLine

流畅的过场动画,Unity内置的插件。

源码简单解析

嵌入网页

直接浏览器打开网页的话使用Application.OpenURL(inputStr);
嵌入试了好几个插件,还是这个3D WebView for Android™ (Web Browser)好用AssetsStoreopen in new window,说是for Android实际上都能用,本质上来说就是系统webview套壳。
提供了Canvas和Image的的Prefab,支持交互支持全端,有示例场景,Prefab拖进去用就可以了,里面直接封好了一个RawImage。虽然资料不多但是不会的去翻文档问AI基本都能满足需求
这东西实际上打包出来非常大,包了个Chrome进去多了三四百兆

基础使用代码
public async void ShowPage(string kw)  
{  
    string url = $"https://www.baidu.com/s?wd=s{kw}";  
  
    introWebViewPrefab.gameObject.SetActive(true);  // 不知道为什么必须要有
    await introWebViewPrefab.WaitUntilInitialized();  //   必须await使用
    introWebViewPrefab.WebView.LoadUrl(url);  //  直接加载,可以和正常浏览器一样
}


Last Edit: 2025-09-20 17:10:07

  1. 来自b站up主隼流阿哲,节奏挺大已经销号了。 ↩︎

Loading...