
简介这份资源是面向Unity开发者的WebGL多平台适配Demo重点解决打包后在Windows浏览器、安卓与苹果移动设备上自动全屏并强制横屏显示的问题。内容围绕Screen.fullScreen与Screen.orientation的调用、Application.platform平台检测、WebGL与浏览器JavaScript桥接交互以及移动端屏幕旋转生命周期处理等关键环节展开适合已具备Unity基础、正在做跨端发布适配的开发者参考。压缩包共145个文件约1.35MB包含65个meta、25个asset、13个shader、10个png以及cs脚本、jslib桥接文件、unity场景、mat材质、json配置与html页面等覆盖工程配置、着色器、脚本与WebGL模板等模块。目前已有1745人学习下载。通过研究其中的工程结构与实现细节读者可快速掌握多平台全屏横屏的落地思路减少自行摸索与排错的时间成本。1. 从一次翻车说起为什么你的 Unity WebGL 打包后总是“缩在角落”如果你做过 Unity WebGL 项目大概率遇到过这个场景本地编辑器里跑得好好的一打包丢到浏览器里画面就缩在页面左上角四周一圈黑边鼠标点上去坐标还偏了。更头疼的是产品经理跑过来说“手机上打开怎么是竖屏的我要横屏全屏”。你打开手机浏览器一看确实画面小得可怜手指滑动还会带着整个页面一起滚。这个 Demo 要解决的就是这件事让 Unity WebGL 打包产物在 Windows 桌面浏览器、Android 手机浏览器、iOS Safari 上都能自动全屏、自动横屏并且画面铺满不留黑边。它不是一个完整的游戏项目而是一套可以直接抄进你现有工程的适配方案包含前端页面模板、Unity 侧调用脚本、以及不同平台的差异化处理逻辑。适合正在做 WebGL 发布、被多端适配折磨过的 Unity 开发者也适合刚接触 WebGL 打包、想搞清楚“为什么打包出来和编辑器不一样”的新手。2. 项目结构与技术选型这套方案到底由哪些文件撑起来2.1 整体目录结构拿到这份 Demo 后先别急着打开 Unity把文件夹展开看一眼。一个典型的 Unity WebGL 适配工程目录结构大致是这样的WebGLFullscreenDemo/ ├── Assets/ │ ├── Scripts/ │ │ ├── FullscreenManager.cs // 全屏与横屏核心控制 │ │ └── PlatformDetector.cs // 平台识别 │ └── Plugins/ │ └── WebGL/ │ └── Fullscreen.jslib // 与浏览器交互的 JS 桥接 ├── ProjectSettings/ │ └── ProjectSettings.asset // 打包分辨率、模板设置 └── WebGLTemplates/ └── FullscreenTemplate/ ├── index.html // 自定义页面模板 ├── style.css // 全屏与横屏样式 └── fullscreen.js // 页面级全屏逻辑Assets/Scripts下放的是 C# 侧逻辑负责在 Unity 运行时判断当前平台、调用浏览器 API。Plugins/WebGL下的.jslib文件是 Unity 提供的 JavaScript 桥接机制让 C# 能直接调用浏览器原生方法。WebGLTemplates是自定义打包模板Unity 打包时会用这里的index.html替换默认模板这是实现“打开即全屏”的关键。2.2 技术选型为什么用 jslib 自定义模板Unity WebGL 本身运行在浏览器的 Canvas 里它没法直接控制浏览器窗口。全屏、横屏这些行为本质上是浏览器层面的能力必须通过 JavaScript 调用 Fullscreen API 和 Screen Orientation API。Unity 官方提供了两种方式一种是在 C# 里用Application.ExternalEval执行 JS 字符串另一种是写.jslib文件做桥接。这个 Demo 选的是.jslib方案原因是它更稳定、可维护。Application.ExternalEval在较新版本的 Unity 里已经被标记为过时而且字符串拼接容易出错。.jslib写好后C# 侧只需要声明一个[DllImport(__Internal)]的外部方法就能调用代码干净很多。自定义模板则是为了解决“页面加载完就自动全屏”的需求。默认模板只提供一个 Canvas 容器没有任何全屏逻辑。把模板换成自己的就能在页面onload时触发全屏请求同时注入横屏样式。提示Unity 2020 之后的版本对 WebGL 模板路径有调整如果打包时提示找不到模板检查ProjectSettings里的WebGL Template是否选到了你自定义的那个。2.3 平台差异Windows、Android、iOS 各有什么坑三个平台的全屏行为差异很大这也是为什么不能一套代码无脑跑。Windows 桌面浏览器Chrome、Edge、Firefox对 Fullscreen API 支持最好requestFullscreen()调用后基本能立即生效。但注意浏览器要求全屏请求必须由用户手势触发比如点击事件。如果页面加载完就自动调用Chrome 会拦截并报错。所以 Demo 里在 Windows 上采用的是“首次点击任意位置触发全屏”的策略。Android 浏览器情况复杂一些。Chrome for Android 支持 Fullscreen API但部分国产浏览器内核版本较低requestFullscreen可能不存在需要降级到webkitRequestFullscreen。另外 Android 上横屏需要调用screen.orientation.lock(landscape)这个 API 在部分浏览器里返回的 Promise 会 reject必须加 catch 处理。iOS Safari 是最特殊的。iPhone 上的 Safari 至今不支持 Fullscreen API 对任意元素全屏只能对 video 元素全屏。所以 iOS 上没法用标准全屏Demo 采用的方案是“伪全屏”通过 CSS 把 Canvas 撑满整个视口隐藏地址栏和工具栏同时用window.scrollTo(0, 1)把页面顶部那 1px 的地址栏顶出去。横屏方面iOS 不支持screen.orientation.lock只能靠 CSS 媒体查询配合transform: rotate(90deg)做视觉旋转。3. 核心模块拆解从 C# 调用到浏览器全屏的完整链路3.1 FullscreenManager.csC# 侧的统一入口先看 C# 侧最核心的脚本。它的职责是判断当前运行平台调用对应的 JS 方法并处理回调。using UnityEngine; using System.Runtime.InteropServices; public class FullscreenManager : MonoBehaviour { // 声明 jslib 中的外部方法仅在 WebGL 平台生效 #if UNITY_WEBGL !UNITY_EDITOR [DllImport(__Internal)] private static extern void RequestFullscreen(); [DllImport(__Internal)] private static extern void LockLandscape(); [DllImport(__Internal)] private static extern int IsMobileDevice(); #endif void Start() { // 编辑器里不执行避免报错 #if UNITY_WEBGL !UNITY_EDITOR if (IsMobileDevice() 1) { // 移动端先锁横屏再请求全屏 LockLandscape(); RequestFullscreen(); } else { // 桌面端监听首次点击由用户手势触发全屏 StartCoroutine(WaitForFirstClick()); } #endif } private System.Collections.IEnumerator WaitForFirstClick() { while (!Input.GetMouseButtonDown(0)) { yield return null; } RequestFullscreen(); } }这段代码的关键点有三个。第一#if UNITY_WEBGL !UNITY_EDITOR这个条件编译必须加否则在编辑器里运行时会因为找不到__Internal而报错。第二IsMobileDevice()返回 int 而不是 bool是因为 jslib 和 C# 之间的类型映射对 bool 支持不稳定用 int 更保险。第三桌面端用协程等待首次点击而不是在Start里直接调用就是为了绕过浏览器的用户手势限制。参数方面RequestFullscreen和LockLandscape都没有参数所有平台判断逻辑都放在 JS 侧。这样做的好处是 C# 侧保持简洁平台差异全部收敛到 jslib 里。3.2 Fullscreen.jslib浏览器 API 的桥接层jslib 文件是 Unity WebGL 和浏览器之间的桥梁语法是 JavaScript 和 Emscripten 的混合体。mergeInto(LibraryManager.library, { // 请求全屏兼容不同浏览器前缀 RequestFullscreen: function () { var canvas document.querySelector(#unity-canvas); if (!canvas) return; var requestMethod canvas.requestFullscreen || canvas.webkitRequestFullscreen || canvas.mozRequestFullScreen || canvas.msRequestFullscreen; if (requestMethod) { requestMethod.call(canvas).catch(function (err) { console.warn(全屏请求被拒绝:, err); }); } else { // iOS Safari 降级方案CSS 伪全屏 document.body.classList.add(pseudo-fullscreen); window.scrollTo(0, 1); } }, // 锁定横屏 LockLandscape: function () { if (screen.orientation screen.orientation.lock) { screen.orientation.lock(landscape).catch(function (err) { console.warn(横屏锁定失败降级到 CSS 旋转:, err); document.body.classList.add(force-landscape); }); } else { document.body.classList.add(force-landscape); } }, // 判断是否移动设备 IsMobileDevice: function () { var ua navigator.userAgent; return /Android|iPhone|iPad|iPod|Mobile/i.test(ua) ? 1 : 0; } });mergeInto是 Emscripten 的固定写法把方法注册到LibraryManager里。RequestFullscreen里做了四层兼容标准 API、webkit 前缀、moz 前缀、ms 前缀。如果全都不存在就走 iOS 的伪全屏方案给body加一个 class由 CSS 负责撑满视口。LockLandscape里对screen.orientation.lock的返回值做了 catch。这个 API 在部分 Android 浏览器上会直接 reject如果不 catch控制台会报未处理的 Promise 错误虽然不影响功能但看起来很不专业。IsMobileDevice用正则匹配 UA 字符串。这里没有用navigator.platform因为那个属性在新版浏览器里逐渐被废弃UA 虽然也不完美但兼容性更好。3.3 自定义模板index.html 与 CSS 的配合模板文件决定了打包后页面的初始状态。核心是在index.html里引入自己的 CSS 和 JS并给 Canvas 一个正确的容器。!DOCTYPE html html langzh-CN head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1.0, maximum-scale1.0, user-scalableno, viewport-fitcover titleUnity WebGL Fullscreen Demo/title link relstylesheet hrefstyle.css /head body div idunity-container canvas idunity-canvas/canvas /div script srcfullscreen.js/script script srcBuild/{{{ BUILD_LOADER }}}/script /body /htmlviewport这行 meta 很关键。user-scalableno禁止用户双指缩放viewport-fitcover让页面延伸到 iPhone 的刘海区域避免出现白边。{{{ BUILD_LOADER }}}是 Unity 模板的占位符打包时会自动替换成实际的 loader 文件名。CSS 部分负责伪全屏和强制横屏的视觉表现html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: #000; } #unity-container { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; } #unity-canvas { width: 100%; height: 100%; display: block; } /* iOS 伪全屏 */ body.pseudo-fullscreen #unity-container { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; } /* 强制横屏旋转容器 */ body.force-landscape #unity-container { position: fixed; top: 50%; left: 50%; width: 100vh; height: 100vw; transform: translate(-50%, -50%) rotate(90deg); transform-origin: center center; }force-landscape这段是给 iOS 和部分不支持横屏锁定的 Android 浏览器用的。思路是把容器宽高互换然后旋转 90 度视觉上就是横屏效果。注意transform-origin要设成 center否则旋转后位置会偏。4. 避坑与常见问题那些打包后才发现的血泪经验4.1 全屏请求被浏览器拦截现象页面加载后自动调用全屏控制台报Failed to execute requestFullscreen on Element: API can only be initiated by a user gesture。原因Chrome、Firefox 等浏览器要求全屏必须由用户手势click、touch触发页面加载时的自动调用会被拒绝。解决桌面端改成监听首次点击再触发全屏。如果产品要求“打开就全屏”可以在页面上放一个半透明的“点击开始”遮罩层用户点击后隐藏遮罩并触发全屏。这是目前唯一合规的做法。4.2 iOS 上全屏后画面被拉伸现象iPhone 上伪全屏后Canvas 画面比例不对人物被拉长。原因width: 100vw; height: 100vh强制拉伸了 Canvas但 Unity 的渲染分辨率没有跟着变。解决在 jslib 里监听resize事件调用 Unity 的SendMessage通知 C# 侧重新设置Screen.SetResolution。或者更简单的方式在 CSS 里用object-fit: contain保持比例但这样会有黑边。Demo 里采用的是监听 resize 并通知 Unity 重设分辨率。4.3 Android 横屏锁定返回 Promise reject现象部分 Android 浏览器上调用screen.orientation.lock(landscape)后控制台报Uncaught (in promise) DOMException。原因这些浏览器虽然实现了 API但出于策略限制不允许网页锁定方向。解决必须加.catch()在 catch 里降级到 CSS 旋转方案。不要用try-catch包裹因为 Promise 的 reject 不会被同步 try-catch 捕获。4.4 打包后 Canvas 尺寸不对现象打包出来画面只占页面一小块周围全是黑边。原因Unity 默认模板的 Canvas 尺寸是固定的没有跟随窗口自适应。解决在自定义模板的 CSS 里把#unity-canvas的宽高设为 100%同时确保#unity-container也是 100%。另外检查 Unity 打包设置里的Resolution and Presentation把Canvas Width和Canvas Height设成 0 或者留空让页面控制。4.5 编辑器里报 DllImport 错误现象在 Unity 编辑器里点运行控制台报DllNotFoundException: __Internal。原因[DllImport(__Internal)]只在 WebGL 平台有效编辑器里没有这个库。解决所有 jslib 相关的声明和调用都必须包在#if UNITY_WEBGL !UNITY_EDITOR里。如果需要在编辑器里测试逻辑可以写一个空的模拟方法放在#else分支里。5. 二次开发与改造把这套方案接进你自己的项目5.1 最小改造步骤如果你已经有一个 Unity WebGL 项目想接入这套方案按下面几步走。第一步把Fullscreen.jslib复制到你的Assets/Plugins/WebGL/目录下。如果没有这个目录就新建一个。注意文件名不要改因为 C# 里DllImport引用的就是文件名。第二步把FullscreenManager.cs挂到场景里一个常驻的 GameObject 上比如GameManager或者新建一个空物体。确保它不会被销毁。第三步在ProjectSettings的Player设置里找到Resolution and Presentation把WebGL Template改成你的自定义模板。如果模板文件夹不在默认路径点Browse手动选。第四步打包测试。先在 Windows 浏览器上验证全屏和横屏再用 Android 手机测最后用 iPhone 测。三个平台的表现应该一致。5.2 改造点适配你的 UI 布局这套 Demo 默认是横屏全屏如果你的项目有竖屏需求需要改两个地方。一是LockLandscape改成LockPortrait调用screen.orientation.lock(portrait)。二是 CSS 里的force-landscape旋转角度从 90 度改成 0 度或者干脆去掉旋转只保留伪全屏。如果你的 UI 用了Canvas Scaler记得把Match Width Or Height设成 0.5 或者根据横竖屏动态调整。横屏下Screen.width Screen.height竖屏反之UI 锚点要跟着变。5.3 验证方法怎么确认真的生效了不要只看画面铺满了就以为成功了。打开浏览器开发者工具在 Console 里输入document.fullscreenElement如果返回 Canvas 元素说明标准全屏生效。输入screen.orientation.type如果返回landscape-primary或landscape-secondary说明横屏锁定生效。iOS 上没法用这两个 API 验证只能肉眼看。伪全屏成功的标志是地址栏自动隐藏页面没有滚动条Canvas 铺满整个屏幕。如果地址栏还在检查window.scrollTo(0, 1)是否执行以及body的overflow是否设成了hidden。5.4 一个容易忽略的细节全屏退出后的状态恢复用户按 ESC 或者手势退出全屏后页面会回到普通状态。这时候如果 Canvas 尺寸没恢复画面会错位。Demo 里在 jslib 中监听了fullscreenchange事件退出全屏时移除pseudo-fullscreen和force-landscape类并通知 Unity 重设分辨率。document.addEventListener(fullscreenchange, function () { if (!document.fullscreenElement) { document.body.classList.remove(pseudo-fullscreen); document.body.classList.remove(force-landscape); // 通知 Unity 恢复分辨率 if (typeof SendMessage function) { SendMessage(FullscreenManager, OnFullscreenExit, ); } } });C# 侧对应的OnFullscreenExit方法里重新调用Screen.SetResolution恢复默认值。这个细节很多教程不会讲但不做的话用户体验会很差。从那以后我每次做 WebGL 打包都会先在真机上把全屏、横屏、退出恢复这三件事各走一遍确认没问题再交给测试。这套 Demo 里的代码我用了好几个项目改改平台判断和 CSS 就能直接复用。希望帮到你。本文还有配套的精品资源点击获取