
1. 先想清楚一件事Unity学习笔记的目录整理的到底是什么做Unity这些年见过太多人笔记记得不少真到用的时候一个都翻不出来。我自己也经历过这个阶段三四个文件夹里躺着几十个md命名从笔记1到新建文本文档(7)某个阴影闪烁的解决方案明明记过硬是搜了三遍关键词都没找着最后重新踩了一遍坑才想起来——那次之后我才下定决心把笔记重做一遍。这篇东西就是把我这套Unity学习笔记目录体系完整拆开讲一遍。它解决的核心问题很具体当你的Unity知识面从能跑个球扩展到渲染、UI、XR、小游戏发布、工程优化以后怎么让笔记变成能检索、能复用、能反复回看的知识资产而不是一堆死档案。适合谁看刚学Unity两三个月、开始觉得东西越来越多的新手做了两三年、知识点分散在各处但没体系的中间层还有准备系统补课、打算把零散经验沉淀成册的人。目录结构这块我会给一套可以直接抄的但更重要的是背后的分类逻辑因为每个人的项目方向不一样照搬结构不照搬逻辑三个月后又乱了。先说一个反常识的结论Unity笔记目录的第一层分类不该按知识模块分而该按我能用它干什么分。原因后面细说。2. 目录结构的分层设计为什么很多人第一层就分错了2.1 我试过的三种分类法最后留下了哪个最早我按官方文档的结构来分基础、脚本、渲染、物理、UI、网络。听起来很正规实际用起来是灾难。因为一个真实问题往往横跨好几个模块。比如微信小游戏里视频播放不出来它既沾渲染也沾平台适配还沾资源加载你按模块分这条笔记放哪都是错的最后就是随手扔一个地方再也找不到。第二种我试过按时间分2023-03、2023-04这样。优点是记录方便缺点是检索全靠猜时间而且同一个知识点会被拆到好几个月份里形成不了体系。第三种也是我最后留下来的是按使用场景/能力域分。核心区别是模块分类回答的是这是什么场景分类回答的是我遇到这类问题该去哪找。这个转变看着小实际影响很大。具体第一层我留了七块覆盖从入门到工程化的完整链路序号目录名收录范围典型条目00环境与工具安装、版本、编辑器、插件Unity Hub、版本选择、Tooltips插件10引擎基础生命周期、组件、脚本、特性Update顺序、宏定义、特性Attribute20渲染与图形Shader、材质、光照、阴影阴影问题、Renderer包围盒30UI与交互UGUI、点击、显隐、对话表情按钮点击范围、UI显隐方案40运行时系统摄像机、导航、物理、通信摄像机跟随、Navigation、串口50工程化与优化性能、混淆、打包、资源数据块大小、gameassembly.dll60跨平台与XR小游戏、Web、Pico4、MR微信小游戏、Cesium for Unity这套分类的好处是我遇到UI显隐用哪种方案这种问题脑子的第一反应是UI与交互直接进30。而MR切VR怎么搞进60。路径是一步到位的不需要先在脑子里做一次模块归类。2.2 顶层目录之后第二层怎么切顶层定了之后第二层我统一用问题域解决方案的方式命名而不是用知识点命名。举个对比不好的第二层UI显隐、按钮点击好的第二层UI显隐的三种做法对比、按钮点击范围扩大差别在哪前者是名词后者是一个带结论的短句。笔记的本质是我解决了什么问题用结论做标题检索的时候你搜的是问题匹配到的却是方案命中率高很多。这一点我是从代码的命名习惯里迁移过来的——函数名写GetVisible不如写IsPanelVisible一个道理。第三层才是真正的具体条目每一条笔记一个文件。文件名我强制要求带上日期前缀和技术关键词格式是20240612-UGUI-Button点击范围扩大.md。日期放前面是为了排序稳定技术关键词是为了模糊搜索。别看这个规范很土它救过我很多次——grep一个关键词按时间排序能直接看到自己对这个知识点的认知是怎么演进的。注意目录不要超过三层。我见过有人分到第五层结果每次存笔记都要想半天放哪最后干脆新建一个文件夹。超过三层的部分用标签解决不要用文件夹解决。3. 环境与工具层安装、版本、编辑器这三块最容易反复踩坑3.1 版本选择为什么我坚持把这条单独记一个大文件Unity的版本管理是个长期折磨人的问题。每年一个新的大版本LTS和Tech Stream两条线并行项目一旦定型就很难动版本。我见过团队因为用了非LTS版本升级时第三方插件集体不兼容光适配花了两周。我的笔记里专门有一份版本选择决策.md记录的是这种规则商业项目锁LTS因为LTS至少两年维护期bug修复有保障插件生态也跟得上。学习阶段可以跟最新Tech Stream能提前接触到新特性但要在笔记里标注此处API可能在下一个版本变动。同一个工程不要混装多个Unity版本尤其是用了Library缓存的情况下切版本容易出莫名其妙的问题。安装本身没什么好讲的Unity Hub走正规流程就行。真正值得记的是安装后的环境检查清单我列一下确认Editor版本号完整例如2022.3.x LTS的具体patch号记进笔记因为很多问题只在特定patch上出现。检查目标平台模块是否勾选Android/iOS/WebGL/Windows没勾的话后面打包报错会很难查。检查脚本编辑器绑定VS/Rider/VSCode外部工具没绑定的话双击脚本打不开。记录磁盘路径Unity工程路径带中文或空格会让某些构建工具出问题这个坑很经典。关于网上常说的中文版下载国际版下载之类我的建议是走官方渠道选对应语言包即可不要为了图快去装来路不明的整合包。工程一旦被污染排查成本远超省下的那几分钟。这条我写在了笔记最上面用加粗标注。3.2 编辑器与工程设置里的高频条目这一块看着琐碎但几乎每个项目都会碰到。我的笔记里固定在00-环境与工具下维护三份清单分辨率设置。这里面有个容易忽略的点Game视图的分辨率和实际构建出来的分辨率不是一回事而且UI Scale Mode选Constant Pixel Size还是Scale With Screen Size直接决定你在不同机型上的表现。我习惯把每个项目的分辨率方案连同一张真机截图一起存进笔记过半年回来看一眼就明白当时为什么这么定。宏定义Scripting Define Symbols。这是做平台差异化的核心手段比如给微信小游戏单独定义一个WX_MINIGAME用#if包住平台代码。我自己踩过的坑是宏定义写完后没切换平台代码不生效以为是自己代码写错了查了半天。所以笔记里我特意加了一条改完宏定义先切一次平台再编译。Tooltips插件这类辅助工具。有些项目会用Tooltips插件做运行时提示好处是不用改代码就能看到Inspector字段说明。但插件版本和Unity版本强绑定记的时候必须写清楚配套版本否则半年后升级Editor插件直接报错。4. 引擎基础层脚本生命周期、宏定义、特性这些基础其实最不基础4.1 脚本执行顺序和生命周期值得单独建一份速查表新人最常问的就是为什么我的代码不执行为什么Awake里拿不到另一个对象。这些问题的答案基本都在执行顺序里。我笔记里有一份表专门记Awake、OnEnable、Start、FixedUpdate、Update、LateUpdate、OnDisable、OnDestroy的执行时机和典型误用回调执行时机适合做什么常见误用Awake对象实例化后、Start前初始化自身引用拿其他对象的Start结果OnEnable每次启用时注册事件、重置状态重复注册导致事件多次触发Start第一帧Update前依赖其他对象的初始化在Start里做重计算拖慢首帧FixedUpdate固定时间步长物理相关在里面读输入导致丢帧LateUpdate所有Update后摄像机跟随在这里改物理状态这份表的价值不在于背而在于出问题时能三秒定位。比如摄像机跟随抖动这个问题答案基本就在LateUpdate这一行——摄像机跟随必须放LateUpdate否则会出现和主角同帧执行导致的抖动。这种问题→原因→修正位置的三段式记录比单纯抄官方文档有用得多。4.2 宏定义和特性Attribute我按用途归档[SerializeField]、[RequireComponent]、[ExecuteInEditMode]这些特性官方文档是按字母顺序排的但你实际用的时候是按场景想的我想在Inspector里显示一个私有字段、我想让这个组件强制依赖另一个组件。所以我笔记里的特性条目全部按用途重写比如想在Inspector调试私有变量 →[SerializeField]想让组件自动挂载依赖 →[RequireComponent(typeof(Rigidbody))]想让脚本在编辑模式下跑 →[ExecuteInEditMode]想自定义Inspector显示 → 配[CustomEditor]有个实操心得[ExecuteInEditMode]用起来很爽能在Scene视图实时看到效果但它会在编辑器里持续执行写不好会拖慢编辑器甚至导致崩溃。我现在只在必须实时预览的工具脚本上用业务脚本一律不用。这条也写在笔记里了属于自己踩过才知道的类型。5. 渲染与图形层阴影、包围盒、Shader笔记要记现象而不是概念5.1 阴影问题用现象描述当笔记标题Unity的阴影问题是个大类接缝、闪烁、漏光、锯齿、远处阴影丢失每一种原因都不一样。如果笔记标题写阴影原理你回头查的时候根本匹配不上。我的做法是用现象当标题阴影边缘锯齿严重怎么办两个物体接触处阴影出现闪烁条纹远处阴影突然消失阴影距离设置实时阴影和烘焙阴影交界处出现硬边每条笔记的结构固定是现象截图 排查路径 根因 参数改动记录。比如远处阴影消失根因通常是Quality设置里的Shadow Distance太小或者摄像机远裁剪面和阴影距离不匹配。我把当时的参数值一起记下来包括改了哪个数值、改前改后画面差异。参数值这东西不记一定会忘。5.2 Renderer包围盒这是个容易被忽略但很实用的点Renderer.bounds世界空间包围盒和Mesh.bounds局部空间包围盒的区别我当初也绕了很久。笔记里我用一个具体场景记录做相机取景或者动态加载剔除的时候需要判断物体是否在视野内这时用的是世界空间的Renderer.bounds因为它已经包含了Transform的缩放。而Mesh.bounds是模型本地的不受Transform影响。实操上有个坑SkinnedMeshRenderer的bounds是估算的不一定准确动画幅度大时会出现物体明明在视野内却被剔除掉的情况。解决办法是手动扩大bounds或者强制不剔除。这个我在做角色动画项目时踩过画面里角色突然消失查了半天才发现是剔除问题。笔记里我附了一段手动扩大bounds的代码// 手动扩大SkinnedMeshRenderer包围盒避免动画导致误剔除 void ExpandBounds(SkinnedMeshRenderer smr) { Bounds b smr.localBounds; b.Expand(0.5f); // 按需扩大别太夸张影响剔除效率 smr.localBounds b; }5.3 Shader笔记的归档方式Shader这块我单独在20-渲染与图形下开了一个子目录因为它的学习曲线和普通脚本完全不是一个量级。归档逻辑是按效果分不是按语法分描边、溶解消失、水面、卡通渲染各一个文件。有个具体需求热词里出现过——脚本控制逐渐消失。这种消失效果纯脚本能做改材质透明度但更稳的是Shader配合。我的笔记里记了两种方案对比方案实现方式优点缺点脚本改透明度改Material的Alpha简单、无需写Shader半透明排序问题、性能一般Shader溶解用噪声贴图裁剪效果好、可控需要写Shader、调试成本高脚本方案里要注意直接改material.color会创建材质实例批量对象时会有性能开销正确做法是用MaterialPropertyBlock。这个细节我在笔记里专门用红字标了出来因为它是那种不踩一次绝对想不到的点。6. UI与交互层显隐方案、点击范围、对话表情全是实战细节6.1 UI显隐SetActive、LocalScale、移出相机到底用哪个这是热词里出现频率极高的问题也是我在项目里反复纠结过的。三种做法的差异我整理成了笔记里的一张核心表方案原理性能开销适用场景注意事项SetActive(false)禁用GameObject有激活/禁用开销会触发OnEnable/OnDisable长期不用的面板频繁切换会有GC和事件重注册问题改LocalScale为0缩放隐藏开销小但仍在渲染管线里高频切换的小元素布局组件可能报错锚点会乱移出相机视野改位置开销最小极致性能场景逻辑上对象仍存在容易误触发我个人的结论写得很明确常规面板用SetActive高频小元素用LocalScale性能敏感的批量元素才考虑移出相机。原因在于SetActive会触发完整的启用/禁用生命周期如果你在OnEnable里注册了事件频繁开关会不断产生委托分配久了就是GC压力。这个分析过程我也记在笔记里了因为它是为什么层面的东西比结论本身更值钱。还有个细节如果把面板移出相机视野它上面的按钮理论上还是能通过代码触发点击的如果逻辑上有依赖不可见即不可点就得自己加状态判断。这个坑我在做弹窗系统时遇到过弹窗隐藏了但还能被点到排查了很久。6.2 按钮点击范围扩大不用改图片的几种做法UGUI默认的点击范围就是Image的矩形。想扩大点击范围常见做法有几种我笔记里记了三种并做了对比加一个透明的Image当父物体或子物体设置raycastTarget为true尺寸放大。简单直接但要注意它会拦截其他元素的点击父子层级和Canvas的排序要理清楚。用Image.alphaHitTestMinimumThreshold这个属性可以让点击检测只响应像素不透明区域反向用来做异形按钮。但要注意它需要把图片的Read/Write Enabled打开会增加内存。自己写一个实现IPointerClickHandler的组件在OnPointerClick外扩判断或者重写IsRaycastLocationValid来扩大有效区域。第三种最灵活我给了一段参考实现// 扩大按钮点击范围通过重写射线检测有效性 public class ExpandClickArea : MonoBehaviour, ICanvasRaycastFilter { public Vector2 padding new Vector2(20f, 20f); public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera) { RectTransform rt transform as RectTransform; Vector2 local; RectTransformUtility.ScreenPointToLocalPointInRectangle( rt, sp, eventCamera, out local); Rect r rt.rect; r.xMin - padding.x; r.xMax padding.x; r.yMin - padding.y; r.yMax padding.y; return r.Contains(local); } }这段代码的好处是不改动原有UI结构加个组件就能扩范围但要注意它只能扩展本元素的有效区域如果外层有遮挡元素还是会被挡。这个先后关系必须搞清楚否则会出现我明明扩了范围还是不响应的情况。6.3 对话与表情表现层笔记的组织方式热词里有个根据对话变化表情这是个典型的剧情系统需求。我在笔记里把它归到30-UI与交互下的表现层条目记录的是数据驱动思路对话数据结构里带一个表情字段播放时根据字段切换Sprite或者切换Animator状态。核心是把文案和表现解耦不要让每句话都硬编码表情。同一个目录下还有Timeline相关的笔记。Timeline做剧情演出很方便但有个经典问题Timeline播放时如果对象被销毁或者被其他逻辑改动轨道会报错。我的笔记里记录了一条预防措施——Timeline播放前先确认它要控制的对象的生命周期必要时用PlayableDirector的信号Signal机制在关键帧回调而不是让轨道直接控制对象属性。7. 运行时系统与工程化摄像机、导航、优化、混淆7.1 摄像机跟随几种写法对应的场景摄像机跟随看着简单实际有细分。我的笔记里记了三种硬跟随每帧同步位置最简单但画面会有生硬感。平滑跟随Lerp/SmoothDamp用插值让镜头有延迟感适合大多数第三人称。注意必须放LateUpdate。带死区的跟随角色在屏幕中间一个小范围内移动时镜头不动超出才跟适合横版或者2.5D。SmoothDamp比Lerp更适合跟随因为Lerp在帧率变化时表现不稳定而SmoothDamp是基于时间步长的。这个区别我写在笔记里配了一小段对比说明。还有个大坑摄像机跟随和物理、动画同时作用时会出现抖动通常是更新顺序问题解决办法是把跟随时机放到LateUpdate或者用FixedUpdate配合插值。7.2 Navigation导航烘焙和动态障碍Unity自带的Navigation系统笔记里记了几个实用点NavMesh烘焙时Agent半径和高度设得太小会导致导航网格贴着墙角角色走位容易卡动态障碍用NavMeshObstacle配合Carve但车轮式开合carve有性能开销大量使用要谨慎。这些结论都是具体的参数经验比Navigation是什么有用一百倍。7.3 工程优化数据块大小、资源、gameassembly.dll热词里限定数据块大小gameassembly.dll的作用这两个点其实都指向构建产物层面。gameassembly.dll是IL2CPP构建后存放编译后C代码的产物它体积大是正常的因为它包含了你所有脚本编译出的原生代码。理解这一点之后就知道压缩它没意义能优化的是代码本身和剥离strip等级。数据块大小比如AssetBundle的chunk限制目的是控制加载时的内存峰值。笔记里我记的思路是按使用场景切Bundle别按资源类型切。比如一个关卡的所有资源打一个包而不是把所有贴图打一个包因为前者加载是整块进出的后者会导致你只需要一张图却要加载一堆。7.4 代码混淆与保护代码混淆这块笔记里的结论是混淆解决的是反编译门槛不是绝对安全。实际做法通常是配合IL2CPP本身就把IL转成C了再做符号混淆重点是保护核心算法和敏感数据。要注意混淆后可能影响反射调用如果你的代码里大量用了字符串反射比如按名字找方法混淆后可能直接崩。所以混淆前必须做一轮完整的回归测试。这个测试清单我也存在笔记里。8. 跨平台发布与XR小游戏、Web、Pico4、数字孪生8.1 微信小游戏打包的常见卡点微信小游戏打包Unity这边有官方的转换工具但坑不少。笔记里记了几个高频问题包体限制首包有大小限制资源要拆分和CDN化。视频播放方案小游戏环境对视频的支持和原生平台不同直接播放在部分环境会失败需要用平台提供的接口播放或者在必要时候降级到序列帧/WebGL方案。广告接入激励视频和插屏广告的调用时机必须在用户交互之后触发否则可能被限制。我的笔记里对视频播放这一条记得特别细因为当时排查了很久。核心结论是小游戏环境的视频要走平台专门接口不能完全依赖原生VideoPlayer同时要做好播放失败的兜底。8.2 Web发布到IIS部署要点Unity发布WebGL后要部署到服务器IIS上最常见的两个问题一是MIME类型没配.wasm、.data这类文件返回404或者被当成下载二是压缩格式不匹配服务器开了gzip但文件不是对应格式。笔记里的解决步骤是先在IIS里补全MIME类型再确认IIS的静态压缩和构建时的压缩选项一致。所有配置我都附了截图和具体的配置项路径。8.3 Pico4、MR切VR、Cesium for Unity与数字孪生XR这块我笔记里单独开了子目录。Pico4基于Android打包流程和普通Android工程类似但要注意SDK版本匹配和渲染管线URP为主的兼容。MR切VR这种需求核心是在运行时切换XR的显示模式或者加载不同的相机配置笔记里记的是配置切换流程和切换时的资源重新加载问题。Cesium for Unity做数字孪生场景离线地图这块的关键是瓦片数据的本地化方案要把地形和影像数据落到本地存储再加载否则网络波动会直接影响场景。我笔记里记的是数据准备、路径配置、加载顺序这三步以及首次加载慢要做的预加载处理。数字孪生项目通常对帧率要求高所以优化笔记和这部分是交叉引用的用标签串起来。9. 让笔记真正能被检索、复用并且在面试里发挥作用9.1 命名规范和标签体系比目录结构更重要前面说了目录不要超过三层那多出来的维度怎么表达靠标签。我在每篇笔记的头部固定写一小段元信息# 20240612-UGUI-Button点击范围扩大 tags: #UI #交互 #射线检测 #UGUI platform: Android/iOS/Editor unity_version: 2022.3.x LTS status: 已验证tags用来跨目录检索platform和unity_version用来过滤status标记这条笔记是已验证还是待验证。这个status字段救过我不少次因为有些笔记是当时随手记的猜想没实测如果和已验证的混在一起下次直接用就会翻车。我现在只信任标记为已验证的条目。命名规范我定了三条硬规则日期前缀、模块关键词、动作短句。看起来死板但grep配合排序检索效率比任何笔记软件的花哨功能都高。9.2 把笔记变成面试复习清单热词里有Unity面试题Unity和C#八股这个其实和笔记体系高度重合。我的做法是每隔一段时间从现有笔记里反向生成一份自测清单。具体就是把每个H2小节转成一个问题比如渲染那节转成Renderer.bounds和Mesh.bounds有什么区别UI那节转成UI显隐三种方案的取舍。能答上来的标绿答不上来的回去补笔记。这样做的额外好处是你的复习材料和你真实做过的东西是绑定的不是网上抄来的通用八股。面试官问到某个点你能顺口说出我在XX项目里遇到过当时是这么解决的这个说服力完全不一样。我的笔记里甚至保留了当时的错误截图和排查日志这些在面试里讲出来就是加分项。9.3 定期整理一个每季度做一次的动作笔记体系不整理一定会烂。我现在每季度做一次清理动作固定三步合并重复条目同一个问题在不同时间记了好几遍的合并成一条保留最新的结论。升级status把待验证的要么验证掉要么直接删掉别留着当噪音。交叉链接把相关的条目互相加引用比如UI显隐和性能优化之间互链形成网状结构。这三步花不了多少时间但能让笔记一直保持在可用状态。我见过太多人的笔记系统死于只进不出越堆越多最后自己都不想打开。10. 我自己跑这套体系下来的几点真实体会这套目录我用了挺长时间中间改过两版。最大的感受是整理笔记这件事的价值不在于笔记本身而在于整理的过程逼你重新理解一遍。很多知识点你以为懂了写到为什么要这么做的时候卡住了那就是没真懂。另一个体会是关于已验证这个标记的。刚开始我嫌麻烦什么都写已验证结果有次直接照搬一条没验证的笔记去改项目排查了半天才发现当时记错了。从那以后我严格区分不确定的就写待验证宁可标注得保守一点。还有个特别小的习惯我觉得挺有用每篇笔记最后留一行下次再看要先确认什么。比如阴影那条我写的是先确认是实时阴影还是烘焙阴影再看Quality设置。这行字能让我重新进入这个知识点的时候不用从头读直接进入状态。笔记写给别人看是一回事写给自己看这种重启提示特别省时间。如果你现在笔记还很乱别想着一次性重构成完美结构。先从最高频的那一类问题开始比如UI或者渲染单独理出一个目录用一两个星期跑顺了再扩到其他模块。一次性大重构坚持不过三天这个我有过教训。