简介这份资源是面向Unity开发者的WebGL多平台全屏横屏适配Demo针对Unity项目打包为WebGL后在Windows浏览器、Android与iOS移动设备上无法自动全屏、屏幕方向不受控的常见痛点提供可运行的参考实现。包内共145个文件以65个meta、25个asset、13个shader及若干cginc、mat等渲染与工程配置资源为主另含2个cs脚本、2个jslib桥接文件、html与json配置压缩包约1.35MB结构完整便于直接导入研究。Demo围绕Screen.fullScreen与Screen.orientation控制、Application.platform平台检测、UnityLoader与浏览器JavaScript交互、移动端旋转生命周期处理等关键环节展开展示了WebGL与原生平台差异化的适配思路。目前已有1745人学习下载适合需要快速落地全屏横屏方案、减少重复踩坑的Unity开发者参考借鉴。1. 从一次翻车说起为什么 WebGL 打包后全屏横屏这么难搞去年帮一个做互动展示的团队调项目Unity WebGL 构建出来的东西在 Windows 浏览器里跑得好好的一放到安卓平板和 iPad 上就出问题——要么画面只占屏幕左上角一小块要么横屏游戏被强制竖屏显示两边留出巨大的黑边。更离谱的是 iOS Safari 上点击全屏按钮后地址栏还在整个 Canvas 被压扁。这不是个例是 Unity WebGL 在移动端浏览器上的经典玄学问题。这个 Demo 要解决的就是这件事让 Unity WebGL 构建产物在 Windows 桌面浏览器、安卓移动设备、苹果移动设备上都能自动全屏并锁定横屏。它适合正在做 H5 小游戏、互动课件、展厅大屏项目的 Unity 开发者尤其是那些不想依赖第三方商业插件、希望用原生 JavaScript 桥接方案搞定全屏横屏的人。核心思路不复杂但浏览器兼容性差异会让第一次做的人踩不少坑。2. 技术选型与整体架构为什么不用插件自己写桥接2.1 三层架构设计这个方案的整体结构分三层。最底层是 Unity 的 WebGL 模板Template负责生成最终的 HTML 壳子和 Canvas 容器中间层是一段注入到页面里的 JavaScript 桥接脚本处理全屏 API 调用、屏幕方向锁定和尺寸自适应最上层是 Unity C# 侧的调用入口通过Application.ExternalCall或jslib插件机制触发浏览器行为。选这个架构而不是直接买个 Asset Store 插件原因有三个。第一全屏和横屏本质上是浏览器 API 的事Unity 管不着插件也只是帮你封装了 JS 调用多一层黑盒不如自己写清楚。第二移动端浏览器对 Fullscreen API 和 Screen Orientation API 的支持差异很大自己写桥接可以针对不同平台做降级处理。第三WebGL 模板本身就可以定制把 JS 逻辑直接写进模板里构建出来就能用不需要额外配置。常见做法是在Assets/Plugins/WebGL/目录下放一个.jslib文件Unity 构建时会把它合并进生成的 JS 里。但更直接的方式是修改 WebGL 模板中的index.html把全屏逻辑写死在模板里这样连 C# 调用都省了页面加载完自动执行。2.2 浏览器 API 能力对比不同平台对关键 API 的支持情况直接决定了代码怎么写APIWindows Chrome/Edge安卓 ChromeiOS SafarirequestFullscreen支持支持不支持需 webkit 前缀screen.orientation.lock支持支持不支持webkitRequestFullscreen支持支持支持自动全屏无用户手势不允许不允许不允许这张表说明两个关键问题iOS 上必须用webkit前缀的 API而且所有浏览器都要求全屏操作必须由用户手势触发不能在页面加载时自动调用。所以“自动全屏”的真实含义是在用户第一次点击/触摸页面时立即触发全屏而不是页面一打开就全屏。2.3 目录结构Demo 的目录结构大致如下WebGLTemplates/ FullscreenTemplate/ index.html # 定制模板包含全屏桥接逻辑 TemplateData/ style.css # Canvas 样式处理居中与尺寸 fullscreen.js # 核心全屏横屏脚本 Assets/ Plugins/ WebGL/ FullscreenBridge.jslib # C# 调用 JS 的桥接 Scripts/ FullscreenTrigger.cs # 游戏内触发全屏的入口 ProjectSettings/ ProjectSettings.asset # 默认模板设置为 FullscreenTemplate把模板放在Assets/WebGLTemplates/下然后在 Player Settings 里选择对应模板构建时 Unity 会自动用它替换默认的index.html。这样整个方案就是自包含的换项目也能直接复制过去。3. 核心模块拆解全屏、横屏、自适应三段代码3.1 全屏请求与兼容性封装全屏逻辑的核心是处理不同浏览器的前缀差异同时要保证在用户手势回调里执行。下面这段代码放在fullscreen.js里// 封装跨浏览器的全屏请求 function requestFullscreen(element) { // 标准 API if (element.requestFullscreen) { return element.requestFullscreen(); } // WebKit 内核iOS Safari、旧版 Chrome if (element.webkitRequestFullscreen) { return element.webkitRequestFullscreen(); } // 旧版 Firefox if (element.mozRequestFullScreen) { return element.mozRequestFullScreen(); } // IE11 if (element.msRequestFullscreen) { return element.msRequestFullscreen(); } console.warn(当前浏览器不支持全屏 API); return Promise.reject(Fullscreen not supported); } // 退出全屏的兼容封装 function exitFullscreen() { if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if (document.msExitFullscreen) { document.msExitFullscreen(); } }这段代码的关键点在于requestFullscreen返回的是 Promise在 iOS 上webkitRequestFullscreen不返回 Promise所以调用方不能依赖.then()做后续操作。参数element通常传document.documentElement或 Canvas 的父容器传 Canvas 本身在某些浏览器上会导致全屏后画面比例异常。我一般传document.documentElement让整个页面全屏Canvas 再通过 CSS 撑满。3.2 横屏锁定与降级方案横屏锁定依赖 Screen Orientation API但 iOS 完全不支持所以必须准备降级方案// 尝试锁定横屏 function lockLandscape() { // 标准 API安卓 Chrome 支持 if (screen.orientation screen.orientation.lock) { screen.orientation.lock(landscape).catch(function(err) { console.warn(横屏锁定失败走降级方案:, err); fallbackRotate(); }); } else { // iOS 等不支持 lock 的平台 fallbackRotate(); } } // 降级方案通过 CSS 旋转 Canvas 模拟横屏 function fallbackRotate() { var canvas document.querySelector(#unity-canvas); if (!canvas) return; var w window.innerWidth; var h window.innerHeight; // 竖屏状态下旋转 90 度 if (h w) { canvas.style.transform rotate(90deg); canvas.style.transformOrigin left top; canvas.style.width h px; canvas.style.height w px; canvas.style.position absolute; canvas.style.left w px; canvas.style.top 0; } else { // 已经是横屏清除旋转 canvas.style.transform ; canvas.style.width 100%; canvas.style.height 100%; canvas.style.left 0; } }screen.orientation.lock(landscape)在安卓 Chrome 上需要页面已经处于全屏状态才能生效所以调用顺序必须是先全屏再锁定。iOS 上的降级方案是通过 CSStransform把 Canvas 旋转 90 度同时交换宽高再用left和top把旋转后的画面推回可视区域。这个方案在 iPad 上实测可用但要注意触摸事件的坐标需要同步旋转否则点击位置会偏移。Demo 里通过 Unity 的Input系统自动处理了坐标映射不需要额外改 C# 代码。3.3 尺寸自适应与 resize 监听移动端浏览器旋转屏幕或地址栏收起时会触发 resizeCanvas 尺寸必须跟着变// 自适应 Canvas 尺寸 function resizeCanvas() { var canvas document.querySelector(#unity-canvas); if (!canvas) return; var w window.innerWidth; var h window.innerHeight; // 保持宽高比避免拉伸变形 var targetRatio 16 / 9; var currentRatio w / h; if (currentRatio targetRatio) { // 屏幕太宽以高度为准 canvas.style.height h px; canvas.style.width (h * targetRatio) px; } else { // 屏幕太高以宽度为准 canvas.style.width w px; canvas.style.height (w / targetRatio) px; } canvas.style.position absolute; canvas.style.left 50%; canvas.style.top 50%; canvas.style.transform translate(-50%, -50%); } // 监听尺寸变化 window.addEventListener(resize, resizeCanvas); window.addEventListener(orientationchange, function() { // 旋转后延迟执行等浏览器更新尺寸 setTimeout(resizeCanvas, 300); });orientationchange事件触发时浏览器还没更新innerWidth和innerHeight所以必须加setTimeout延迟。300ms 是个经验值iOS 上有时需要 500ms。宽高比targetRatio根据项目实际设计分辨率调整Demo 里用的是 16:9如果是 4:3 的课件项目就改成4/3。3.4 用户手势触发全屏的绑定所有浏览器都要求全屏必须由用户手势触发所以需要在页面加载后绑定一次性的点击事件// 首次用户交互时触发全屏 function bindFirstInteraction() { var triggered false; function onFirstTouch() { if (triggered) return; triggered true; requestFullscreen(document.documentElement).then(function() { lockLandscape(); resizeCanvas(); }).catch(function(err) { console.warn(全屏请求被拒绝:, err); }); // 移除监听只触发一次 document.removeEventListener(click, onFirstTouch); document.removeEventListener(touchstart, onFirstTouch); } document.addEventListener(click, onFirstTouch); document.addEventListener(touchstart, onFirstTouch); } // 页面加载完成后绑定 window.addEventListener(load, function() { bindFirstInteraction(); resizeCanvas(); });用triggered标志位防止重复触发因为click和touchstart在移动端可能同时触发。移除监听用removeEventListener不要用once: true因为部分旧版浏览器不支持这个选项。全屏成功后立即调用lockLandscape和resizeCanvas保证画面状态正确。4. 避坑与常见问题排查4.1 全屏后画面模糊或拉伸现象全屏后 Canvas 分辨率没变画面被拉伸模糊。原因是 Unity WebGL 的 Canvas 尺寸在构建时就固定了全屏只是 CSS 放大。解决办法是在 Unity 的 Player Settings 里把 Resolution and Presentation 的默认 Canvas 宽度高度设大一些比如 1920x1080然后在 JS 里通过unityInstance.Module.setCanvasSize动态调整。Demo 里在resizeCanvas之后调用了这个接口。4.2 iOS 上全屏按钮点击无反应现象iPhone 上点击页面没有任何全屏效果。原因是 iOS Safari 不支持requestFullscreen只支持webkitRequestFullscreen而且必须绑定在touchend事件上touchstart有时会被忽略。解决办法是把监听事件改成touchend并且确保webkitRequestFullscreen是在事件回调的同步代码里调用不能放在setTimeout或 Promise 里。4.3 安卓上横屏锁定报错现象控制台报NotSupportedError: screen.orientation.lock() is not available。原因是页面还没进入全屏状态就调用了 lock。解决办法是确保requestFullscreen的 Promise resolve 之后再调用lockLandscapeDemo 里已经用.then()保证了顺序。如果仍然报错检查是否在 iframe 里运行iframe 需要加allowfullscreen属性。4.4 旋转屏幕后触摸坐标偏移现象降级旋转方案下点击位置和实际按钮位置对不上。原因是 CSStransform旋转后触摸事件的坐标还是原始坐标系。解决办法是在 Unity 的 WebGL 输入模块里做坐标变换或者更简单的方式不用 CSS 旋转而是让 Unity 自己处理横屏通过Screen.orientation在 C# 侧判断。Demo 里两种方案都提供了CSS 旋转方案适合快速验证C# 方案适合正式项目。4.5 构建后模板没生效现象改了index.html但构建出来的页面还是默认样式。原因是 WebGL 模板必须放在Assets/WebGLTemplates/下并且在 Player Settings 的 Resolution and Presentation 里选中对应模板名称。另外模板文件夹名不能有空格index.html必须直接放在模板文件夹根目录不能嵌套子文件夹。5. 二次开发与改造思路从 Demo 到生产项目拿到这个 Demo 后最直接的改造是把全屏逻辑从模板里抽出来做成可配置的。我一般会在fullscreen.js顶部加一个配置对象var FullscreenConfig { autoFullscreen: true, // 是否首次交互自动全屏 lockOrientation: landscape, // 锁定方向可选 landscape/portrait targetRatio: 16 / 9, // 设计分辨率宽高比 resizeDelay: 300 // 旋转后延迟毫秒 };这样不同项目只需要改配置不用动核心逻辑。如果项目需要竖屏把lockOrientation改成portraittargetRatio改成9/16降级旋转逻辑也要相应调整。第二个改造点是和 Unity C# 侧的通信。Demo 里用的是页面加载自动触发但有些项目需要在游戏内特定场景才全屏比如点击“开始游戏”按钮后。这时候需要在.jslib里暴露一个函数// FullscreenBridge.jslib mergeInto(LibraryManager.library, { RequestFullscreen: function() { requestFullscreen(document.documentElement).then(function() { lockLandscape(); }); }, ExitFullscreen: function() { exitFullscreen(); } });然后在 C# 里调用using System.Runtime.InteropServices; using UnityEngine; public class FullscreenTrigger : MonoBehaviour { [DllImport(__Internal)] private static extern void RequestFullscreen(); [DllImport(__Internal)] private static extern void ExitFullscreen(); public void EnterFullscreen() { #if UNITY_WEBGL !UNITY_EDITOR RequestFullscreen(); #endif } public void QuitFullscreen() { #if UNITY_WEBGL !UNITY_EDITOR ExitFullscreen(); #endif } }注意#if UNITY_WEBGL !UNITY_EDITOR这个条件编译因为DllImport(__Internal)在编辑器里会报错必须排除编辑器环境。第三个改造方向是处理多平台差异。Windows 桌面浏览器上全屏后按 ESC 会退出需要监听fullscreenchange事件同步 Unity 内的状态document.addEventListener(fullscreenchange, function() { var isFullscreen !!document.fullscreenElement; // 通知 Unity 当前全屏状态 if (unityInstance) { unityInstance.SendMessage(FullscreenManager, OnFullscreenChanged, isFullscreen ? 1 : 0); } });unityInstance是 Unity 2020 之后构建产物暴露的全局对象旧版本用gameInstance。SendMessage的第一个参数是场景中 GameObject 的名字第二个是方法名第三个是字符串参数。C# 侧对应的方法签名是public void OnFullscreenChanged(string state)。验证改造是否成功我习惯用三个检查点Windows Chrome 上按 F11 和 ESC 切换全屏画面不闪烁安卓 Chrome 上旋转屏幕Canvas 自动适配且触摸准确iOS Safari 上点击页面全屏后横屏显示且地址栏隐藏。三个都过了才算真正可用。从那以后我每次做 WebGL 项目都会先把全屏横屏这段逻辑单独抽成一个模板构建一次就在三个平台上各跑一遍确认没问题再往下写业务代码。这个习惯帮我省掉了至少三次返工。希望帮到你。本文还有配套的精品资源点击获取