Unity老项目迁移WebGL实战:AI辅助两小时上线浏览器塔防
2018年我闲着没事用Unity写了个类保卫萝卜的塔防小demo敌人沿着路径走炮塔自动攻击漏怪就扣血攒金币造塔最高守到第20波。前几天整理作品集翻出来想着“要是能直接放到网页上给人点开就玩就好了”。本来以为这种老工程搬上浏览器调试编译环境少说也得折腾两三天结果在AI的辅助下从改代码到Unity WebGL构建再到部署上线前后大概两个小时。这篇就把完整过程做个复盘AI在迁移里能承担什么、Unity的老项目适配浏览器有哪些绕不开的坑、哪些事AI真的帮不上忙。如果你手里也有一个吃灰的Unity旧项目想搬进Chrome、Edge这类浏览器里跑这篇基本可以当操作手册用。1. 先盘家底这个老塔防项目到底能不能搬1.1 2018年的工程里有什么动手之前先把这个工程项目翻了个底朝天。所谓“Unity版保卫萝卜”核心玩法就是最经典的塔防三件套路径、敌人、炮塔。场景是一张二维俯视地图敌人沿着预设路点从出生点走向终点玩家在道路两侧的格子放不同功能的炮塔比如单体高伤、范围减速、溅射AOE。每波敌人数量、血量、速度都由配置文件控制每打死一个敌人给金币金币用来升级炮塔基地血量归零就失败。文件结构上整个游戏不算大核心C#脚本大概十几个Enemy.cs负责敌人移动和血量Turret.cs负责索敌和发射子弹WaveManager.cs负责一波一波生成敌人GameManager.cs负责金币、基地血量和UI状态另外还有几个辅助类处理对象池和路径点。UI全部用UGUI没有Timeline没有复杂Shader没有物理引擎的刚体碰撞——敌人移动用的transform.position direction * speed * Time.deltaTime子弹检测用的Vector3.Distance。这种“怎么简单怎么写”的写法后来证明是能顺利搬进浏览器的最大福气。在开始让AI介入之前我先自己做了一次手动“体检”。检查清单包括项目用的Unity版本、是否有第三方插件、是否用了System.IO读写文件、是否开了多线程、脚本中是否出现平台相关API。这个体检不需要多深只要把项目里Assets目录下的脚本梳理一遍标记出可能涉及文件系统、进程、反射、动态编译的用法。塔防项目里最可疑的是读敌人配置和存档这两个功能前者用了StreamReader.ReadAllText后者用了二进制FileStream。看到这两行的时候我基本就清楚后面要改什么了。1.2 浏览器端的技术选型为什么仍是Unity WebGL有人可能会问既然都要搬到浏览器为什么不干脆用HTML5重写或者转成Phaser、Cocos说实话如果目标是从零做一个新的网页塔防选Web技术栈当然合理但我的目标是“把2018年的老工程搬过去”不是“用新语言重写一个”。在Unity、Cocos、Godot这些具备Web导出能力的引擎里最不折腾的路径就是Unity自带的WebGL构建可以直接把现有场景、预制体、材质、动画状态机导出成浏览器能跑的WebAssembly不需要重写任何浮现于表面的游戏逻辑。当然Unity WebGL并不是“一键搬运万事大吉”的银弹。它本质上运行在浏览器的沙箱环境里这意味着原来Windows/Mac上能用的不少“本地能力”都会失效文件系统变成浏览器私有化的IndexedDB多线程不可直接使用完整的System.Threading动态链接库和反射受限内存被限制在一个固定的可寻址范围里。所以技术选型可以提前定但适配工作必须一项项做。把旧工程当成一个“能跑但可能跑不到浏览器里”的候选项目然后用AI辅助逐步消除不兼容点这是我这次迁移的整个思路。2. AI提效的关键节点两小时都花在哪了2.1 第一段让AI先做“迁移体检”拿到工程清单后我没有急着让AI改代码而是先把项目整体结构、关键脚本内容、使用到的Unity模块摘要一起丢给AI让它做一轮“迁移体检”。这一步看似简单但非常唤醒效率。我用的提示词大概是这样的“我有一个Unity 2018年开发的2D塔防项目主要组件有UGUI、Animator、AudioSource、PlayerPrefs、System.IO现在要迁移到Unity WebGL平台。请帮我审查这些代码里在WebGL下可能报错或失效的API按严重程度排序并为每一项给出替换建议。”AI很快就给出一份清单。最严重的是FileStream和StreamReaderWebGL环境下根本没有本地文件路径这两类API调用会在运行时直接失败。其次是System.Net.HttpWebRequest如果代码里有的话也得换成UnityWebRequest。还有Application.dataPath的返回值语义在WebGL下变得奇怪不能靠它去读可写的配置文件。这些内容我其实心里有数但让AI帮我整理成清单等于省掉了对着文档逐条核对的时间并且它还额外提醒了对象池里使用到的“在脚本运行时动态加载AssetBundle”在WebGL下的注意事项——虽然这个项目没踩到但这个提醒帮我在脑子里拉出了一条“不要突破包体边界”的线。这里多说一句。AI给出的“体检报告”不能全盘相信权贵是先把报告里的每一条和原始代码对应一遍。比如它曾建议“把PlayerPrefs替换成File.WriteAllText到Application.persistentDataPath”这在WebGL下同样有问题因为WebGL根本没有本地文件写入正确做法是PlayerPrefs本身或者专门使用IndexedDB封装。AI懂大面上的知识但容易忽略浏览器沙箱的特殊性这一步人工校验不能省。2.2 第二段不兼容代码的批量替换体检完之后真正的体力活就是改代码。Unity的C#脚本在WebGL下能够兼容大部分常用库问题集中在那几处平台相关API上。我让AI生成了两个补丁一个是把读配置文件的方式从StreamReader改成在项目里放TextAsset再用Resources.Load读取另一个是把存档读写从二进制文件改成JSON字符串配合PlayerPrefs存储。这里有个小经验不要直接把整个旧文件扔给AI让它“重写”因为它很可能把逻辑改得面目全非或者引入新的命名空间冲突。我采用的方式是手动指定改动范围告诉AI“只修改ConfigReader.cs第30行到45行的读文件方法保留类的对外接口不变使用Resources.Load读取函数返回类型和参数都不能变”。这样AI生成的替换代码就能像“换零件”一样直接替换进原工程不会牵连游戏里其他地方调用这个类的方法。以读配置为例原来是这样using System.IO; public static string LoadConfig(string fileName) { string path Application.streamingAssetsPath / fileName; return File.ReadAllText(path); }改成兼容WebGL的版本using UnityEngine; public static string LoadConfig(string fileName) { TextAsset asset Resources.LoadTextAsset(fileName); if (asset null) { Debug.LogError(Config not found: fileName); return string.Empty; } return asset.text; }Resources.Load选中的文件会打进构建包所以它读的是只读配置不能再动态写入。好在塔防的配置都属于“关卡设计”类静态数据不需要玩家运行时修改这样替换完全没有副作用。存档部分则把对象序列化成JSON类似JsonUtility.ToJson(saveData)再塞进PlayerPrefs.SetString(SaveData, json)。WebGL的PlayerPrefs底层会自动落到浏览器的IndexedDB里刷新页面后数据还在这就解决了“浏览器不能写文件”的天然限制。替换完成后让AI做了一轮“静态检查”把所有脚本里出现过的System.IO、System.Net、System.Threading关键字都扫了一遍逐个判断是否还在用最后确认只剩System.Collections、System.Collections.Generic这些安全的命名空间。这一步处理完之后代码层面已经具备构建WebGL的基本条件。2.3 第三段构建、排错、调通出包代码改完接下来是Unity编辑器里的工程配置。首先要确认安装了“WebGL Build Support”模块这个在Unity Hub里就能勾选不过我因为旧工程是用2018版本建的直接打开后Unity提示需要升级顺手把项目升到LTS版本升级过程还算平稳UGUI和Animator都没有出现破坏性变化。然后进入Build Settings把平台切到WebGLPlayer Settings里做几个关键配置Publishing Settings / Compression Format选择Brotli体积最小部署到支持Brotli的静态服务器上加载会快不少。Other Settings / Code Optimization选Release配Development Build暂时不打勾先以最快方式出包验证逻辑。Other Settings / Color Space保持原来的Gamma或Linear实际上2D塔防用Gamma即可贴图不用太纠结算子。内存方面我手动把WebGL Memory Size上限调成256MB。这个塔防项目小建筑和敌人加起来的模型面数不多贴图也都是512以内的256MB绰绰有余。第一次构建很顺利但运行时立刻遇到一个经典问题浏览器里打开生成的index.html加载画面走完就卡在灰屏控制台报Failed to load file: Build/xxx.data。这里原因很多常见的是静态服务器没有正确设置Content-Encoding导致Brotli压缩后的数据无法解压。我直接把报错信息复制给AI它提醒我先看DevTools的Network面板确认.data文件的响应头是否包含Content-Encoding: br。我检查后发现用的本地预览工具没处理这个头重新用npx serve这类会正确配置压缩的静态服务器后游戏立刻正常弹出来。 AI在这一步虽然没有直接替我把服务器配好但让我的排查路径从“瞎猜Unity版本问题”转向了“检查HTTP响应头”方向对了后面自然顺。3. 真正决定改命的WebGL适配细节3.1 存档与文件系统IDBFS和PlayerPrefs的关系很多Unity老项目在搬WebGL时第一道坎就是存档。开发者习惯了PC上直接写Application.persistentDataPath下的文件到浏览器就傻了。浏览器没有本地目录Application.persistentDataPath在WebGL下虽然有值但它指向的是由Emscripten模拟出来的虚拟文件系统默认并不会自动持久化到浏览器。要想真正做到“关掉网页再打开数据还在”要么用PlayerPrefs要么配合IndexedDB封装一个文件系统模块。这是我在这次迁移里反复和AI确认的点。AI给出的“把存档写成json然后存到Application.persistentDataPath”其实是个陷阱在WebGL这样写是存不到持久化位置的刷新之后就没了。正确做法是直接用PlayerPrefs.SetString存JSON。PlayerPrefs在WebGL实现里会自动走浏览器的IndexedDB数据量小的场景完全够用。如果把整个塔防的进度、金币、解锁关卡都序列化成一个JSON字典撑死几十KB用PlayerPrefs没任何问题。如果你确实需要更复杂的文件结构比如关卡编辑器要导出地图、玩家要自定义皮肤导入Unity WebGL社区通常建议直接用IndexedDB相关JavaScript插件和C#互操作这就是网上经常看到的idbfs字样的来源。Unity的WebGL支持里自带一个“IndexedDB文件夹同步”方案需要在构建后往生成的JS里挂载一个文件系统持久化逻辑这套东西对老项目来说有点重不建议刚迁移就上。这次我选择PlayerPrefs方案就是用最小代价解决核心痛点等以后真要做自定义分享地图再在架构上多考虑宿主环境。3.2 内存和包体浏览器跑Unity的硬约束WebGL版的Unity运行时是单线程的、固定内存的。不像PC桌面可以动态申请内存浏览器里给Unity分配的内存是一块预先申请的ArrayBuffer项目加载时就会锁死。这意味着如果游戏内容超出预设上限轻则加载阶段崩溃重则运行时频繁报OutOfMemory。对塔防这种对象数量不夸张的游戏真正的风险其实在包体。2018年时的项目资源管理比较粗放一堆未压缩的PNG纹理、几段WAV格式音效直接构建出来.data文件可能有几百MB浏览器加载会慢到让人直接关掉页面。我在AI的建议下做了一轮资源瘦身所有UI贴图压缩成PNG或JPG把2048的大图降到1024或512音频转成压缩的OGG格式删掉场景里没有用到的空对象和冗余材质。构建产物从最初的可笑500MB降到不到30MBBrotli后进一步压到十几MB日常加载速度已经可以接受了。这里也推荐用一个技巧在WebGL的Player Settings里开启“Strip Engine Code”。它会剔除用不到的Unity内置模块塔防这种UI音频2D的项目开了之后包体又能小一圈。如果项目用到反射或者动态代码很少这个开关基本没有副作用。3.3 UI、输入与全屏别在最后一步翻车游戏逻辑在浏览器里跑起来后剩下最容易阴沟翻船的就是UI适配和输入事件。2018年那个塔防的Canvas是固定分辨率写的是1920x1080到浏览器窗口里如果直接等比缩放在手机或窄窗口上会出现UI被截断、按钮点不到的情况。解决办法是用Unity自带的Canvas Scaler把UI Scale Mode设为“Scale With Screen Size”参考分辨率设为1920x1080匹配模式一般选0.5或根据UI布局微调。这个操作不涉及写代码但在老项目里经常被忽略。输入方面鼠标点击和拖拽在Unity WebGL默认就支持但有两个细节需要额外处理。一是浏览器会给鼠标右键弹出菜单如果你在游戏里用右键锁定目标或取消建造必须调用Input.GetMouseButtonDown(1)然后在Canvas上挂一个脚本阻止contextmenu事件默认弹出。二是滚轮缩放地图浏览器也会默认把滚轮当页面滚动如果页面本身有滚动条就会出现奇怪的双层操作。比较干净的方案是让Unity的index.html页面固定高度全屏无滚动配合Canvas铺满窗口。全屏也是个很容易被忽略的点。不少用户点开游戏后想全屏玩我一开始直接在启动按钮里调用Screen.fullScreen true结果在Chrome里没有任何反应控制台还提示“Fullscreen request requires a user gesture”。因为浏览器的沙箱规定必须由用户主动交互比如点击按钮才能触发全屏不能页面加载后主动全屏。所以我把全屏功能改成挂在游戏内的一个“全屏按钮”上用户点击时再调用Screen.fullScreen true这个限制就绕过去了。4. AI帮了多少又在哪些地方“一本正经胡说八道”4.1 真正省时间的三个场景这次两个小时能走完AI在三个场景里确实立了大功。第一个是批量代码清理。用正则和脚本逐一替换旧API本来是个很机械、很费眼的事AI能把“找出来-替换-检查”的流程变成一次对话。比如我让它扫描所有脚本中StreamReader相关的调用它不仅能列出文件列表还能直接生成对应的Resources.Load版本替换片段节省的时间非常明显。第二个是构建报错的快速定位。WebGL的报错信息有时候很绕比如abort(Error: Assertion failed: undefined)这种单靠搜索引擎很容易陷入“每个人都遇到但没人说清楚”的死胡同。AI能结合上下文推断问题方向比如它提示我去检查音频格式是否被浏览器支持、贴图尺寸是否超过WebGL纹理上限这比我自己漫无目的地翻论坛高效得多。第三个是将旧代码里的隐式平台假设可视化。我在让AI梳理Application.dataPath、StreamingAssets、Environment.CurrentDirectory这些调用时它给出了“哪些在WebGL下不可靠、哪些需要改”的表格让我意识到除了文件读写还有几个隐藏的坑要提前处理。这些东西其实文档里都能找到但AI等于替我读了一遍文档并把结论直接贴到对话里。4.2 需要警惕的三个AI回答陷阱不过AI也不是万能这个项目里我就遇到三次它“一本正经胡说八道”的情况这里分享出来大家可以避坑。第一次是它建议用File.WriteAllText写到Application.persistentDataPath来实现WebGL存档。前面已经说过这在WebGL的虚拟文件系统下根本不能跨页面持久化属于典型的“知道API名字但不知道平台差异”。第二次是它生成了一段读取StreamingAssets的代码让构建时把配置文件放到StreamingAssets文件夹再用UnityWebRequest读取。其实WebGL可以使用UnityWebRequest读StreamingAssets但路径处理方式比较简单不如直接用TextAssetAI给的方案反而绕了一大圈。第三次是它试图“修复”一个根本不存在的报错因为我在问题里描述得不够精确它先入为主认为项目用了DLL动态库生成了和项目需求完全不相关的建议。这些坑给我的直接经验是和AI协作时必须提供充分的“上下文约束”。比如明确告诉它“项目没有使用任何原生插件”“WebGL平台没有本地文件系统”“Unity版本是LTS的2022系列”这样它生成的内容会更聚焦。另外AI给出的代码改动最好逐行review尤其是涉及文件、网络、内存这类平台相关特性的部分官方文档永远是最高的裁判。5. 部署到线上与验收清单5.1 发布目录的结构和本地验证Unity做完WebGL构建后会在输出目录里生成一个完整的Web项目包含index.html、若干.js、.wasm、.data等文件。这个目录结构不能随便改尤其是文件名带有随机哈希后缀的构建产物因为index.html里的加载器脚本是按固定名字去引用它们的手动改名会导致加载失败。在部署到公网之前我习惯先在本地跑一个静态服务器验证而不是直接双击打开index.html。原因很简单浏览器出于安全限制直接以file://协议打开时Unity WebGL的很多请求会因跨域或本地文件策略而失败。一个快速有效的本地验证命令是npx serve .它会开一个本地的HTTP静态服务器默认端口3000然后再访问http://localhost:3000打开游戏测试开局、建造炮塔、敌人走到终点、存档刷新重载这几个核心流程。如果本地能正常跑完一遍再往线上部署。5.2 线上部署和兼容性检查线上部署这块我图省事直接把构建产物扔到GitHub Pages。这种静态托管的好处是自带HTTPS和全球CDN不需要自己折腾服务器。注意用Brotli压缩的话托管平台需要能正确返回对应的Content-Encoding头GitHub Pages这一类主流平台基本都支持。如果不行可以把Compression Format改为Disabled代价就是包体变大加载慢一点。真正要用心的是兼容性测试。我在Chrome、Edge、Firefox里各跑了一轮重点看三件事字体渲染、音频自动播放、全屏切换。Firefox里WebGL的旧版音频策略会更严格音频需要用户先点击一次页面才开始播放所以我在游戏开始界面放了一个“点击开始”的按钮顺便解决了这个兼容问题。另外浏览器窗口尺寸从1920宽调整到768宽时UI布局不能出现按钮消失或文字溢出这个也得逐项检查。如果条件允许建议在手机上再打开一次同一个页面。移动端浏览器对WebGL的支持好坏差异很大虽然我没做专门的移动端适配但用Canvas Scaler的缩放方案之后竖屏体验也还能玩。只是炮塔点击范围比较小需要把按钮的点击热区做大一点这个用Graphic Raycaster和透明Image就能处理算是塔防搬到移动端比较典型的优化点。5.3 这个项目还能怎么继续玩既然都搬到浏览器了后续的扩展空间一下子就打开了。比如给塔防加一个关卡编辑器的Web版用一套简单的JSON格式保存地图然后通过PlayerPrefs存到每个玩家的浏览器里玩家之间可以复制分享码互相体验或者把加载页做成带进度的沉浸式开场让作品集展示更精致一点再进一步衔接排行榜功能把单局最高波数提交到后端数据库就变成了一个能持续吸引玩家回访的小游戏页面。不过就我个人的建议从“旧项目迁移到WebGL”这个目标来看现在已经够用了。先把能玩的版本挂到线上让别人点开就能玩就已经完成了80%的价值。后续要不要加功能完全看这个项目在你的作品集里承担什么角色——如果是拿来展示技术能力那做得完整一些值得如果只是想验证“能不能搬”那及时收手把精力放到新项目上更高效。这个WebGL塔防上线后我在浏览器里反复试玩了几次发现旧代码里有一个延迟很影响手感炮塔索敌用了每帧遍历所有敌人敌人一多就明显卡顿。我用AI帮我分析热点发现不是索敌本身慢而是每帧创建了很多临时Vector3和List分配造成GC压力。于是改成用缓存数组和对象池复用帧率立刻从40多帧拉回满帧。这个优化放在桌面端可能感知不强但浏览器里性能余量小收益非常直观。最后再分享一个小技巧WebGL构建出来后记得检查index.html里的加载进度条样式Unity默认的进度条很简陋简单改一下CSS在等待加载的几秒钟里也能给玩家留下不错的印象。很多时候玩家愿不愿意点第二下就在这点启动体验的细节里。