
我前阵子收拾旧硬盘翻出一个 2018 年用 Unity 做的仿《保卫萝卜》塔防 Demo当时做完丢在本地就没管了。这次本来只是想跑起来怀念一下结果一冲动直接用 AI 辅助花了两个多小时把它搬到了浏览器上用 Chrome 直接就能玩。这件事做完之后我想明白一个道理老项目的“移植成本”其实不是引擎版本差多少而是你对旧代码还有没有耐心。这次我用 AI 帮忙查文档、补构建坑、写迁移脚本硬是把一个满是老旧 API 的 Unity 项目救活了。这篇就把整个流程、踩过的坑、以及浏览器端部署那堆破事一次说清楚给同样想把 Unity 老项目搬到 Web 上的朋友做个参考。1. 先想清楚为什么是老项目 浏览器 AI 这三件事凑在一起1.1 老 Unity 项目移植的真实成本2018 年的 Unity 项目和现在新建的项目差别非常大。最直观的一点是 2018 年主流使用 .NET Framework 4.x API 和内置渲染管线很多老项目会直接用OnGUI绘制界面或者用GameObject.Find满天飞而到了 2024 年以后的客户端大家更习惯用 UGUI 配合 Addressables。把这些老代码原封不动放到新版 Unity 里编译报错能刷一屏。更麻烦的是 WebGL 平台本身的限制。WebGL 打包出来不是一套常规的可执行文件而是把 C# 代码编译成 WebAssembly跑在浏览器的沙箱环境里。这意味着一部分运行时能力会被砍掉比如System.IO.File这种直接读写本地文件的方式在浏览器里不可用。多线程支持不完整Thread类可以编译但实际会被模拟性能跟桌面端不是一回事。部分网络 API 受浏览器同源策略限制直接用UnityWebRequest请求本地相对路径要小心。存档不能写到 C 盘而是要走浏览器的 IndexedDB也就是很多人踩过坑的 IDBFS 写入失败问题。这一步的核心是搞明白老项目最难迁移的不是业务逻辑而是它依赖的“硬环境”。业务逻辑是纯 C# 代码在 WebGL 下照样能跑但文件读写、输入方式、字体渲染、Shader 兼容性这些才是真正的迁移大头。想清楚这一点后面推进就快了。1.2 为什么选 WebGL 而不是换个方案重做我一度想过用 Cocos Creator 或者直接用 H5 重写这个塔防游戏后来都否掉了。原因很简单这是一个完整可玩的游戏有炮塔升级、怪物寻路、关卡波次光是怪物寻路和路径点逻辑在当年就调了不少时间用 H5 重写少说也要一周。WebGL 方案只需要解决平台适配不改变代码逻辑改动范围可控得多。另外Unity 对 WebGL 的整链路支持已经成熟。从 2018 年到现在的版本Unity 在 WebGL 上默认采用 IL2CPP 编译搭配 WebAssembly 运行性能足够跑 2D 塔防这种轻度游戏。而且 Unity WebGL 构建产物可以直接丢给任何静态文件服务器不需要装后端用户打开链接就能玩。这比搞一个桌面安装包再让玩家下载体验轻太多。当然WebGL 的缺点是启动加载慢、内存受限、无法访问本地文件系统但塔防游戏本身不涉及大场景流式加载内存占用也远够用这些问题都可以通过构建配置缓解。对我这个场景来说WebGL 是性价比最高的路径。1.3 AI 在这个流程里的真实角色很多人以为“AI 帮我搬游戏”就是输入一句“帮我把 Unity 项目转成网页版”然后 AI 就直接上交成品。实际情况不是这样。AI 在项目迁移里更像一个“读过很多文档的实习生”它能帮你查 API、找报错原因、写辅助脚本但每一步都需要你确认和把关。比如这次我用 AI 查了 Unity 2018 的 WebGL 构建参数含义AI 能很快告诉我说Compression Format选 Brotli 体积最小、但需要服务器支持Content-Encoding: br这个回答非常快比我去翻文档高效得多。再比如我让 AI 帮我写了一段批处理脚本批量压缩贴图并检查 Alpha 通道情况省了不少手工活。但涉及业务逻辑的地方比如塔的攻击目标选择AI 给的代码我只能作为参考。因为在改某个路径点算法时AI 理解不了我当年设计的怪物转向逻辑生搬硬套反而会出错。结论是AI 是好用的“效率工具”但别把它当成“全自动迁移器”。2. 两个小时的拆解一个不可复制的脏项目怎么被一点点救回来2.1 项目体检先搞清楚 2018 年的项目到底缺了什么拿到旧项目之后我做的第一件事不是急着打包而是先体检。体检步骤很简单打开 Unity 项目目录看ProjectSettings/ProjectVersion.txt确认项目是在哪个 Unity 版本下创建的。我那次看到m_EditorVersion: 2018.4.36f1长舒一口气还好是老 LTS不用从远古版本一路升级上来。接着我会看两个地方Assets目录下有没有明显第三方插件以及ProjectSettings/ProjectSettings.asset里图形 API 的设置。老项目里插了几个 Asset Store 插件其中有个 Tween 插件已经停止维护换成新版 Unity 后大概率编译报错。好在升级到 2018.4 LTS 后大部分内置 API 没有破坏性变更几个插件也能正常编译。做完体检后我列了一个“迁移风险清单”大致是输入模块项目里用了老的Input.GetMouseButtonDownWebGL 下鼠标事件基本没问题但要加虚拟按键适配移动端。中文显示游戏内用了动态字体加载中文字库WebGL 构建时如果没有把字体打包进去会显示成方块。存档系统原来的PlayerPrefs在桌面端正常但 WebGL 下依赖 IndexedDB如果用户玩到一半存档丢失体验会很糟。音频压缩原来的音频资源在 WebGL 下需要考虑浏览器解码格式MP3 没问题但 WAV 大文件会影响加载。这个清单帮我省了大量时间。因为在后续打包时我只要一一核对清单里的问题就能快速定位异常点而不是等构建完成后再从一堆控制台日志里人肉找错。2.2 分阶段推进从能跑起来到能存档老项目迁移最大的忌是“一步到位”。我第一次构建 WebGL 的时候也天真地想直接 Build 完丢到服务器上就能玩。结果打开页面黑屏、报错、日志刷屏连 UI 都出不来。后来我总结经验大会分成三个阶段阶段一先让项目在目标版本里能编译通过。不急着切 WebGL先在编辑器里把File Build Settings Platform切换到 WebGL看看有哪些编译报错。这个阶段通常消耗最多心力因为老 API 全都翻出来了。我用 AI 帮忙处理了两类问题一是WWW类替换成UnityWebRequest二是部分OnGUI字体渲染报错。改完之后编辑器能正常运行游戏这个阶段就过了。阶段二构建最小产物到本地验证 WebGL 适配。为了不让一整个项目打包耗时太久我会先把大部分场景剔除只保留主菜单那一个空场景构建出一个最小 WebGL 包。把包丢到本地 HTTP 服务器上跑一下如果主菜单能正常加载并点击按钮说明链路通了这时再逐步加回游戏场景。这样能快速定位“到底是哪个资源导致的问题”而不是面对一个大黑屏无从下手。阶段三处理存档和跨域发布到公网。游戏场景能跑后接下来处理PlayerPrefs在浏览器下的持久化问题。Unity WebGL 的PlayerPrefs默认会保存到 IndexedDB但有几种情况会导致写入失败隐私模式、storage quota 超限、iframe 跨域。如果存档量小PlayerPrefs就够了如果存的是关卡数据、地图状态这种复杂对象我建议写个简单序列化类转成 JSON 存到PlayerPrefs或者干脆用官方的IndexedDB插件。2.3 AI 辅助点哪些活是 AI 真的能帮上忙的这次实战下来我觉得在游戏迁移类需求里AI 最适合帮做这几类事情第一类是文档速查和报错翻译。Unity 报错信息通常很精简比如DllNotFoundException: UnityPlayer.dll在 WebGL 下其实不是缺 dll而是你没装对应平台的 Build Support 模块。这类经验性问题AI 能很快给出答案和官方文档链接比搜索引擎直接定位还省事。第二类是数据迁移脚本。比如我想把老项目的 PlayerPrefs 键值对统一改成新命名规则手工改几十处地方太容易漏直接让 AI 帮我用 Python 写一个批量替换脚本扫一遍全部场景文件确定没改错后再提交。这种活 AI 做得又快又好。第三类是资源处理批处理。我有一批 2048x2048 的背景图要压缩成 1024x1024并且保留透明通道AI 帮我生成了一套 ImageMagick 批处理命令。这些命令不用记住参数让 AI 生成、自己跑一遍肉眼检查就行。但 AI 帮不了你做的事包括对游戏手感、数值平衡、美术效果的判断。比如塔的射程从 10 改成 12AI 会照做但它无法判断这个改动对游戏体验有多大影响。这些需要你亲自开着游戏跑几关感受。3. 核心关卡WebGL 构建与浏览器端的坑3.1 构建配置Player Settings 里的关键选项Unity WebGL 构建不是点一下 Build 就完事Player Settings里的配置直接决定产物能不能跑起来、体积有多大。这次我重点调了下面几个参数。Compression Format一定要选。默认是 Disabled如果不选构建出来的.wasm文件可能大几十 MB浏览器加载要半天。选 Brotli 压缩率高但前提是服务器支持Content-Encoding: br选 Gzip 更通用CDN 基本都支持。我这次选了 Gzip体积压到 20MB 以内加载速度可以接受。注意选了压缩格式之后Unity 会生成.gz/.br后缀的预压缩文件但也保留原始规则。如果没有对应后缀的预压缩文件服务器也不会自动压缩到时候浏览器会报Unexpected token 之类的加载错误。还有一个开关叫Strip Engine Code它会在构建时移除未用到的引擎模块体积能再小一圈但偶尔也会误删一些通过反射调用或字符串动态调用的代码。如果项目里有AssetBundle或者热更新相关的代码建议先不开这个选项等验证稳定后再逐步打开。WebGL 的Code Optimization默认是 Recommended适合绝大多数项目。代码量很大、追求极致性能的项目可以考虑 Fastest但 IL2CPP 的编译时间会明显变长我这次没必要动它。另外Auto Graphics API默认是勾选的它会让浏览器根据设备能力选择 WebGL 2 或 WebGL 1。如果项目用了比较老的 Shader不兼容 WebGL 2会出现渲染异常。可以手动取消勾选只保留 WebGL 2.0 或只保留 WebGL 1.0逐个测试。我在一个影子相关的效果上遇到问题最后只保留了 WebGL 1.0 才稳定这在老旧 Shader 项目里很常见。3.2 文件系统与存档IDBFS 写入失败的前因后果浏览器环境下Unity 把Application.persistentDataPath映射到 IndexedDB但很多老项目代码里会直接写File.WriteAllText这在桌面端没问题到了 WebGL 端就会报访问被拒绝。Unity 官方给出的方案是使用PlayerPrefs它内部已经处理了浏览器持久化。但存复杂对象时PlayerPrefs不适合放太大的字符串IndexedDB 有容量限制大概在浏览器存储配额以内通常在几十 MB 到几百 MB 不等隐私模式下甚至会直接禁用持久化。我在测试的时候就遇到过 IDBFS 写入失败表现为第一次进游戏存档正常刷新页面后存档丢失控制台报存储写入错误。后续查证发现原因是开发者工具或浏览器隐私模式下IndexedDB 不可用Unity 静默降级为内存存储刷新后就没了。实操建议写一个小工具在游戏启动时先写一个临时数据到PlayerPrefs再读出来校验。如果读不回说明当前浏览器环境不支持持久化弹一个提示框告诉玩家“当前浏览器无法保存进度请用正常模式进入”。对于复杂的存档结构我更推荐把数据序列化成 JSON 字符串再用PlayerPrefs.SetString存。唯一的注意点是存储配额。塔防这种游戏存档数据很小通常几百字节完全够用。如果哪天要做大世界、大量玩家自定义内容再考虑接入 IndexedDB 原生 API。3.3 内存、加载时间与性能调优WebGL 端的内存是浏览器和 wasm 共享的Unity 默认给 wasm 分配的内存大小写在构建配置里太小了加载大场景会崩太大了又占用浏览器标签页导致整体卡顿。Unity 的Memory Size默认是 512MB我这次玩的是 2D 塔防256MB 就够用了。调完这个数值之后有个直接好处浏览器打开页面时的初始内存占用明显下降。加载时间是另一个用户体验问题。除了上面提到的压缩格式AssetBundle的设计也能显著摊薄首包体积。我的项目里有几十张关卡背景图如果全部打进第一个包启动加载要等好几秒。后来我把每关的背景和音频做成了独立 AssetBundle首包只留下核心 UI 和第一关资源加载速度提升肉眼可见。不过 AssetBundle 的加载路径在 WebGL 下也要注意不能直接读本地路径要用UnityWebRequestAssetBundle请求服务器路径。性能方面2018 年的老代码在浏览器里跑最大瓶颈往往不是 CPU而是 DrawCall 和贴图填充率。塔防游戏里塔和怪物的动画我用的是旧版 Sprite 动画一个角色有几十帧序列图如果全部在一屏内播放填充率压力会很大。这时可以用 Unity 自带的动态图集Sprite Atlas把散图合并极大降低 DrawCall。我们把怪物和小兵相关的贴图做成了图集效果立竿见影。还有一个容易被忽略的点是计时器。浏览器对后台标签页的 setTimeout 有限频Unity WebGL 在切换到后台时.net的Time.deltaTime可能会异常跳变导致怪物走位、塔攻击节奏出问题。后来我看社区经验在OnApplicationFocus和OnApplicationPause回调里做了游戏暂停处理才把这个偶发问题压下去。4. 常见问题与排查技巧实录4.1 浏览器加载阶段的问题速查现象常见原因排查与解决路径页面打开后一片空白控制台无报错WebGL 上下文创建失败多为 GPU 驱动或 WebGL 2.0 兼容性问题关机重启显卡驱动或在 Player Settings 里改回 WebGL 1.0报Unexpected token 或 JSON 报错服务器把.wasm/.data文件当文本返回了MIME 类型错误在 Nginx 或托管平台配置正确的 MIME或改用文件名里不带自定义后缀的构建方式一直卡在加载界面进度条不动资源压缩后服务器未配置对应的Content-Encoding检查响应头是否有Content-Encoding: gzip没有就改回 Disabled 重新构建加载到一半Unity 主程序崩溃.data文件太大浏览器单次加载内存峰值过高压缩资源体积、开启AssetBundle拆分首包、调低 Memory Size刷新后存档丢失浏览器隐私模式 / IndexedDB 不可用 / 跨域 iframe启动时做写入校验不可用就友好提示避免在 iframe 中跨域运行中文字体显示成方块动态字体没打到包里或浏览器不认某种字体格式把字体子集化或用系统字体或切换为图片字这里面最让我无语的是 MIME 类型问题。第一次我自己在本地起了个 Python 静态服务器打开页面一直报Compressed Archive ... invalid我以为是压缩包坏了后来才发现 PythonSimpleHTTPServer没有给.wasm设置对的 MIMEUnity 加载器直接把它当文本试了。后来换 Nginx 并加了几行mime.types配置才解决。4.2 运行时崩溃与渲染问题实录有一次玩家其实就是我自己玩到第三关时画面出现大片闪白关掉所有特效继续玩反而没事。后来逐个排查发现是某个特效 Shader 里的Blend One One在 WebGL 2 下表现异常改成标准的Blend SrcAlpha OneMinusSrcAlpha后就好了。这种问题很旧项目新项目用 URP 或 HDRP 不会踩但内置渲染管线的 Shader 跨平台就是容易这样。另外老项目里用Camera.onPostRender或者OnRenderImage做后处理的地方在 WebGL 下可能失效或性能很差。因为后处理需要额外的 RenderTexture如果没有兼容 WebGL 的格式部分ARGB32/RGBA32可以但带深度的就可能有问题会出现黑屏或渲染丢失。我的塔防项目里有个低分辨率模糊特效构建后画面糊成一团最后直接把特效找了个替代方案。运行时崩溃里最隐晦的是代码裁剪问题。有一次构建出来的版本在编辑器里一切正常网页端一运行就报NullReferenceException后来发现是Strip Engine Code把某个通过字符串拼接调用的事件系统给裁剪了。从那以后我构建 WebGL 版本时配置link.xml保留要动态调用的类和Assembly-CSharp相关程序集稳定多了。4.3 浏览器选择与内存占用杂谈好多朋友问我Edge 打开游戏时内存占用巨大是不是游戏的问题。这得分开看。Unity WebGL 的 wasm 堆会在启动时按照Memory Size预留内存如果这个设置是 1024MB那浏览器任务管理器里必然显示大几百 MB 占用。游戏玩起来以后如果页面写得不好每帧都创建 GameObject内存还会涨。建议先把Memory Size调到 256MB 或 512MB多测几个场景找到“不崩但够用”的临界点。还有一个 Chrome 打开网址闪一下变空白的案例多是 WebGL 上下文创建失败。这跟显卡驱动、浏览器版本、硬件加速开关都有关系。最简单的处理方式先在 Chrome 设置里打开“硬件加速”刷新页面试试不行再在 Player Settings 里手动勾选到 WebGL 1.0。我的经验是ARM 芯片的 Mac 和新版 Chrome 对 WebGL 2 支持很好反而是老 Windows 集成显卡有时翻车。Edge 的内存占用问题如果在多开标签页的情况下测数据会很吓人但它可能是浏览器本身的预加载机制。我用同一个游戏在 Chrome、Edge、Firefox 各跑一遍内存占用差距可以忽略。真正优化内存还是得回到 Unity 侧做资源管理。5. 这次移植的额外收获与扩展玩法5.1 一次“数字孪生”式的老项目复活圈里最近聊“数字孪生”聊得很多但对我来说这次把 2018 年的 Unity 项目搬进浏览器本质上也是一次数字孪生——把上个时代的二进制逻辑在今天的浏览器沙箱里重新还原并且让用户用现在的设备直接访问。这个过程让我再次意识到Unity 项目资产的变现能力其实比很多人想象中强。几年前的 Demo 看似过时但只要逻辑还完整它依然可以成为新的演示工具、教学案例甚至直接打包成 Web 版本发给客户看。这一次我甚至连个像样的营销页面都没做就是直接把链接丢给朋友对方 Chrome 打开就能玩这种体验和让他下载安装包完全是两个量级。5.2 AI 编程辅助的边界与实用姿势很多人担心 AI 编程会让人手生但这次实际用下来我的感受是AI 反而是老项目扩展维护的最佳外挂。在应对“这个 API 在新版本里改名了”“这段代码在 WebGL 下应该怎么写”这类问题时AI 几乎是一问一个准极大减少了搜索时间。但AI也有让人难受的时候。比如它有时会一本正经地给出一个“看起来正确但实际不存在”的 Unity API直接复制进代码就编译失败。我处理的方法是让它先给官方文档链接再根据文档内容写代码。如果它给不出链接我就当它在编答案。这个习惯帮我省了很多无效修改。还有个小技巧把 Unity 的报错原样粘贴给 AI它会比搜索引擎更快定位到具体代码行尤其是 IL2CPP 那堆长长的堆栈信息。我之前遇到一次ExecutionEngineException: Attempting to call method ... for which no ahead of time (AOT) code was generated不懂行的直接破防AI 却能解释说是泛型或反射触发的 AOT 裁剪问题并对症下药。5.3 从塔防到行业应用Unity WebGL 还能这么用这次能搬成功超乎意料的是它顺带把我的知识面拓宽了。Unity WebGL 近年不只是游戏圈在用很多数字孪生、智慧园区、交互演示项目都开始用 Unity WebGL 发布因为它能做到浏览器里直接看 3D 模型、漫游、点击交互不需要装客户端。比如工业设备交互说明书、线上展厅、医疗手术演示用 Unity 做一套WebGL 直接嵌入官网访客零门槛浏览这对很多传统行业来说非常实用。我在想如果当初项目里不只有塔防玩法还包含设备状态模拟、数据可视化看板那这次迁移的价值就不是“怀旧”而是直接能当个轻量级数字孪生原型了。不过这类应用要特别注意 WebGL 的沙箱限制。如果项目里用了 TCP、Socket、UDP 这类原生网络接口浏览器里基本不可用。要实时数据只能走 WebSocket、HTTP 轮询或者用UnityWebRequest发 REST 接口。我这次塔防里没有这类需求但如果你以后接设备数据一定要在架构设计时就把“浏览器不能为所欲为”这个前提记在脑子里。6. 最后分享一点个人实操体会这次两个小时能用 AI 把 2018 年的 Unity 版保卫萝卜搬进浏览器回头想想最关键的不是有多少代码能力而是“拆解任务”的耐心。老的WWW类、旧版 UI 事件、动态字体、本地存档每一个单独拿出来都不是大问题但堆在一起没有流程会把人压垮。我的流程就是分平台编译、最小构建验证、逐步加回功能、最后处理存档和部署。每一步都卡死“能跑起来”的小目标而不是一口气追求完美。如果你也想干类似的事我的建议是先看看项目有没有用早年那批“死了的第三方插件”这是最大的编译报错来源。其次是先花十分钟做资源体检把超大贴图、包含 WAV 音频、动态字体的清单拉出来再决定怎么压缩。最后才是开 Player Settings 调构建参数、选压缩格式。还有一个良心建议给浏览器端做项目时别把眼睛只盯着 Chrome。迭代了几轮之后我才发现 Firefox 对 WebGL 的某些默认设置跟 Chrome 不一样同一个项目在 Chrome 完全正常Firefox 的阴影效果会糊掉。所以至少准备 Chrome 和 Firefox 两个浏览器做回归测试不同机器上显卡驱动带来的差异也很玄有条件就把开发机、公司电脑、旧笔记本各跑一遍。好就说这么多。这次移植最大的快乐不是“游戏又能玩了”而是我发现只要愿意耐心拆解再老的项目都有机会在新环境里活过来AI 能帮你扫路上的坑但方向还是得自己定。如果你手头也有个吃灰的老 Unity 项目真的可以试试这个思路用不了两个完整工作日你就会重新看到当年做它的那股热情。