为什么 Godot 网页版在编辑器里能跑,到了浏览器却打不开?

带线程的 Godot 4 网页导出需要 SharedArrayBuffer,只在安全上下文里的跨源隔离页面上可用。没有 Cross-Origin-Opener-Policy: same-origin 与 Cross-Origin-Embedder-Policy: require-corp 这两个响应头、又没用 Progressive Web App 的 service worker 办法时,文档说项目将无法运行。自 Godot 4.3 起默认的单线程导出绕开了响应头,但不能用线程。

为什么 Godot 网页版在编辑器里能跑,到了浏览器却打不开?
本篇导航5 个章节

编辑器里游戏能跑:场景加载、角色移动,一切正常。把同一个项目导出成网页版、上传、打开链接,浏览器版却在开始前就停住了。关卡没坏,脚本没改,资源也没丢。差别不在游戏里,而在浏览器被告知了什么。

Chromium 开发者工具 Service Workers 面板,测试站点为 localhost。这是 Godot 文档截图,不是游戏画面,也不是响应头设置界面。

快速信息

  • 来源:2026年10月30日打开的 Godot Engine 稳定版文档 Exporting for the Web,以及 Godot 网页导出 JavaScript 参考。

  • 定性:有文档依据的陷阱,不是某家工作室的具名事故。没有找到可核实的某个游戏的复盘,因此本文用一个明确标注的、在你自己项目上做的最小复现。

  • 陷阱本身:只有开启 Thread Support 的网页导出才需要 SharedArrayBuffer,浏览器只在安全上下文里的跨源隔离页面上放行它,需要 Cross-Origin-Opener-Policy: same-origin 与 Cross-Origin-Embedder-Policy: require-corp 这两个响应头。没有所需响应头、又没用 service worker 办法时,文档说项目将无法运行。

  • 能救大多数项目的变化:从 Godot 4.3 起,单线程导出是网页导出首选、也是现在的默认方式。它不需要特殊响应头,但不能用线程。

  • 本文不声称的内容:没有具名游戏、没有性能数字、也没有针对某个主机的服务器配置写法,因为打开过的来源都没有核实这些。

  • 最后核实:2026年10月30日。

指向游戏之外的症状

这个陷阱容易被误读,因为失败看起来像构建坏了。编辑器里能跑,于是会去翻脚本、重新导出、怀疑最近的改动。但原因在游戏下面一层,在托管服务器与浏览器之间的约定里时,这些都没用。带线程的网页导出通过 SharedArrayBuffer 向浏览器要共享内存,浏览器回应时看的是页面、不是游戏。页面没有跨源隔离,答案就是不行。

Godot 4.3 改变了这个陷阱出现的频率。在此之前,网页导出全面走多线程对音频和性能有意义,响应头要求也随之而来。从 4.3 起,Godot 支持单线程导出,不向浏览器要线程,也就绕开了响应头问题。文档称它是网页导出的首选、也是现在的默认方式,还说它与商店和网页发行平台的兼容性更好。保持默认、从没开过 Thread Support 的开发者,可能根本遇不到这个陷阱。会遇到的通常是三种情况:沿用下来的老的多线程导出、确实在网页上用了 Thread 类或 GDExtension 能力的项目,或者为了低延迟 Stream 音频开了 Thread Support、却没注意到这个开关同时买下了一个托管要求。

根因:两个响应头,一种隔离状态

对带线程的导出,Godot 文档把要求说得很直白。只有用 Use Threads 导出时,Godot 4 网页导出才会用 SharedArrayBuffer,目的是保持低音频延迟、并在网页上允许使用 Thread 类。这项使用需要安全上下文,以及 served 文件带上两个响应头:Cross-Origin-Opener-Policy 设为 same-origin,Cross-Origin-Embedder-Policy 设为 require-corp。两者一起把页面置于跨源隔离状态,而这正是浏览器交出共享内存之前要求的状态。文档接着毫不含糊地写了后果:如果客户端没有收到所需响应头、又没用 service worker 办法,项目将无法运行。

两个相关的事实解释了编辑器运行为什么会误导、以及响应头修复为什么不是免费的。第一,编辑器里的桌面运行不是浏览器页面,根本不用通过浏览器的隔离检查。第二,跨源隔离是整页的事:文档提醒,这意味着托管游戏的网站上不能有广告、也不能有第三方集成。开线程因此不只是性能选择,更是托管选择。

最小复现,以及三条出路

因为没有可核实的具名事故,这里给一个诚实的替代做法:在你自己的最小项目上、在测试部署上做的复现。做一个很小的 Godot 4 项目,用看得见的方式用到线程,例如一个后台任务、结果显示在屏幕上。开着 Thread Support 把它导出成网页版,保持导出文件齐全。把这组文件传到一个不发送两个隔离响应头的测试主机上,打开页面,在开发者控制台里看页面是否报告自己处于跨源隔离状态。然后一次只改一个条件、重复测试:先是在测试主机上带上两个响应头,再是单独走下面说的 Progressive Web App 路线。如果第一次失败、而改动后的任一条件通过,原因就锁定在托管约定上、不是游戏上。只在测试部署上做这件事,绝不要在没有备份、没有回退办法的情况下直接改线上站点唯一的配置。

三条出路是:关掉 Thread Support、重新导出成单线程构建,代价是 Thread 类不可用、默认 Sample 音频限制音频效果与程序化音频;保留线程、让主机在安全上下文里准确发送上面两个响应头;或者对无法配置的主机,在导出选项里启用 Progressive Web App,它会装一个 service worker 来模拟响应头的存在,但仍然需要安全上下文。不要顺手取消勾选 Enable Cross Origin Isolation Headers,否则这条路就失效了。如果重测时出现昨天的构建,就在开发者工具里注销当前的 service worker 再刷新,也就是封面图里的那个面板。改了导出选项后都要重新导出,因为直接改生成的 HTML 文件会在以后的导出里丢失。

周一早上照着做

  • 先说清楚导出的是哪一种。看 Thread Support 是否开启;项目在网页上不用线程,就关掉它、重新导出成单线程并重测。

  • 只在测试部署上复现。导出很小的带线程测试项目,传到测试主机,看部署好的页面是否报告自己处于跨源隔离状态。

  • 如果保留线程,就查主机的响应头设置、不是游戏。页面响应须准确带有 Cross-Origin-Opener-Policy: same-origin 与 Cross-Origin-Embedder-Policy: require-corp,并且处于安全上下文。本文不给针对某个主机的配置写法,因为没有核实过。

  • 如果设不了响应头,就走 service worker 路线。启用 Progressive Web App,保持 Enable Cross Origin Isolation Headers 勾选,重新导出并重测。仍然需要安全上下文。

  • 本地测试只用文档里的测试服务器。把 Godot 仓库的 Python 脚本存为 serve.py、放在导出好的 index.html 旁,然后在该文件夹运行 python serve.py --root .,系统需要时把 python 换成 python3。它只用于测试、不用于正式生产。

  • 如果重测出现旧构建,就清理 service worker。打开 Application 标签,找到封面图里的 Service Workers 面板,注销当前的 service worker 再刷新。

  • 把决定记在导出预设里、不是生成的文件里。改了预设就重新导出,重测带线程的功能与音频,并把托管要求写进发布说明。