
1. 项目骨架规则目录划分、命名约定与资源管理的硬约束1.1 目录怎么分才不会在三个月后想重构Unity 项目有个特点任何人拿到一个新工程第一眼看的不是代码是 Assets 目录。目录结构如果乱后面所有事儿都会跟着乱。我见过太多项目Scripts 下面塞着几十个脚本Materials 和 Textures 混在一起Plugins 里躺着三四个平台的 SDK还有叫 NewFolder 的、叫 test2 的、叫 final_v3 的目录这种项目别说接手自己过两周回来都找不到东西。我这边长期使用的规范是这样的按资源类型和业务模块双向划分Assets/ ├── _Project/ │ ├── Art/ # 美术资源Models、Textures、Materials、Animations │ ├── Audio/ # 音频BGM、SFX、Voice │ ├── Code/ # C# 脚本Framework、Gameplay、UI、Editor │ ├── Data/ # ScriptableObject、Json、配置表 │ ├── Effects/ # 粒子、Shader、特效相关 │ ├── Prefabs/ # 预制体Player、UI、Props、FX │ ├── Scenes/ # 场景文件按关卡或功能分子目录 │ ├── Settings/ # 渲染管线资产、输入配置、物理配置 │ └── ThirdParty/ # 第三方插件、SDK为什么加_Project这个前缀因为 Unity 默认的 Assets 根目录下面经常会有 Unity 自动生成的目录比如Editor、Resources、Plugins如果我们自己的业务资源全部收拢到一个带下划线前缀的目录里一眼就能和引擎级目录区分开。下划线前缀还能让这个目录在 Project 窗口里默认排最前面打开工程第一眼看到的就是自己的项目结构。另外有一个原则Resources目录尽量只有一个且放在_Project/Resources底下。很多人习惯在多个目录里各建一个 ResourcesUnity 虽然支持这种写法但一旦资源路径冲突或者你想做热更新、AssetBundle 迁移会非常痛苦。统一收口所有动态加载的资源走同一个目录是后续优化资源管理的基础。1.2 命名规范从资源名到脚本类名的统一约定命名这件事看起来是小事儿但对协作的影响极大。我参与过十几个 Unity 项目踩过最深的坑就是命名字段不统一比如同一个角色模型有人叫Hero_Swordman_001有人叫swordmanFBX还有人叫角色-剑士-01。这种资源在美术、策划、程序之间流转时每次都要人肉确认这个是不是那个效率极低。我们团队最终定下来一套规则简单粗暴但有效文件名使用 PascalCase即每个单词首字母大写如PlayerController.cs、Swordman_Attack.fbx。资源名前缀追踪资源类型模型M_前缀M 代表 Mesh/Model如M_Swordman.fbx材质MAT_前缀如MAT_IronArmor.mat纹理T_前缀加用途说明如T_Swordman_Albedo、T_Swordman_Normal、T_Swordman_Roughness预制体PF_前缀如PF_Enemy_Swordman.prefab动画片段Anim_前缀如Anim_Swordman_Attack01UI 界面UI_前缀如UI_MainMenu.prefab脚本不强制加前缀但类名必须和文件名完全一致否则 Unity Inspector 上 MonoBehaviour 会变成 Missing Script场景命名Scene_前缀加模块名和序号比如Scene_Game_MainLevel_01。导出动画片段时特别注意如果从美术工具导出 FBX 时带多个动画在 Unity 里通过 Model Importer 的 Animation 页签拆分出Take 001、Take 002导出前就约定好用Anim_重命名每一个 Clip而不是让Take 0xx留在工程里不然早晚会引用错动画。这套规则并不复杂难的是坚持。所以我建议把它写进项目的 README并且在 Review 时把命名规范作为第一道检查项。别嫌烦一个三个月后还能快速找资源的项目和一堆乱命名资源的项目协作效率能差出一倍。1.3 资源导入设置Meta 文件与平台适配在源头就要管好Unity 里的每一个资源都有对应的.meta文件这个文件记录着资源的 GUID、导入设置等关键信息。规范里必须有一条硬性规定meta 文件必须强制提交到版本控制任何时候都不能删。一旦 meta 丢失Unity 会重新生成新的 GUID所有引用到这个资源的场景、预制体、脚本序列化数据全部会断链表现出来的就是 Prefab 上缺字段、场景里脚本丢失。这种事情在团队协作里发生过太多次每次都是大事故。资源导入设置也要在源头规范化。不同平台对纹理、音频、网格的默认导入设置完全不同如果不处理就进工程包体、内存、加载时间都会失控。我的建议是纹理资源根据用途提前指定类型。UI 图用Sprite(2D and UI)模型贴图用Default法线贴图必须标记为Normal Map。像素压缩格式统一Android 用 ASTCiOS 用 ASTC 或 PVRTC不使用平台默认的 TrueColor 保留格式否则内存直接拉满。音频资源背景音乐用Streaming流式加载音效用Compressed In Memory短效提示音用Decompress On Load。Vorbis 压缩质量设置在 50~80 之间别默认拉到 100包体大不说听感差异极小。模型资源如果模型只用于场景摆放不参与骨骼动画Rig类型设为None这样可以跳过骨骼解析如果模型没有动画Animation Type设为None能省不少内存如果有动画才设Humanoid或Generic。Read/Write Enabled默认保持关闭只有当脚本需要运行时读取网格数据时才打开这个开关开着会缓存一份 CPU 可访问的网格副本直接导致内存翻倍。这些设置看起来琐碎但就是这些琐碎的东西最后决定了你的包体能压到多少、内存能不能稳住、场景切换掉不掉帧。2. 脚本与代码规范生命周期、状态管理与事件驱动的正确姿势2.1 MonoBehaviour 的职责边界别让一个脚本变成垃圾桶Unity 项目里最常见的坏味道就是一个MonoBehaviour类里什么都有能接受输入、能播放动画、能调 UI、能发网络请求、还能算伤害数值。这种脚本一开始写的时候很爽后面越改越痛苦因为每加一个功能都要在同一个类里找地方塞依赖关系越来越乱。我把脚本按职责拆成了四种类型组件型脚本挂载在 GameObject 上处理该物体自身的表现逻辑。比如PlayerMotor负责移动PlayerAnimatorController负责动画状态切换HealthComponent负责血量变化。管理器型脚本全局唯一负责某一类系统的调度。比如UIManager、AudioManager、EventManager通常是单例或静态类不挂在场景物体上或挂在专属的管理节点上。数据型脚本纯 C# 类不继承 MonoBehaviour用于描述数据结构和配置。比如PlayerData、ItemConfig。能写成纯 C# 的绝不写成 MonoBehaviour这样方便单测也避免场景引用导致的内存泄漏。工具型脚本静态方法集比如MathUtils、FileUtils、StringUtils没有状态只做纯计算和 IO 操作。为什么强调这个拆分因为 MonoBehaviour 一旦挂到 GameObject 上它的生命周期就被引擎接管了什么时候 Awake、什么时候 OnDestroy 不完全由你控制。如果我们把纯逻辑的代码也塞进 MonoBehaviour在单元测试时就要创建 GameObject、挂组件、跑场景折腾一圈下来成本极高。而纯 C# 类可以在 EditMode 下直接跑测试快速验证逻辑。2.2 Update 方法的使用纪律能不用轮询就不用Unity 开发里性能杀手第一名就是滥用Update。很多人习惯把判断写在 Update 里每帧执行比如void Update() { if (Input.GetKeyDown(KeyCode.Space)) { DoJump(); } }这个例子还好性能损耗可忽略但如果你在 Update 里做路径查找、资源加载、字符串拼接、反射调用那问题就大了。规范里我定了这么几条事件驱动优先输入检测、UI 显隐、业务逻辑触发优先用事件或回调。比如按钮点击用onClick.AddListener粒子播放完用ParticleSystem.Stop回调而不是在 Update 里每帧检查状态。需要每帧处理的才放进 Update比如角色移动、摄像机跟随、动画参数更新。这些东西本身就需要逐帧插值或同步放 Update 理所当然。低频逻辑用协程或计时器比如每 5 秒刷新一次排行榜写一个协程while(true){ yield return new WaitForSeconds(5f); Refresh(); }比在 Update 里Timer Time.deltaTime然后判断要干净得多。固定物理用 FixedUpdate刚体相关的力、速度修改要放在 FixedUpdate 里放 Update 会导致物理计算不稳定现象是低帧率时角色飘、跳跃高度不一致。LateUpdate 只用于摄像机跟随和动画LateUpdate 在 Update 之后执行此时所有角色移动已经完成相机跟随不会出现抖动。协程这个方案有个注意点协程的执行时机是受 MonoBehaviour 生命周期影响的如果物体被 SetActive(false)协程会继续跑如果物体被销毁协程停。所以在使用协程时要确保对协程的启停有清晰的生命周期控制最好用专门的TaskRunner或 UniTask 管理而不是到处裸奔StartCoroutine。2.3 单例、事件中心和静态状态全局变量的边界在哪里Unity 项目里单例模式几乎成了标配。GameManager.Instance、AudioManager.Instance、DataManager.Instance到处都是。单例用得好能简化跨模块通信用得不好就是隐形的地雷场景切换不销毁、静态引用不释放、模块间耦合过重。我的规范是单例对象必须明确区分全局单例和场景单例。全局单例跨场景存活放在专门的_App节点下场景加载时不销毁场景单例随场景销毁比如LevelManager在场景内各系统间共享状态。单例的构造和销毁要处理干净。建议用自动创建模式即在Instance属性里判断为空时自动new GameObject挂载而不是在场景里手拖。这样可以避免忘挂、重复挂的问题。跨模块通信优先走事件中心而不是直接引用单例。比如背包物品被拖拽到装备栏这个事件BagManager只管发出事件EquipmentManager监听事件并处理双方不持有彼此引用。这样新增模块、替换模块时不动其他代码。静态类的使用要克制。静态状态静态字段、静态属性不归任何对象管理一旦改了就全局生效且很难追踪。能放进 ScriptableObject 配置的别用静态字段能通过依赖注入传参的别直接访问静态类。还有一点很多人忽略Unity 的OnApplicationQuit和OnDestroy执行顺序在不同平台不一样如果单例在退出时互相引用可能报空引用错误。我建议在单例的OnDestroy里只做释放本对象持有的资源这一个动作不要尝试调用其他单例的方法各管各的出问题概率会小很多。3. 渲染与场景管理规范从包围盒、阴影到 UI 显隐都有讲究3.1 包围盒与合批看不见的渲染开销决定性能上限很多项目在优化渲染时第一反应是把阴影关掉把分辨率降一点这些当然有效但属于先砍视觉再砍性能。真正该做的第一件事是检查渲染器的包围盒Bounds是否合理。Unity 渲染器上的Renderer.bounds是 Unity 做视锥剔除、遮挡剔除、光照计算的基础数据。如果包围盒不对会引发两类明显问题包围盒过大物体会在不可见时仍然参与渲染浪费 DrawCall 和渲染时间。常见原因是从美术工具导入的模型没有正确计算包围盒或者预制体上有残存的不可见子物体把 Bounds 撑大了。包围盒过小物体会在镜头已经能看到它时被剔除屏幕上直接出现凭空消失的模型。常见原因是运行时修改了 Mesh 的顶点数据比如地形切割、动态变形但没有调用Renderer.UpdateBounds。实操上的做法是所有新导入的模型都在Model Importer里勾选Auto Generate Colliders如果需要并检查Mesh.bounds是否合理运行时动态修改网格后必须手动调用rebuildBounds。另外要把动态合批和静态合批的使用边界划清楚。动态合批适用于移动物体但有顶点数限制每批 300 顶点以内取决于平台静态合批适用于不动的物体能显著减少 DrawCall代价是合批后无法单独变换和减裁内存会略微增加。规范里写死一条所有不动的物件场景装饰、墙体、桥梁必须标记为 Static 并启用 Batching所有移动物件尽可能用低面数模型让引擎动态合批能命中。还有人习惯用一个巨大的空物体包着整关卡只有一个渲染器这在个别项目里是为了好做整体显隐但代价是完全破坏了视锥剔除的粒度——整个关卡永远在渲染范围内等于关闭了剔除。这种场景我建议拆成多个区域块每块一个渲染器用遮挡剔除Occlusion Culling来处理性能和内存都会好很多。3.2 阴影、Shader 与渲染管线的规格约束阴影和 Shader 是渲染规范里最容易失控的部分。很多美术同学对性能没有概念随手放一盏平行光加一盏点光再开实时阴影场景里什么都齐了一跑起来掉帧掉到没法看。我的建议是先约定渲染管线和光照方案。项目启动时就要确定是使用内置渲染管线Built-in、URP 还是 HDRP。URP 是目前中小型项目的主流选择性能好、扩展性强、移动端友好。渲染管线一旦确定尽量避免在中途大规模更换这个过程会痛苦到你想删库。实时阴影只在必要时开启。移动端项目角色和主要场景物件可以开实时阴影其余大量装饰物用烘焙光照贴图Baked Lightmap。Shadow Distance按场景大小调试一般设置在 30~80 米之间超过这个距离的阴影取消渲染这比调阴影分辨率更省性能。Shader 的使用要统一收口。项目里所有材质应该基于同一套 Shader 变体构建避免每个人从网上下一个 Shader 就往工程里扔。变体数量会直接影响打包时间、包体和运行时加载耗时。在 Shader 里慎用#pragma multi_compile能用#pragma shader_feature的绝不用前者因为前者会把所有关键字组合都打进变体内容直接指数膨胀。关于渲染管线的逆向分析调试麻烦的渲染问题时可以在 Frame Debugger 里逐 DrawCall 查看渲染状态这能定位大多数为什么这个物体被渲染了两次为什么这个乱画了的问题。团队里如果有人负责图形底层建议常规性地在 Frame Debugger 和 RenderDoc 中检查 pass 数量和材质参数避免人均 Shader 滥用导致的问题变成长期技术债。3.3 UI 显隐方案SetActive、LocalScale 与移出屏幕怎么选UI 显隐这个问题在网上讨论很热烈核心争论是SetActive(false/true)、LocalScale、移出屏幕三种方案哪个好。我的经验是没有绝对好坏只有适合的场景。我整理一个对照表方案原理性能开销适用场景SetActive(false)禁用 GameObject 及所有组件重新激活时重建组件状态开销较大但释放渲染和更新开销不频繁切换的界面、弹窗、加载界面SetActive(false) 替代方案Canvas.enabled禁用 Canvas 渲染但保持逻辑活动不重建组件但 Update 仍在跑UI 需要在隐藏时继续做逻辑比如倒计时LocalScale Vector3.zero把物体缩到零视觉上隐藏开销最小但 UI 仍然参与布局计算极端情况会产生视觉残影不推荐除非在特殊动画过渡中移出屏幕RectTransform 移到视口外通过修改 anchoredPosition 把 UI 移走无重建开销但位置计算麻烦容易出 Bug不推荐日常使用多用于特定过场动画我的规范是界面频率低、结构复杂的排行榜、商城、设置面板用SetActive(false)进入和退出时配合简单的 CanvasGroup 淡入淡出动画避免突兀。需要频繁切换的比如 Tab 页签用CanvasGroup控制 alpha 和 raycastTarget保留所有子物体激活牺牲一点内存换流畅切换。绝对不要用 LocalScale(0) 做永久隐藏因为在部分 UI 布局下会出现子物体布局残留、点击区域不符等问题。另外GraphicRaycaster是 UI 点击检测的入口面板隐藏后一定要关闭raycastTarget否则面板看不见但依然挡着下层 UI 点击。这也是新手最常见的问题之一——按钮没反应找半天发现上面挡了个透明 Panel。4. 性能与内存优化规范用数据说话别拍脑袋优化4.1 Profiler 使用规范性能优化必须从数据开始我见过太多项目组在优化性能时靠感觉感觉这个界面打开有点慢感觉战斗时有点卡感觉内存有点高。问哪里慢说不上来看具体数据没记录过。这种优化方式十次有八次方向是错的。Unity 的性能分析工具链已经很成熟规范里必须要求每个开发者学会使用Unity Profiler定位 CPU、GPU、内存、渲染、物理、UI 各部分耗时按时间线检查卡顿点。Frame Debugger定位渲染管线中每个 DrawCall 的耗时和提交顺序检查是否有多余的 pass。Memory Profiler查看托管堆分配、原生内存、资源占用定位内存泄漏。Profiler 的 Deep Profile 模式如果定位不到具体方法耗时用 Deep Profile 会大幅降低帧率但提供所有函数调用时间适合攻坚阶段。性能优化的核心步骤永远是先有数据再定目标然后动手。比如发现某场景帧耗 18ms用 Profiler 一查发现 Physics 占了 6msUI 占了 4ms渲染占了 5ms你就可以针对性地去查物理碰撞体和 UI 刷新逻辑而不是一上来就关阴影降分辨率。4.2 资源加载、缓存与释放内存管理的生命周期约定内存管理的核心不是内存不够了想怎么释放而是从设计上就控制内存的增长。我们团队的内存规范包括这几条场景资源按需加载场景里不用的资源不要一股脑加载进来。UI 资源用 Addressables 或 AssetBundle 按功能模块打包和加载主城场景只加载主城需要的资源战斗场景只加载战斗资源绝不把全量资源放进一个包。资源缓存统一管理同一份资源比如一个角色预制体不能被多个系统重复加载。用AssetReference或资源管理系统统一查重已加载的资源直接引用计数加一释放时逐一减一引用计数归零才真正卸载。这样做能避免同一个模型被加载了 20 份的内存灾难。定时清理和场景切换清理场景切换时统一走资源管理器释放当前场景资源避免单独在OnDestroy里东一个西一个地 Unload。对象池管理高频物射出去的子弹、飘字、粒子特效这些高频创建销毁的对象必须走对象池。对象池的核心是预分配和复用实例化出的对象把组件状态重置干净再入池取出时执行OnSpawn/OnDespawn回调而不是靠SetActive硬切。还有一个容易忽略的点Resources.Load加载的资源默认不会卸载即使调用Resources.UnloadUnusedAssets也要等到场景切换的合适时机才能完全释放。如果项目不是做极小的 Demo建议长期方案是切换到 Addressables它对资源依赖分析、内存释放、远程加载的支持要完整得多。4.3 物理、动画与 Timeline 的取舍规范物理是性能大头。规范里我要求碰撞体尽量用基本形状Box、Sphere、Capsule不要用 Mesh Collider特别是凹多边形网格碰撞复杂的碰撞体能省则省。减少每帧物理查询OverlapBox、SphereCast、Raycast这类查询如果在 Update/频繁调用中滥用会极大地拖慢性能。能用事件驱动如触发器 OnTriggerEnter就绝不用轮询射线检测。控制刚体数量场景里如果有几万个刚体性能是不可能好的。静态碰撞体用不带刚体的 Collider只有真正需要物理响应的物体才加刚体。动画方面Animator的 Parameters 数量和状态机复杂度会影响 CPU 开销。能用Timeline做过场动画和序列化事件的地方不要用一大坨脚本状态机硬写。Timeline 的PlayableDirector可以轻松控制多轨动画、音效、事件回调但要注意 Timeline 使用的 Clip 资源如果是通过 FBX 导入的多个动画片段需要在导入阶段就把Animation Type和Loop Time设置正确否则运行时会出现动画跳帧或循环异常。关于动画 Event用 Animation Event 进行动画播到某一帧调某个方法的逻辑时要特别注意参数类型只能使用简单类型int、float、string不要试图传复杂对象也不要试图在动画事件里做重逻辑结构比如异步加载大资源这些用法都会引入难以排查的时序问题。5. 数据通信规范串口、网络与数字孪生场景的统一封装5.1 串口通信的封装与异常处理搜索热词里有 Unity 串口通信这个场景在数字孪生、工业设备对接、传感器数据采集中非常常见。Unity 本身不直接支持串口需要通过System.IO.Ports.SerialPort来实现但直接用这个类写业务会有很多坑。我的规范是封装一个SerialPortManager串口参数配置放在 ScriptableObject 或 JSON 配置文件里端口号、波特率、数据位、停止位、校验位都抽出来不要在代码里写死。换设备时改配置就行。读写分离读数据放后台线程写数据走主线程队列。SerialPort.DataReceived事件在后台线程触发不能在事件里直接操作 Unity API。做法是先把数据丢进 ConcurrentQueue在主线程的 Update 里取出并解析。必须做异常和断线重连串口经常会出现拔插、占用、数据乱码等问题。封装类里要有重连机制检测到SerialPinChanged或者超时无数据时自动重连而不是直接抛出异常崩溃。超时控制串口读取要设置ReadTimeout和WriteTimeout避免设备不响应时阻塞主线程导致游戏卡死。5.2 网络协议与数据同步规则网络通信和串口最大的不同在于数据和时序的复杂度。规范上我建议统一走消息分发框架客户端和服务器之间统一使用消息 ID 数据体的格式消息 ID 用 int 或 short 表示数据体用 Protocol Buffers 或 MessagePack 序列化。不要混用 Json、BinaryFormatter、Xml否则后续维护成本爆炸。客户端 UI 和网络层之间不能直接耦合。网络回调里更新 UI 必须通过事件中心派发且回调只会发生在主线程。Unity 的跨线程问题在协程网络库中很容易踩坑用 UnityWebRequest 的SendWebRequest回调时默认已经回到主线程可以放心更新 UI但使用原生 TCP Socket 或第三方网络库时必须手动处理回调线程。数据同步要区分全量和增量。每次切换场景、登录进服时拉全量数据运行中只发增量数据比如玩家位置、背包物品变化。全量数据包如果频繁发送网络带宽和客户端解析开销都会失控。5.3 地图场景与数字孪生的接入规范数字孪生、智慧城市、地图可视化的需求越来越多这类项目里经常用 Cesium for Unity 加载真实地理信息数据。接入这类 SDK 时规范上要注意首次对接先确认许可协议和 SDK 版本Cesium for Unity 的版本和 Unity 主版本有较强绑定关系选错版本会导致整个工程跑不起来。离线地图数据是另一个大坑如果项目对网络环境有要求内网部署、展厅无外网需要把 Cesium 的影像数据、地形数据下载到本地在工程里配置离线地形和影像源。这个环节要在项目设计阶段就确定不能在开发中后期再转型否则会把整个数据加载架构推倒重来。地图场景的性能优化Cesium 的 3D Tiles 加载是动态调度的场景里同时可见的瓦片数量越多DrawCall 和显存占用越高。在场景规范里要限制MaximumScreenSpaceError的值它控制瓦片精度的选择值越大加载的瓦片越粗糙、数量越少在主要关注宏观呈现的项目里可以适当调大。地图场景和业务场景分离地图功能做成独立模块不要让业务逻辑直接操作 Cesium 的内部对象。这样地图 SDK 升级时业务层不需要改动。关于 Perlin Noise 这类过程化地图生成搜索热词里也有mathf.perlinnoise如果项目中涉及地形生成建议把它封装成独立的地形生成工具输出高度图到 TerrainData而不是在运行时逐格生成顶点。运行时逐格修改地形顶点性能开销大且容易触发很多隐性 Bug法线、碰撞、LOD 重建等。6. 构建发布与平台适配规范从 Windows 到微信小游戏一次跑通6.1 宏定义与平台分支的使用纪律Unity 的宏定义#if UNITY_ANDROID、#if UNITY_IOS、#if UNITY_EDITOR是平台适配的基础工具用好了事半功倍用差了代码可读性极差。我的规范是平台差异代码必须收口不能散落在各个业务脚本里。统一用PlatformAdapter类封装业务层永远只调用PlatformAdapter.SomeMethod()内部通过宏做平台分支。例如支付、分享、推送、登录等第三方 SDK每个 SDK 都封装成一个 Provider通过平台适配器来决定实例化哪个 Provider。宏定义里的代码必须保持最小只放平台 API 调用和数据类型转换不放业务逻辑。业务逻辑应该抽到公共方法里让不同平台调用同一个方法。在Player Settings里的Scripting Define Symbols要记录每一项的含义和开启人避免项目切换到另一个分支时出现为什么我这边多一个宏的困惑。团队的ProjectSettings文件里相关字段要定期 Review。特别提一下UNITY_WEBGL和微信小游戏平台。微信小游戏本质上运行在 WebGL 环境但又有自己的一层 JavaScript 桥接。像Application.platform在微信小游戏里返回的是 WebGL但如果用了 WeChat 小游戏 SDK它是通过WX对象注入的所以必须区分通用 WebGL和WeChat 小游戏两层平台维度。这个适配建议专门做一个WechatPlatformAdapter在微信小游戏增加广告、支付、视频播放能力的接入和回退。6.2 构建配置与部署WebGL、IIS 与微信小游戏的差异化处理从热词里能看到unity 发布web部署iis、unity 微信小游戏打包这些具体需求这里展开讲一下部署层面的规范。WebGL 构建压缩格式Brotli 或 Gzip选择一种并在服务器端配置对应的Content-Encoding头。WebGL 的gz文件结尾不要改扩展名否则部署到服务器后容易 404。Player Settings - Publishing Settings里Enable Exceptions一般选Explicitly Thrown Exceptions Only部署正式环境时关闭Development Build避免过大的调试开销。WebGL 部署到 IIS 时需要单独配置 MIME 类型.unityweb映射为application/octet-stream.wasm映射为application/wasm否则浏览器下载时类型不对可能导致无法加载。每次构建后有条件的可以写一个自动部署脚本把构建产物拷到 Web 目录并刷新 IIS 应用池减少手动操作。微信小游戏微信小游戏打包和其他平台最大的不同是需要把 Unity 的 IL2CPP 产物和微信环境桥接常用的方案是官方提供的Unity WebGL 小游戏适配插件。构建时要把Compression Format设置为Disabled或具体适配插件支持的格式否则小游戏运行时解压会出问题。小游戏的包体限制很敏感主包超过 4MB 就需要走分包加载。这类项目要尽早把游戏资源从StreamingAssets调整为 CDN 远程资源避免后期全部返工。微信小游戏的视频播放方案通常是使用video原生组件插在 Canvas 上通过WX.createVideo创建视频对象播放 Unity 外部 CDN 上的视频文件。这块的坑在于视频层级与 Unity Canvas 层级冲突需要做原生层和 WebGL 层的同步建议封装成一个 YouTube 式的视频播放组件对外暴露播放、暂停、关闭、回调统一接口。Windows 构建提到一个常见的报错Unity is running with administrator privileges, which is not supported.这个在 Windows 上以管理员身份运行 Unity Editor 时常出现主要是 Unity 觉得管理员权限会影响某些功能。规范上的做法是不要拿管理员权限跑 Unity Editor如果不是必须用普通用户权限启动。如果项目里确实有脚本需要管理员权限才能操作文件/注册表也应该把权限提升放到运行时单独做而不是让整个编辑器全程管理员运行。6.3 代码混淆、加密与安全的基础规则Unity 客户端的代码安全一直是个伪命题因为它原生跑在用户设备上逆向只是时间问题。但我们依然要做基本的防护目的不是绝对安全而是提高逆向门槛、保护付费逻辑和客户端的协议层。混淆工具用 Beebyte、Obfuscator 或 open-source 的混淆方案对最终发布的 C# 程序集做混淆。注意混淆后要完整跑一遍回归测试混淆经常会踩到反射、序列化、字符串加密相关的坑。敏感信息剥离支付密钥、服务器 IP、AES 密钥禁止直接写死在代码和配置里。正确做法是放在服务器下发的配置里或者用公钥加密后运行时解密。如果必须内置密钥也要拆散存储并混淆变量名。协议加密网络通信至少做一层加密全走明文很容易被工具抓包后模拟请求。轻量场景用 AES 随机 IV重要场景用 HTTPS 或 TLS。数字签名要加防重放机制客户端和服务器都加上时间戳校验。防破解的思路要现实不要在客户端做 是否正版的强校验因为这会被 patch 调分支跳过。更实际的是把核心玩法逻辑放在服务器端客户端只做表现层。7. 版本管理与协作规范Git、Review 与自动化质检7.1 Unity 项目的 Git 配置要点Unity 项目在 Git 协作上有一个和其他项目截然不同的地方场景文件、预制体、资源导入设置都是文本化的 YAML 格式虽然可以 diff但冲突极其频繁。所以版本管理的规范必须前置。.gitignore必须提前配好把Library/、Temp/、Obj/、Build/、Logs/、UserSettings/都忽略掉。Packages/目录要提交ProjectSettings/要提交。Assets/下所有.meta文件必须提交不能进.gitignore这条之前讲过这里是操作层面的强制要求。建议开启git lfs管理大文件资源模型、纹理、音频否则仓库体积很快膨胀到几个人拉不动代码。在.gitattributes里声明*.fbx filterlfs,*.png filterlfs之类的规则。场景文件的冲突处理思路同一时间尽量只让一个人改同一个场景。如果确实要并行开发先让一个人把场景结构空物体、节点层级定好提交其他人只往里面填内容这样冲突概率小很多。7.2 代码 Review 检查清单与常用检查点Review 的目的不是挑刺而是把常见坑拦在合入前。我整理了一份 Unity 项目专用的 Review 检查清单每次提交代码前自己先过一遍是否引用了外部资源如Resources.Load、AssetBundle 加载而没有对应的释放逻辑是否在 Update 里做了不必要的计算字符串操作、反射、一次性初始化是否用到了GameObject.Find、FindObjectOfType等搜索型 API如果在 Update 里出现直接打回。是否在OnDestroy中安全地解绑了全局事件否则跨场景会留下悬挂事件调用时直接空引用是否在静态类或单例里持有场景对象引用这会使场景对象在切换时无法被 Destroy是否处理了OnDisable/OnEnable的重复注册问题AddListener 时先 RemoveListener避免重复调用修改了渲染相关的代码后是否在目标平台真机测试过Editor 里渲染行为和真机差别很大是否在OnValidate、OnDrawGizmos里写了会影响合批的代码如果项目组成员对代码规范不熟悉可以在项目里加一个自动化检查工具用脚本扫描比如检查Update里是否调用了GetComponent或Resources.Load这些都能用Editor脚本静态扫描出来在 CI 或打包流水线上跑。7.3 CI/CD 与自动化构建的接入经验Unity 项目的 CI/CD 没有 Web 项目那么轻量因为构建环境需要安装 Unity Editor 和模块且 Unity 的许可证激活、-batchmode命令行参数都要处理。但这块一旦跑通收益非常明显。标准做法本地安装 Unity 命令行工具用unity-editor -batchmode -quit -projectPath ... -executeMethod BuildScript.BuildXXX -logFile -在服务器或本地流水线上执行构建。BuildScript里用BuildPipeline.BuildPlayer的 API 指定目标平台、输出路径、场景列表、宏定义、AssetBundle 打包。自动化要分成三层Testing 层跑 EditMode / PlayMode 测试、Validation 层资源检查、命名检查、Shader 变体检查、Build 层产出 Android/iOS/Windows/WebGL 安装包。三层都过了才允许合入主分支。一个很关键的细节Unity 的-batchmode下跑测试必须加上-runTests -testPlatform EditMode -testResults参数并且建议在 CI 环境里锁定 Unity 的小版本比如 2022.3.20f1不同 patch 版本对某些资源导入和 Shader 编译的差异会带来构建结果不一致的问题。8. 结尾前再多讲两句Unity 开发规范的本质是降低沟通成本写到最后我想说一句开发规范这东西它不是束缚不是教条它是让团队里每个人在同一个语境下工作的一套默认约定。规范的价值不在于每一条都对而在于团队有共识这件事本身。哪怕你是一个人做项目写规范也能帮你三个月后快速找回当时的思路带团队的话规范的杠杆作用就更明显了。我之前在项目里一度觉得规范很麻烦每个脚本要按框架写、命名要带前缀、资源要按目录放总觉得拖慢节奏。等到项目进入中后期当我要同时改十个脚本、加一个新功能、发一个测试包的时候发现因为前期规范立住了所有东西都能被预料到那一刻才真正体会到规范省下来的时间有多可观。如果这篇内容对你的项目有帮助可以从最痛的 2~3 条开始落地比如先把meta文件的提交规则和Update的使用纪律立起来。不用一口气全上渐进地来你的 Unity 项目会慢慢变得干净、可维护最终受益的一定是每一个参与开发的人。