Unity老项目迁移WebGL实战:两小时将塔防Demo搬进浏览器 开头就不另起标题了咖啡还没来得及凉项目已经从Unity工程变成浏览器里能直接跑的一关塔防。我说的就是这个事2018年写的一个Unity版“保卫萝卜”Demo三个月前还躺在硬盘里连工程文件都快忘了这次周末抽了两个小时把核心流程选塔、铺塔、出怪、打怪、收金币完整搬到了WebGL上顺手把存档、全屏、移动端适配也收拾干净了。整个过程我没手写超过五十行新代码大部分改动用的是AI辅助——先让它读脚本、分析报错、给我补WebGL兼容层的胶水代码我再对着运行效果做取舍。今天把这套流程完整复盘出来不吹效率重点说清楚哪些步骤AI是真能省时间的哪些步骤你交给AI就是给自己挖坑以及Unity 2018这种老工程迁移到WebGL时躲不开的几道坎到底怎么迈过去。如果你手头也有一个几年前的Unity小游戏想搬到浏览器里复用这篇文章可以直接当操作流程参考。1. 动手前先盘家底2018年的Unity项目要搬什么1.1 先搞清楚这个项目是用什么版本、哪些库写的别一上来就切WebGL平台点构建第一件事是把工程在Unity里打开看一眼版本。我那个项目当初用的是Unity 2018.4.36f1这个版本恰好是2018系列最后一个LTSAPI相对稳定但和如今的Unity 6在渲染管线、包管理器、序列化格式上差别不小。如果你的项目比这还老比如2017、2016那第一步不是迁WebGL而是先考虑是否值得升级到现代版本。我的建议是如果项目不复杂保留老版本直接发布WebGL反而省事如果用了很多第三方插件升级成本远比迁移高那就要掂量一下了。看完版本之后第二步是盘脚本与依赖。打开Project窗口把Assets目录过一遍重点找三类东西脚本引用了System.IO、System.Net、System.Threading等桌面API这些在WebGL下基本都不支持或需要特殊处理用到了MonoBehaviour生命周期之外的东西比如多线程、Socket通信、反射WebGL环境是单线程沙箱限制很多第三方插件比如A*寻路、DOTween、NGUI或旧版UI插件有些在WebGL下开箱即用有些需要改设置。我这个项目里最典型的问题是存档代码用了File.WriteAllText和Directory.CreateDirectory桌面Windows上跑得飞起但WebGL的沙箱系统压根不给你直接访问文件系统后面折腾IDBFS的时候我会细讲。另外一个容易被忽略的点Unity 2018之后WebGL构建默认使用的是IL2CPP后端你那些用C#写的脚本会先编译成C再交叉编译成WebAssembly和编辑器里用Mono跑是两套完全不同的运行时。所以编辑器里没报错不代表WebGL构建能过老项目尤其如此。1.2 WebGL和桌面构建的底层差异很多人以为WebGL就是把Unity游戏“导出”成网页版点一下Build就完事。真不是这样。从技术底层看Unity WebGL把游戏打包成了一个带Unity引擎运行时的WebAssembly模块外加一个HTML加载器和一堆资源文件。游戏逻辑通过IL2CPP被转换成C再编译成wasm所以之前C#里那些反射、动态代码生成、P/Invoke操作会变得非常脆弱甚至直接崩掉。同一套代码放到WebGL里最大的差异在四个方面一是IO。桌面端可以读写任意路径WebGL只能靠PlayerPrefs或IndexedDB二是网络。如果你用WWW或UnityWebRequest加载远程资源WebGL必须遵守浏览器同源策略跨域得配CORS头三是多线程。WebGL早期版本不支持托管线程现在虽然可以通过Web Worker模拟但Unity的很多API在WebGL下依然默认主线程执行你在Update里开个Thread做寻路桌面没问题WebGL直接给你异常四是内存。Unity WebGL把所有运行时内存限制在一段连续缓冲区里默认上限通常是2GB但实际因浏览器、设备而异分配失败就直接白屏崩溃。这也就解释了为什么很多老项目第一次构建出来能打开菜单但一进游戏加载关卡就嘎嘣弹错或者慢得离谱。资源加载方式、内存占用、IO策略这些都是桌面平台不敏感但在WebGL下会被无限放大的问题。1.3 迁移难度评估的三个关键信号在动手改代码之前我建议你花二十分钟做一次“难度体检”只盯三个信号就能判断这个项目两小时能不能迁完第一个信号是读写本地文件的频率。如果游戏只在开局存一次档、读一次配置那问题不大换成PlayerPrefs就行如果是那种每回合自动写日志、离线收益要频繁写盘的设计迁移成本会几何级上升。第二个信号是依赖第三方SDK的程度。你的项目里有没有用微信SDK、实时语音、原生支付之类的插件这些在WebGL下统统不可用除非自己再实现一套HTTP对接方案工作量不是两小时能搞定的。第三个信号是资源体量和纹理格式。几百MB的AssetBundle几千张大尺寸纹理这种项目就算AI再能改代码加载时间和内存也扛不住。塔防游戏好就好在资源量小、逻辑围绕预制体和脚本展开天然适合WebGL。我当时评估后觉得可行主动砍掉的功能是动态加载的远程关卡包、本地截图分享功能、还有基于System.Drawing的缩略图生成这个在WebGL下更是天方夜谭。砍完核心流程干净了很多这也是两小时能做完的前提。迁移WebGL不是复制是做减法加适配。2. AI在这场移植里到底能干什么别把大模型当神当实习生用2.1 让AI先“读一遍”整个项目的正确姿势最早我也犯过傻把整个Assets文件夹拖进AI输入框让它“帮我移植”。结果平台直接拒绝就算能接受几千行脚本混在一起也没法用。后面摸索出来的正确姿势是先手动做一次结构梳理再分模块丢给AI。具体操作是我先在本地打开工程按照功能目录列一个清单比如“Scripts/Audio/、Scripts/UI/、Scripts/Tower/、Scripts/Enemy/”然后挑出核心脚本按依赖顺序发给AI。发给AI时不要只粘贴代码要给它上下文我写的是类似下面这样的提示“这是一个Unity 2018.4的塔防游戏项目。Scripts/Tower/Turret.cs控制炮塔的锁定目标、旋转和发射子弹由Bullet.cs负责移动和伤害。现在要把项目迁移到WebGL请重点检查以下代码在Unity WebGL平台下可持续运行吗有没有非兼容API需要替换的话给出最小改动方案。”AI在这种带明确上下文的提问下给的回答准确率高很多。它会直接指出Application.dataPath不能用于写入持久化数据、WWW已经被淘汰换成UnityWebRequest这些虽然我都知道但省下的搜索时间是真的。还有一点体验很好AI能做“批量静态替换”。比如一个工程里几十处用了File.WriteAllText存储存档我让AI写一个带方法签名一致的FileSafe类内部自动判断平台WebGL下走PlayerPrefs桌面下走原文件逻辑。这个类AI几十秒就生成了我自己写少说也要十分钟还得调试。AI适合干这种体力活不适合干判断题。2.2 AI能改代码但哪些事千万别指望它AI帮你改C#脚本、生成配置、解释编译报错这些都很靠谱。但有几件事你千万别交给AI否则会被带到坑里第一它看不见你的资源导入设置。比如你的Sprite图集、动画状态机、预制体绑定关系AI在纯代码层面是完全看不出来的。它给你分析“模型不在场景中”其实只是某个预制体的Mesh引用丢了这个问题AI永远发现不了只有打开Unity场景看着Inspector才能定位。第二它不知道WebGL构建目录下的服务器配置。很多问题其实不在代码而在于你托管构建产物的服务器没配.unityweb文件的MIME类型或者没有gzip压缩。AI给你的代码修复方案纯属多余你得自己去检查IIS、Nginx或某个静态托管平台的MIME映射关系。第三它缺乏“浏览器黑盒”的感知能力。WebGL白屏、页面无响应、内存溢出这些往往和C#代码沾不上边可能是浏览器版本、显卡驱动、WebGL Context丢失等原因。AI只能干瞪眼你必须手动打开DevTools看Console和控制台网络请求。所以我的原则是AI处理“文本层面的问题”我处理“运行现场的问题”。凡是能在代码里搜索、替换、比较的丢给AI凡是改完代码还要开浏览器验证逻辑的自己来。2.3 一套能复用的AI提示词模板给AI下需求也有固定套路。我用的模板基本是三段式项目背景一句话。比如“Unity 2018 WebGL项目塔防游戏”当前目标和报错信息。比如“发布后打开页面空白Console报Unable to load file from file://协议”约束条件。比如“保持现有场景结构不变最小改动不要重写整个系统”。举个例子我处理存档问题时是这样提问的“Unity 2018 WebGL项目游戏中用System.IO.File.WriteAllText把存档写到Application.persistentDataPath下。发布WebGL后运行时提示无权限写入希望改成在WebGL下使用PlayerPrefs来存储存档但保留在Windows编辑器下继续用文件存储的能力。请生成一个跨平台存档工具类并给出调用替换说明。”AI不到一分钟就生成了一个带#if UNITY_WEBGL和#elif UNITY_EDITOR条件编译的工具类我粘贴进工程改了几个方法名存档就通了。如果你连这个提示词都不知道怎么写效果就会差很多AI不知道前因后果就会给你一版天花乱坠但其实不满足约束的代码。3. 两个小时的实操流水账从本地工程到浏览器可玩3.1 第30分钟构建前清理与平台切换我切到Build Settings点Platform列表里的WebGL然后等Unity弹出一个“Switch Platform”提示。这一步会把当前平台从Windows/Mac切到WebGL需要重新导入所有资源时间一般3到10分钟。老项目资源多这一步慢是正常的。平台切换完成后先别急着Build。我先做了三件事第一关闭不必要的编辑器模块。Project Settings里的Player Settings把Scripting Runtime Version设为.NET 4.x EquivalentUnity 2018里可选Api Compatibility Level如果项目没有特殊需求就选.NET Standard 2.0兼容性更稳。第二设置压缩格式。WebGL发布时有很多压缩选项我选了Brotli需要在托管服务器上配置对应的Content-Encoding否则浏览器解不开。如果服务器配置不方便可以用Disabled或者Gzip但Build文件会大不少。第三检查Code Stripping。我起初勾了Managed Stripping Level为Aggressive结果部分反射代码被打包时给剔除了运行时报MissingMethodException。后面改成Medium才稳定这个建议后面再调前期别激进。做完这些我第一次Build用的是最低画质、关闭阴影、关闭抗锯齿。不要一上来就高画质先把管道跑通后面再逐项开回来。3.2 第60分钟第一版WebGL构建和最典型的三个编译错误第一次构建大概花了四分钟构建出来的是一个包含index.html、Build\xxx.data.unityweb、xxx.wasm.unityweb、xxx.framework.js的目录。我启动本地静态服务器试跑几乎立刻看到三个错误这算是Unity 2018项目转WebGL的标准三连第一个是Protocol Error或404。这是因为本地服务器没有给.unityweb后缀的文件设置正确的MIME类型Nginx默认当成application/octet-stream返回Unity加载器不认识。解决方式是在Nginx配置里加一行application/octet-stream unityweb;并顺带开启gzip。如果你用的是python -m http.server很多版本会自动处理但以后放到任何新服务器都别忘了这一茬。第二个是WebGL 1.0不支持某Shader特性。我这个塔防的敌人用了半透明材质标准Shader在WebGL 1.0下有些Pass不兼容表现为敌人变成紫色或者崩溃。解决办法是改成移动端Shader变体要么升级项目的手写Shader要么直接在Quality Settings里关掉实时阴影和HDR。这个时代Unity WebGL虽然也支持WebGL 2.0但2018版本默认特性集偏保守在浏览器里就别追求桌面级效果了。第三个是AudioSource播放无声或循环错乱。WebGL的音频系统不是完全实时流式有些格式比如Vorbis之外的WAV在不同浏览器表现不一致。Unity 2018发布WebGL时建议所有音频用MP3或OGG并关闭预加载以外的流式播放选项否则可能发生播放延迟甚至不出声。我把背景音乐转成OGG音效转成MP3重新导入后恢复。3.3 第90分钟IDBFS存档失败、布局错乱的现场处理第一版构建能跑起来之后我点“开始游戏”塔防第一波怪能出、塔能攻击但退出游戏再进存档没了。Unity编辑器下存档正常的代码到了WebGL里注销了控制台报错是类似写入IndexedDB失败或者权限不足。这就回到1.1里说的File.WriteAllText问题。WebGL运行时并没有传统文件系统Unity将FileAPI映射到内存文件系统只要页面一刷新数据全部归零。要想持久化你需要把文件系统挂载到IndexedDB上或者干脆用PlayerPrefs这种引擎级封装。我对这个项目采取的是后者——存档体量也就几十KB用PlayerPrefs存JSON字符串完全够用。AI帮我生成的条件编译工具类在这个环节发挥了全部价值。layout错乱的现场是另一种风格桌面分辨率下UI正常切到手机窄屏后左上角的金币标签和右上角的波次标签叠在一起。原因很直白2018年的UI用了绝对坐标没有做自适应布局。这部分AI没法看场景我手动改Canvas Scaler的模式为Scale With Screen Size再把关键UI节点的锚点从固定角落改成拉伸对齐花了一些时间。3.4 第120分钟性能优化与收尾流程通了以后剩下的时间全部用来做性能优化。塔防游戏最怕的是怪一多就掉帧。我做了四件事第一是对象池化子弹和敌人。2018年源码里敌人死亡会Instantiate一遍爆炸特效再多造出几十个敌人时新建GameObject的开销会被放大。我导了一个简单对象池类把子弹、敌人的生成和回收统一接管。AI帮我改写了调用点这一步大约二十分钟。第二是关掉不需要的光照和阴影。WebGL下实时阴影是最耗性能的Feature之一我把Quality Settings的Shadows全关光影全用不透明贴图和顶点色模拟帧率立刻提升一截。塔防这种俯视视角游戏其实根本不需要动态阴影。第三是图集化UI与Sprite。把散落的小图标打成图集减少DrawCall同时在不需要交互的Image上关闭Raycast Target减少事件检测开销。第四是设置WebGL内存上限。Unity 2018在Player Settings里可以直接设置WebGL Memory Size我给这个项目设了256MB。太大很容易让低端设备崩溃设置得当可以提前规避“内存增长到极限后页面白屏”的问题。最终版本在Chrome和Edge里都能流程稳定SteamDeck的浏览器模式我也顺手测了除了加载慢一点游戏内帧率稳定在55到60之间。4. 浏览器里跑塔防的三道坎存档、性能、交互细节4.1 存档不是PlayerPrefs那么简单IDBFS系列问题很多新手以为用了PlayerPrefs就万事大吉其实在WebGL下PlayerPrefs底层仍然走的是IndexedDB。正常情况下Unity会封装好读写你调用PlayerPrefs.SetString和PlayerPrefs.Save之后数据会被异步写进浏览器的IndexedDB。但有几个场景特别容易踩坑第一个是浏览器的隐私模式。隐私模式下IndexedDB可能是可写但隔离的一旦关闭隐私窗口数据就清空游客会觉得“存档我明明保存了为什么第二天没了”这个没法从代码层面完全规避顶多在UI里加一个“当前为无痕模式关闭浏览器后存档丢失”的提示。第二个是跨域隔离与第三方Cookie策略。如果Unity跑到iframe里一些浏览器默认禁用第三方存储PlayerPrefs写入会静默失败。这时你要么让接入方设置allowstorage-access之类的策略要么提示用户必须在顶层窗口打开游戏。第三个是玩家主动清浏览器数据。这个谁也拦不住。如果你的塔防游戏有付费道具或账号体系就不要把唯一存档放在本地浏览器必须走后端同步。我这次只是Demo所以本地存档就够了但如果是正式项目至少要把存档字符串做成可导出、可导入的文本让玩家自己备份能少很多售后问题。4.2 WebGL性能优化从对象池到DrawCall如果说桌面游戏的性能瓶颈往往在GPU那WebGL游戏先卡死的往往是CPU和内存因为主线程要同时承担游戏逻辑和浏览器事件处理。塔防场景下怪多、子弹多、UI刷新频繁最容易形成三个瓶颈GC分配、DrawCall、内存峰值。GC分配的优化方式是减少每帧的临时对象分配。比如不要在Update里用字符串拼接不要频繁创建List需要用到的容器提前复用。2018年的代码里有一个每帧统计金币加成的string.Format修改之后用StringBuilder缓存帧率在低端手机上提升了肉眼可见。DrawCall的优化方式前面提过图集和静态合批这里补充一句要时刻关注Frame Debugger。Unity的Window Analysis Frame Debugger可以看到每一帧的DrawCall是怎么提交的哪些资源没法合批一目了然。我检查后发现自己场景里最大的合批破坏者竟然是血条UI上的多个不同字体Texture统一字体和Graphic后再渲染一遍批次数从一百多降到了五十左右。内存峰值的问题则是老工程最深的坑。如果你的项目有一些不必要的场景预加载资源请把它们从Resources文件夹移除改成AssetBundle或Addressables按需加载。WebGL构建的文件虽小但运行时展开到内存可能膨胀好几倍。尤其塔防后面会有整波敌人预制体、多关卡的地图数据一次性全加载完中低端设备直接白屏。4.3 那些看起来很小却要命的交互细节界面能跑、性能也稳定不代表用户会觉得“好用”。WebGL项目最容易被吐槽的往往是这些边角第一是鼠标滚轮缩放和右键菜单。默认情况下Unity WebGL会把鼠标事件捕获到Canvas上但浏览器仍然可能弹出右键菜单。我在启动时加了一个oncontextmenu事件拦截同时给滚轮缩放设了上下限防止玩家把摄像机拉到地图外面导致穿过背景。第二是按钮的点击范围。如果你在代码里用OnGUI或者EventSystemUI元素的alpha为0的可点击区域经常被误伤。我之前在Unity里做了一个隐藏的“全屏下一波”按钮结果按钮的透明区域盖住了半屏AI是看不出这种物理体量的全靠手动在Scene里查射线检测。建议遇到交互异常时打开Scene窗口临时显示全部UI事件区域先看看有没有透明遮罩挡路。第三是自动聚焦与键盘输入。WebGL游戏如果嵌在页面里有时第一次点击后按钮才响应这是因为Canvas还没获得键盘焦点。解决办法是页面加载完成后通过外部JS调用Unity的focus()方法或者把Unity实例的canvas.tabIndex设为0。我自己把这个逻辑写进了HTML加载完成后的一段脚本里从根上解决了“点第一次没反应”的问题。5. 常见问题速查表遇到直接抄作业现象根本原因解决思路构建产物用file://打开白屏Console报跨域或加载失败浏览器禁止本地文件跨域读取WebAssembly用本地HTTP服务器托管目录访问比如python -m http.server 8080.unityweb文件404或加载中止服务器没配置对应的MIME或压缩头Nginx加application/octet-stream unityweb;并开启gzip/brotli游戏运行正常但退出后存档丢失使用了内存文件系统或PlayerPrefs被清WebGL下不要用File.WriteAllText改用PlayerPrefs若数据量大用IndexedDB封装进入战斗后帧率暴跌实例化频繁、无对象池、DrawCall过多引入对象池图集化关阴影用Frame Debugger查合批高内存占用导致页面崩溃TOTAL_MEMORY设置过大或资源预加载过多Player Settings里调低WebGL Memory Size关掉不需要的场景预加载模型显示为紫色/材质丢失Shader不兼容WebGL换移动端Shader变体或减少高级渲染特性鼠标点击第一次没反应Canvas缺少键盘/鼠标焦点在HTML加载后调用实例focus设置tabIndex无法播放音频或延迟明显音频压缩格式不兼容将音频转成OGG/MP3避免WAV直出浏览器窗口缩放后UI错乱没有设置Canvas ScalerCanvas Scaler切为Scale With Screen Size锚点拉好提示缺少gameassembly.dll或类似运行时错误WebGL没有DLL加载器未解析成功确认构建产物完整不要修改文件名检查服务器是否支持压缩编码这张表可以说是Unity 2018老项目搬WebGL时问题密度最高的十个现场。实际过程中还有更细碎的问题比如Shader编译耗时导致首帧卡顿、白屏期间没有loading状态、Resource加载过多阻塞主线程这些也不是代码层面能一次性解决的。我的经验是先保证主流程三分钟能跑通再把体验细节按影响面排序逐个磨。6. 一些按项目性质区分的补充建议如果你做的不是塔防而是卡牌、模拟经营、休闲三消这套流程同样适用。差别只在于资源管理的重心和UI交互的复杂度。卡牌游戏的核心资源是图鉴数据和卡牌图片迁移WebGL时重点检查动态加载和卡牌图集建议把卡牌数据从ScriptableObject改成JSON外部加载方便后续做热更和换皮。模拟经营类的存档通常比塔防大得多动不动几百KB上MB的玩家数据用PlayerPrefs存JSON就不是好主意了这时候应该自己在IndexedDB里建表或者对接后端云存档。三消类主要看粒子特效和动画性能WebGL下的Animator在多对象同步播放时依然很吃力尽量换成GPU粒子或者序列帧动画。另一个方向是把WebGL当作测试脚手架。有些人问为什么不直接用Unity编辑器做测试非要发布WebGL我的看法是WebGL构建能暴露很多桌面端注意不到的架构问题——IO依赖、跨域、资源体积、弱鸡性能设备上的表现这些都是正式发给玩家前必须验的。哪怕你最后目标是Steam或主机早做一次WebGL版也能提前扫出一大批潜在炸弹。我在实际操作中最大的体会是AI这次帮了大忙但它只解决了我“知道要改什么”之后的速度问题不能替代我判断“为什么要这么改”。比如File.WriteAllText要换成PlayerPrefs如果我不懂WebGL沙箱AI给一百个版本我也接不住又比如内存设置256MB这个数值不是AI告诉我的而是反复构建、打开任务管理器观察浏览器内存占用后拍板的。两小时迁移成功靠的不是AI神奇而是2018年写代码时就重逻辑、轻依赖的好习惯加上这一次愿意砍功能、认怂适配。这也是我给所有想把手头老游戏搬上浏览器的人最核心的建议控制依赖、做减法、先跑通再优化。