
简介这份资源是面向Unity开发者的WebGL多平台适配Demo重点解决打包后在Windows浏览器、安卓与苹果移动设备上自动全屏并强制横屏的难题。内容围绕Screen.fullScreen与Screen.orientation的调用、Application.platform平台判断、WebGL与浏览器JavaScript桥接以及移动端旋转生命周期处理等关键环节展开适合已具备Unity基础、正被跨端显示问题困扰的中级开发者参考。压缩包共145个文件约1.35MB包含65个meta、25个asset、13个shader、10个png及jslib、cs脚本、html页面等覆盖工程配置、着色器、资源与WebGL交互模板目录结构完整。目前已有1744人学习下载。通过研究该Demo读者可掌握全屏请求注入、横屏锁定与平台差异化策略的落地写法并借助现成脚本与配置快速复用到自己的项目中减少反复试错的时间成本。1. Unity WebGL 打包后全屏横屏为什么浏览器里跑得好好的一上真机就翻车做过 Unity WebGL 的人大概率都遇到过这个场景在 Windows 浏览器里点开构建产物画面正常、点击正常一切岁月静好可一旦把链接丢到安卓手机或 iPad 上画面要么缩在左上角一小块要么竖屏状态下被压成一条要么点屏幕没反应横屏更是想都别想。这不是玄学而是 WebGL 平台和移动端浏览器之间那层「宿主环境」在作怪——Unity 只负责把画面渲染进一个 canvas至于这个 canvas 多大、怎么摆放、能不能全屏、屏幕转不转全由外层 HTML 和浏览器策略说了算。这篇要解决的就是这件事让同一份 Unity WebGL 构建产物在 Windows 桌面浏览器、安卓移动设备、苹果 iOS/iPadOS 设备上都能自动进入全屏并锁定横屏且尽量少让用户手动点。适合正在做 WebGL 项目、需要跨端交付的 Unity 开发者也适合被「移动端体验差」反复投诉的产品同学。下面从原理到可抄的代码一步步拆开讲。2. 全屏与横屏到底卡在哪浏览器策略、canvas 尺寸与 Unity 的三方博弈2.1 全屏 API 为什么必须由用户手势触发浏览器有一条几乎无法绕过的安全策略requestFullscreen()这类全屏请求必须发生在「用户主动交互」的事件回调里比如 click、touchend、keydown。你在页面onload里直接调用绝大多数移动浏览器会直接拒绝控制台丢一句Failed to execute requestFullscreen on Element: API can only be initiated by a user gesture。这就是为什么很多 Demo 在桌面能自动全屏、到手机上就失效——桌面 Chrome 相对宽松移动端 Safari 和 Chrome 卡得很死。所以正确的思路不是「自动全屏」而是「用一次用户点击把全屏和横屏一起锁掉」。常见做法是在 Unity 画布上盖一个「点击开始」的遮罩层用户点一下遮罩消失的同时触发全屏 横屏锁定之后整个游戏过程都保持这个状态。这个交互成本用户完全能接受也符合浏览器规范。2.2 横屏锁定依赖 Screen Orientation API兼容性是最大变量锁横屏用的是screen.orientation.lock(landscape)。这里有个血泪经验这个 API 在安卓 Chrome 上基本可用但在 iOS Safari 上长期支持很差甚至在部分版本直接不存在。所以不能只靠它必须做降级——iOS 上更可靠的做法是「引导用户把设备转横 用 CSS 把竖屏状态下的画面旋转 90 度」来兜底。也就是说横屏这件事要分两条路能锁就锁锁不了就转。另外lock()同样要求全屏状态已经生效顺序必须是「先全屏再锁方向」反过来会报The operation is not supported。这个顺序坑我踩过不止一次。2.3 Unity 侧的分辨率与 canvas 适配Unity WebGL 默认会根据自己的 Canvas 设置去改 canvas 的宽高。如果外层 CSS 没约束好移动端会出现「画面比屏幕大、被裁掉」或者「画面比屏幕小、周围留黑边」。关键参数在 Player Settings 里Resolution and Presentation下的Default Canvas Width/Height建议设成 1920×1080 这类 16:9 基准同时把Run In Background打开避免切后台回来画面卡死。真正决定显示效果的是外层容器的 CSSUnity 生成的 canvas 要让它width:100%; height:100%撑满。2.4 一个最小可用的 HTML 骨架下面这段是整套方案的宿主页面骨架把全屏、横屏、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 全屏横屏 Demo/title style html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: #000; } /* Unity 容器撑满整个视口 */ #unity-container { position: fixed; inset: 0; width: 100%; height: 100%; } #unity-canvas { width: 100%; height: 100%; display: block; } /* 点击开始遮罩用于承接用户手势 */ #start-overlay { position: fixed; inset: 0; z-index: 999; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.85); color: #fff; font-size: 20px; cursor: pointer; user-select: none; } /* 竖屏兜底当无法锁定横屏时把容器旋转 90 度 */ #unity-container.force-landscape { width: 100vh; height: 100vw; transform: rotate(90deg) translateY(-100%); transform-origin: top left; } /style /head body div idunity-container canvas idunity-canvas/canvas /div div idstart-overlay点击屏幕开始/div script srcfullscreen-landscape.js/script /body /html这段 HTML 里几个点值得说清楚viewport-fitcover是为了适配 iPhone 刘海屏不加的话全屏后左右可能被裁overflow:hidden防止移动端出现滚动条把画面顶偏#unity-container.force-landscape那套旋转逻辑是给 iOS 兜底用的宽高互换再旋转 90 度视觉上就变成横屏了。注意旋转后坐标系会变触摸事件需要额外做映射这点在避坑章节会展开。3. 把全屏横屏逻辑写成可复用脚本从点击遮罩到方向锁定3.1 全屏 横屏的核心函数把逻辑抽成一个独立 JS 文件Unity 构建产物和它解耦换项目也能直接搬。核心就两个函数进全屏、锁横屏。// fullscreen-landscape.js const container document.getElementById(unity-container); const overlay document.getElementById(start-overlay); // 判断当前是否 iOS用于走不同的横屏策略 function isIOS() { return /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform MacIntel navigator.maxTouchPoints 1); } // 进入全屏兼容各浏览器的前缀写法 async function enterFullscreen(el) { const fn el.requestFullscreen || el.webkitRequestFullscreen || el.mozRequestFullScreen || el.msRequestFullscreen; if (!fn) return false; try { await fn.call(el); return true; } catch (e) { console.warn(全屏失败:, e.message); return false; } } // 锁定横屏失败则返回 false 交给兜底逻辑 async function lockLandscape() { if (!screen.orientation || !screen.orientation.lock) return false; try { await screen.orientation.lock(landscape); return true; } catch (e) { console.warn(横屏锁定失败:, e.message); return false; } } // 竖屏兜底用 CSS 旋转容器 function applyForceLandscape() { const isPortrait window.innerHeight window.innerWidth; if (isPortrait) { container.classList.add(force-landscape); } else { container.classList.remove(force-landscape); } }enterFullscreen里做了前缀兼容老一点的安卓 WebView 只认webkitRequestFullscreen。lockLandscape返回布尔值而不是抛异常是为了让调用方决定要不要走兜底。applyForceLandscape只在竖屏时加旋转类横屏时移除配合resize事件监听就能动态切换。3.2 用一次点击把三件事串起来遮罩层的点击回调是整套流程的入口顺序不能乱先全屏再锁方向最后隐藏遮罩。overlay.addEventListener(click, async () { // 1. 先全屏必须在用户手势内同步发起 const fsOk await enterFullscreen(container); // 2. 全屏成功后再尝试锁横屏 if (fsOk) { const lockOk await lockLandscape(); // 3. 锁不上典型是 iOS就走 CSS 旋转兜底 if (!lockOk) applyForceLandscape(); } else { // 全屏都失败至少保证横屏视觉 applyForceLandscape(); } overlay.style.display none; }); // 屏幕旋转或尺寸变化时重新判断兜底 window.addEventListener(resize, () { if (overlay.style.display none) applyForceLandscape(); });这里有个容易翻车的细节await enterFullscreen之后浏览器可能还没真正完成全屏状态切换紧接着调lock有时会失败。稳妥做法是在全屏成功的fullscreenchange事件里再锁方向。如果实测发现锁不上把lockLandscape挪到document.addEventListener(fullscreenchange, ...)回调里成功率会明显提升。3.3 参数怎么调几个必须关注的配置项配置项位置建议值说明Default Canvas Width/HeightUnity Player Settings1920×108016:9 基准移动端按比例缩放Run In BackgroundUnity Player Settings勾选防止切后台回来黑屏viewport user-scalableHTML metano禁止双指缩放破坏布局overflowbody CSShidden消除移动端滚动条force-landscape 触发阈值JSinnerHeight innerWidth竖屏判定这张表里的值不是拍脑袋定的是多个项目调出来的经验值。Default Canvas Width/Height设成 16:9 是因为绝大多数移动设备是宽屏比例设成 4:3 会在横屏时上下留黑边。Run In Background不勾的话安卓上切出去再回来WebGL 上下文可能丢失画面直接白屏。3.4 触摸坐标在旋转兜底下的映射一旦走了force-landscape的 CSS 旋转触摸事件的坐标就和视觉对不上了这是最隐蔽的坑。用户点在视觉上的某个位置实际事件坐标是旋转前的。需要在 Unity 的输入层或者 JS 层做一次坐标变换。// 当容器处于旋转状态时把触摸坐标映射回旋转前的坐标系 function mapTouchPoint(clientX, clientY) { if (!container.classList.contains(force-landscape)) { return { x: clientX, y: clientY }; } // 旋转 90 度后x 对应原来的 yy 对应原来的 (视口宽 - x) const vw window.innerWidth; return { x: clientY, y: vw - clientX }; }这个映射公式的推导容器绕左上角顺时针旋转 90 度原坐标 (x, y) 旋转后视觉位置变成 (y, 视口宽 - x)。反过来已知视觉点击点求原始坐标就是上面这个式子。实际项目里如果 Unity 用的是自己的输入系统更推荐在 Unity 侧用Input.touches配合屏幕尺寸做换算避免 JS 和 Unity 两套坐标打架。4. 避坑与排查全屏横屏在真机上最容易翻车的 5 个点4.1 现象桌面能自动全屏手机点了没反应原因移动浏览器强制要求全屏请求在用户手势的同步调用栈里发起。如果你的代码里先await了一个网络请求或者setTimeout手势上下文就丢了全屏被静默拒绝。解决把enterFullscreen放在 click 回调的第一行前面不要有任何await。如果确实需要异步准备先同步调用全屏再在fullscreenchange里做后续初始化。4.2 现象横屏锁定了但画面还是竖的原因screen.orientation.lock成功返回了但 Unity 的 canvas 尺寸没跟着更新或者外层容器还是竖屏的宽高。锁方向只改变设备方向不改变你的 DOM 布局。解决监听orientationchange和resize在回调里重新设置 canvas 宽高并调用 Unity 实例的Module.resize或者触发一次窗口 resize 让 Unity 重新适配。4.3 现象iOS 上screen.orientation.lock直接报 undefined原因iOS Safari 长期不支持 Screen Orientation API 的 lock 方法screen.orientation对象可能都不存在。解决先做能力检测if (!screen.orientation || !screen.orientation.lock)不支持就走 CSS 旋转兜底。别指望 iOS 能锁方向这是平台限制绕不过去。4.4 现象全屏后画面被拉伸变形原因canvas 的宽高比和屏幕宽高比不一致Unity 默认会拉伸填充。解决在 Unity 的 Camera 上根据Screen.width / Screen.height动态调整aspect或者用Camera.rect做黑边适配。WebGL 平台下Screen.width/height拿到的就是 canvas 的实际像素尺寸可以直接用。4.5 现象切后台再回来画面卡死或黑屏原因移动浏览器在页面不可见时会暂停 requestAnimationFrameWebGL 上下文也可能被回收。Unity 默认没处理这种恢复。解决Player Settings 里勾选Run In Background并在 JS 里监听visibilitychange页面重新可见时调用 Unity 实例的Module.resume或触发一次 focus 事件。如果上下文真的丢了只能提示用户刷新所以关键状态要定期存到 localStorage。5. 进阶用一套构建产物覆盖三端的自适应策略与验证清单前面讲的是一套通用逻辑但 Windows 桌面、安卓、iOS 三端的浏览器行为差异不小真正上线前得有一套自适应策略和验证方法。我一般会在fullscreen-landscape.js里加一个环境探测根据平台走不同分支而不是一套代码硬扛。// 环境探测决定走哪条全屏横屏路径 function detectPlatform() { const ua navigator.userAgent; if (isIOS()) return ios; if (/Android/i.test(ua)) return android; return desktop; } // 按平台初始化策略 function initByPlatform() { const platform detectPlatform(); switch (platform) { case ios: // iOS 锁不了方向直接上 CSS 旋转兜底 applyForceLandscape(); break; case android: // 安卓优先尝试原生锁定失败再兜底 lockLandscape().then(ok { if (!ok) applyForceLandscape(); }); break; default: // 桌面端全屏即可方向无所谓 break; } }这段代码的价值在于把「平台差异」显式化。iOS 分支直接跳过 lock 尝试省掉一次必然失败的调用安卓分支保留原生锁定优先桌面端不做多余操作。这样三端行为可预期出问题也好定位。验证清单我习惯按下面这几条逐项过缺一条都可能在某个机型上翻车验证项Windows Chrome安卓 ChromeiOS Safari点击遮罩进全屏通过通过通过原生横屏锁定不适用通过不支持走兜底CSS 旋转兜底不适用备用主路径触摸坐标准确鼠标正常需验证需验证切后台恢复正常需验证需验证刘海屏不裁切不适用部分机型需验证这张表建议每个项目上线前真机跑一遍尤其是 iOS 的触摸坐标和切后台恢复模拟器测不出来。安卓机型碎片化严重至少覆盖一台高刷屏和一台老机型。最后说个我自己的习惯这套全屏横屏逻辑我从来不直接写进 Unity 构建产物里而是放在宿主 HTML 和独立 JS 里。原因是 Unity 每次重新构建都会覆盖index.html逻辑写进去等于每次都要重贴。把它抽成外部文件构建产物只负责渲染宿主层负责环境适配两边解耦换 Unity 版本、换项目都能复用。这个习惯帮我省了无数次重复劳动也希望帮到你。本文还有配套的精品资源点击获取