Unity WebGL全屏横屏适配:自定义HTML模板方案
发布时间:2026/9/8 3:40:02 锦皓数字建站

简介在Unity WebGL开发中跨平台的全屏与横屏适配常因浏览器安全策略和设备差异而变得棘手。这套Demo资源专门针对这一痛点给出完整解决示例面向需要将WebGL项目部署到Windows桌面、Android和iOS设备上的Unity开发者与移动端适配工程师。资源压缩包共145个文件仅1.35MB内部包含Unity场景与设置资产、C#逻辑脚本、自定义Shader与cginc、用于与浏览器交互的jslib桥接文件以及PNG图标和说明文档等结构精简方便按模块研读尤其便于快速定位全屏逻辑、平台判断与浏览器桥接等关键实现。已有1742人学习下载。Demo在初始化时通过Screen.fullScreen、Screen.orientation和Application.platform实现自动全屏与强制横屏利用UnityLoader.js桥接浏览器全屏API同时兼顾Android与iOS设备的方向变化生命周期提供了从平台判断到交互适配的完整代码组织方式。对需要快速落地WebGL自动全屏横屏功能的开发团队这套示例能显著降低自行调研兼容性问题的成本让跨平台显示适配变得更直接可控。 移动端打开Unity WebGL项目屏幕缩在中间还默认显示竖屏用起来相当难受Windows浏览器里点击运行画面却窝在页面一角体验也很有割裂感。我相信但凡做过WebGL发布的人大概率都被这两件事恶心过。今天分享一份Demo专门解决Unity WebGL打包后在Windows、安卓、苹果设备上的全屏与横屏适配问题。核心思路不是靠Unity侧硬改而是通过自定义HTML模板在浏览器层拦截用户手势、请求全屏、锁定横屏并把Unity初始化也挪到同一次交互里顺带解决自动播放限制。内容不复杂但涉及浏览器安全策略、iOS兼容、模板结构等多处细节值得完整拆开讲。1. 先搞清楚卡点在哪浏览器凭什么不让你全屏1.1 三层结构拆解需求如果是第一次做WebGL全屏很容易把它想成“Unity里选个全屏模式就完事”。实际发布到网页后你的Unity程序已经封装成一个跑在canvas里的Web应用能不能全屏、能不能横屏主机权限都在浏览器手里。整个问题得拆成三层看Unity侧决定画布分辨率、缩放策略、是否锁定屏幕方向以及最终输出什么样的页面模板。浏览器层处理用户手势、Fullscreen API、屏幕方向锁定向以及不同浏览器版本的兼容差异。设备系统层比如iPhone Safari对网页全屏和横屏锁定的限制Android Chrome则相对宽松。这三层任何一个地方没打通最后表现就是“要么点了没反应要么横屏变竖屏要么画面拉伸变形”。1.2 “一键全屏”为什么只能做成“一次点击”很多人期望网页一打开就自动全屏。我先说结论纯自动全屏在现代浏览器里做不到必须借用户的一次真实交互。浏览器不允许网页在未经过用户允许的情况下进入全屏从Chrome到Safari都有这套安全限制。移动端点击屏幕时浏览器甚至会先用自己浏览器UI响应网页层拿不到权限。所以方案退一步首次打开后展示一个“点击开始”遮罩页用户点击这个按钮时在事件回调里发起全屏请求、锁定横屏、启动Unity实例一次交互把所有权限都拿到手。这个“一次点击”不是妥协反而解决了不少问题。点击事件还能规避移动端自动播放音频受限的规矩——Unity初始化发生在真实手势里音频启动不会被拦截。实际操作中你会发现这个流程比“自动全屏”更稳定也更好排查。2. Unity侧这几项配置不改后面全白搭2.1 Player Settings中的关键项进入Build Settings选择WebGL平台后Player Settings里有几项和全屏横屏直接相关必须确认Resolution and Presentation - Fullscreen Mode我建议选Windowed然后画布分辨率由HTML页面控制。选Fullscreen反而会导致Unity启动时就用自己的内部逻辑去申请全屏页面脚本不方便接管出问题更难排查。Resolution and Presentation - Default Canvas Width/Height设计分辨率我用1280x720兼顾桌面和移动端写UI时也容易换算成相对比例。移动端分辨率差异大后面靠CSS缩放做适配。Resolution and Presentation - Canvas 缩放策略需要在模板CSS里处理Unity侧不用额外设置拉伸。Other Settings 里的 Auto Graphics API一般保持默认即可需要关注的是WebGL 2.0的支持新版Unity默认使用WebGL 2.0低版本Android浏览器可能出现兼容问题。打开浏览器开发者工具看Unity实例有没有报WebGL context相关错误。另外到Unity 2020以后的版本Player Settings里还有个“Player Data”下的 “WebGL Compression Format”建议保持默认的Brotli压缩率高加载快。别为了省事全选Disable会明显增加下载体积。2.2 自定义模板与构建产物结构搞定Player Settings后重点来了默认模板里的index.html是Unity官方写好的想插入全屏和横屏逻辑必须做自定义WebGL模板。在Assets目录下创建WebGLTemplates文件夹里面新建一个子文件夹名字就是模板名比如MyFullscreen把模板文件index.html和TemplateData子目录放进去。构建时就能在Player Settings里看到这个模板。构建输出目录里会生成index.html、Build/、TemplateData/三个部分。模板引擎会把%(UNITY_WEBGL_LOADER_URL%)、%(UNITY_WEBGL_BUILD_URL%)、%(UNITY_WEBGL_PRODUCT_NAME%)这些占位符替换成真实构建文件名。所以自定义模板的核心工作就是模仿Unity默认模板的结构在正确时机调用createUnityInstance同时在页面层补全屏和横屏逻辑。新版本Unity模板使用loader.js加createUnityInstance老版本是UnityLoader.js加UnityLoader.instantiate。如果你的项目还没升级需要注意API差异本方案里的逻辑不变只是初始化入口不同。3. 自定义HTML模板全屏、横屏的核心战场3.1 模板文件结构与初始化入口模板里的index.html必须包含一个canvas元素和加载逻辑。整体思路是页面加载后先只显示一个居中的开始按钮Unity实例暂不初始化用户点击按钮后同时调用全屏API、状态旋转API再创建Unity实例。这样既绕开浏览器限制又可以让Unity的加载进度覆盖全屏后的页面。一个精简的模板结构大概是这样的!DOCTYPE html html langzh-CN head meta charsetutf-8 meta nameviewport contentwidthdevice-width, heightdevice-height, initial-scale1.0, maximum-scale1.0, user-scalableno titleUnity WebGL Fullscreen Demo/title link relstylesheet hrefTemplateData/style.css style html, body { margin: 0; padding: 0; width: 100%; height: 100%; background: #000; overflow: hidden; } #startMask { position: fixed; left: 0; top: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: #000; z-index: 999; } #startBtn { width: 240px; height: 56px; font-size: 20px; background: #f26f2b; color: #fff; border: none; border-radius: 10px; cursor: pointer; } /style /head body div idstartMask button idstartBtn点击开始全屏体验/button /div div idunity-container canvas idunity-canvas/canvas /div script srcBuild/loader.js/script script var startMask document.getElementById(startMask); var startBtn document.getElementById(startBtn); var canvas document.getElementById(unity-canvas); startBtn.addEventListener(click, function () { // 1. 请求全屏 enterFullscreen(document.documentElement); // 2. 锁横屏兼容写法 lockLandscape(); // 3. 隐藏开始层创建Unity实例 startMask.style.display none; createUnityInstance(canvas, { arguments: [-fullscreen], dataUrl: Build/xxx.data, frameworkUrl: Build/xxx.framework.js, codeUrl: Build/xxx.wasm, streamingAssetsUrl: StreamingAssets, companyName: YourCompany, productName: YourProduct, productVersion: 1.0 }).then(function (unityInstance) { window.unityInstance unityInstance; }).catch(function (message) { alert(message); }); }); /script /body /html注意dataUrl、frameworkUrl、codeUrl在实际构建模板中要用%(UNITY_WEBGL_BUILD_URL%)之类的占位符替换不必手写文件名。核心逻辑顺序是全屏请求 - 横屏锁定 - 启动Unity。3.2 全屏API的兼容写法与触发姿势浏览器的Fullscreen API标准接口是element.requestFullscreen()但WebGL项目跑在真机时用户可能是老版本Chrome、旧版Safari或者老Android WebView兼容前缀还是得带上function enterFullscreen(element) { if (element.requestFullscreen) { element.requestFullscreen(); } else if (element.webkitRequestFullscreen) { element.webkitRequestFullscreen(); } else if (element.msRequestFullscreen) { element.msRequestFullscreen(); } else if (element.mozRequestFullScreen) { element.mozRequestFullScreen(); } }关于全屏目标元素我建议对document.documentElement做全屏而不是只对canvas做。如果只全屏canvasUnity画布全屏后可能因为尺寸变化产生黑边或拉伸对根元素全屏配合CSS把canvas铺满表现更统一。退出的处理也要兜底。用户按Esc或者F11退出全屏后Unity还是会继续渲染但时序上可能出现输入丢失之类的问题。最好监听fullscreenchange事件在退出全屏时恢复页面提示状态或者通知Unity暂停逻辑避免角色瞎跑。document.addEventListener(fullscreenchange, function () { var isFullscreen document.fullscreenElement || document.webkitFullscreenElement; if (!isFullscreen) { // 通知Unity比如调用SendMessage切换到暂停状态 if (window.unityInstance) { window.unityInstance.SendMessage(GameManager, OnFullscreenExit); } } });3.3 横屏锁定与iOS降级方案移动端横屏有两层手段。第一层是screen.orientation.lock这是最正统的方式Android端Chrome支持良好可以在全屏状态下强制锁到横屏function lockLandscape() { if (screen.orientation screen.orientation.lock) { var promise screen.orientation.lock(landscape); if (promise promise.catch) { promise.catch(function (err) { console.warn(orientation lock failed:, err); }); } } }第二层是iOS。iPhone上的Safari对screen.orientation.lock支持非常有限真机上经常直接没有这个函数或者抛出promise失败。解决办法就是降级处理监听orientationchange事件检测到竖屏时弹一个提示让用户手动旋转设备再用window.orientation 0 || window.orientation 180判断竖屏状态。配合一个半透明遮罩提示“请横屏使用”承担提醒职责。iOS Safari对Fullscreen API的支持也是分版本的iPhone上过去只能通过视频伪全屏新版支持的层级也不如Android完整。所以移动端最稳妥的组合拳是Android走screen.orientation.lockiOS走“检测竖屏 提示旋转”加上页面级的横屏CSS适配。如果项目一定要强行走iOS竖屏转正可以用CSS把整个canvas旋转90度并缩放但会引入触摸坐标映射的麻烦我后面会展开说。3.4 用“点击开始”遮罩串联整个启动流程把1.2节的设计落到模板里遮罩作用不只是好看。它把“浏览器禁止自动全屏”和“启动Unity”两个问题合并成一次点击解决。你还可以顺手把加载进度条也放进遮罩层里点击按钮后遮罩里的文字变成“加载中…”显示进度百分比加载完成后再切状态。注意一个细节不要在加载完成前就隐藏遮罩。Unity实例初始化过程中页面如果已经切到全屏canvas尺寸变化可能影响Unity的渲染尺寸建议在createUnityInstance.then回调里再隐藏遮罩。进度回调用onProgress比如createUnityInstance(canvas, config, function (progress) { var p Math.round(progress * 100); document.getElementById(progressText).textContent 加载中 p %; }).then(function (unityInstance) { startMask.style.display none; });这么做的好处是用户点击时听到的声音、触发的事件、Unity里申请的音频上下文都在同一用户手势链上不会因为自动播放策略被静音。实测下来很多项目困扰的“WebGL里没有声音”问题也会被这一步顺带解决。4. Unity与浏览器通信把全屏按钮搬进游戏内4.1 .jslib插件Unity调用JS的推荐方式有些场景不适合用网页遮罩比如游戏已经在Unity里跑起来了主菜单里放了一个“全屏”按钮希望点击后通知浏览器进入全屏。这时就需要Unity调用外部JS。最推荐的方式是.jslib插件它比老的Application.ExternalCall更干净且能直接封到构建产物里。在Assets/Plugins/WebGL目录下新建FullscreenPlugin.jslibmergeInto(LibraryManager.library, { JSRequestFullscreen: function () { var el document.documentElement; if (el.requestFullscreen) { el.requestFullscreen(); } else if (el.webkitRequestFullscreen) { el.webkitRequestFullscreen(); } }, JSLockLandscape: function () { if (screen.orientation screen.orientation.lock) { screen.orientation.lock(landscape).catch(function (e) { console.warn(lock landscape failed, e); }); } }, JSIsFullscreen: function () { return !!(document.fullscreenElement || document.webkitFullscreenElement); } });C#侧用DllImport绑定using System.Runtime.InteropServices; using UnityEngine; public class FullscreenBridge : MonoBehaviour { [DllImport(__Internal)] private static extern void JSRequestFullscreen(); [DllImport(__Internal)] private static extern void JSLockLandscape(); [DllImport(__Internal)] private static extern bool JSIsFullscreen(); public void RequestFullscreen() { if (Application.platform RuntimePlatform.WebGLPlayer) { JSRequestFullscreen(); JSLockLandscape(); } } }打包时会自动生成互操作代码WebGL平台下不需要额外配置。4.2 游戏内全屏按钮的绑定流程在Unity里把FullscreenBridge挂到一个空物体上UI按钮的OnClick事件指向RequestFullscreen。点击流程是Unity按钮 - C#方法 - jslib - 浏览器API。由于Unity UI的点击事件底层也是用户手势浏览器会认可这个手势链条全屏请求能正常生效。这里有个容易踩的坑如果你的脚本里用了SendMessage或者Application.ExternalCall在Unity 2020以上版本会被标记为过时。jslib虽然写起来多几个文件但跨版本兼容性最好我建议一次到位。另外如果Unity项目里还用了WebSocket、WebGL线程之类别在jslib里直接调用Unity的实例方法尽量只做浏览器层操作保持职责分离。4.3 监听全屏变化更新Unity状态全屏状态下用户按Esc退出浏览器不会通知Unity需要Unity主动轮询或由JS回调告知。在JS侧监听fullscreenchange后用unityInstance.SendMessage通知C#。在C#侧写一个接收方法public void OnFullscreenExit() { // 暂停游戏或显示恢复全屏的提示按钮 Debug.Log(Fullscreen exited); }JS侧对应document.addEventListener(fullscreenchange, function () { var isFullscreen document.fullscreenElement || document.webkitFullscreenElement; if (!isFullscreen window.unityInstance) { window.unityInstance.SendMessage(FullscreenBridge, OnFullscreenExit); } });这个方法也适用于安卓/iOS浏览器从全屏退出到普通浏览器页面时的状态同步。5. 真机实测常见坑与排查速查5.1 排查清单全屏毫无反应我把做这套方案时遇到的“点了没反应”情况整理成一个速查表建议照顺序排现象常见原因解决方案点击后控制台报错API can only be initiated by a user gesture全屏请求不在用户手势回调里检查发起请求的JS是否在click/touchstart事件同步代码块内在某个后台或平台预览里无法全屏页面被嵌套在iframe中iframe未授权给iframe标签加allowfullscreen属性老浏览器全屏无反应接口带私有前缀使用webkitRequestFullscreen等兼容写法在Unity里点击游戏UI按钮全屏失败调用了Application.ExternalCall或事件链被异步延后改用jslib确保请求在事件同步代码栈里Chrome全屏后瞬间退出关闭了浏览器“自动全屏”权限设置到站点设置里检查全屏权限这几个问题里iframe的allow属性最容易忽略。不少开发者是在企业后台、在线文档、第三方运营平台里嵌入WebGL项目iframe默认会限制全屏能力必须手动在嵌入方页面加权限属性。5.2 iOS端横屏失效的两种处理iOS Safari横屏方案只能靠“提示用户旋转”和“监听方向”兜底。我在一个实际项目里见过团队硬写了个CSS强制旋转方案在竖屏时把canvas容器用transform: rotate(90deg)旋转同时把容器的宽高互换强行让玩家用横屏视角。这个方案的代码量不大但副作用很明显。旋转后的canvas宽高和实际触摸坐标对不上Unity的Input系统拿到的点在旋转前坐标系里要么点击位置偏移要么UI响应错位。虽然可以额外写一套坐标反算逻辑但维护成本高。我个人的经验是不要做CSS强制旋转老老实实提示“请将设备旋转为横屏”再加上一个明显的旋转动画引导用户体验不比强制旋转差反而更稳定。iOS上另一个坑是screen.orientation.lock在某些版本里会返回一个rejected promise如果不catch控制台会飘红色报错。模板里一定要加.catch避免玩家开个控制台看到满屏错误误以为页面崩了。5.3 黑边、拉伸与输入坐标偏移画布铺满全屏后稍不留神就会出现黑边或拉伸。核心逻辑是保证Unity画布与页面容器的宽高比例一致。可以把canvas的CSS设为width: 100%; height: 100%;然后用object-fit或调整容器Padding来适配比例。Unity本身的分辨率适配策略在Player Settings的“WebGL Canvas Resize”选项里如果画布和窗口比例不一致推荐在模板JS里监听窗口大小变化动态调用Unity的SetCanvas方法或者触发一次Unity渲染区域的更新window.addEventListener(resize, function () { if (window.unityInstance) { window.unityInstance.SendMessage(GameManager, OnCanvasResize); } });输入坐标偏移主要集中在两处一是CSS缩放后Unity不知道实际点击点对应的逻辑坐标需要检查canvas的width/height属性和CSS尺寸是否一致二是全屏切换瞬间会触发一次resize此时Unity还没完成重新适配玩家立刻点击会错位。建议全屏切换后延迟200毫秒再显示交互UI给Unity一点缓冲时间。5.4 多平台真机验证顺序建议就算代码都没问题也建议按这套顺序在真机上过一遍先是Windows Chrome确认全屏和Esc退出正常再是Android Chrome重点看横屏锁定和返回键退出全屏然后是Android微信内置浏览器这块比较特殊X5内核的Fullscreen行为和白名单机制不稳定最好引导用户用系统浏览器打开最后是iPhone Safari重点看能否正确提示玩家转屏。微信内置浏览器对WebGL的兼容性一直是老大难。如果项目要优先兼顾移动端在产品层面最靠谱的做法是提供一个“在浏览器中打开”的引导页或者提醒用户在Game Center/App内跳转时选择系统浏览器。这不是技术偷懒而是实测后的务实选择。团队里如果有多台Android设备建议优先测试不同厂商的WebView。某些国产浏览器的内核对screen.orientation的锁定策略不同有的锁横屏后旋转到竖屏也能继续画满全屏但浏览器UI仍然露出一点体验参差不齐。只要核心全屏逻辑没问题这种差异可以接受。最后再分享一个小技巧。调试时总是反复点开始、退出全屏很烦可以在本地开发模板里通过URL加个参数例如?autostart1自动跳过遮罩并直接创建Unity实例方便快速看游戏逻辑发布时再把遮罩和全屏逻辑完整打开。这样既不影响开发效率又能保证线上体验严谨。整个方案其实不复杂关键是把浏览器安全策略、移动端兼容差异、Unity启动流程这三个环节串在一起。按这套模板改一次以后换项目直接搬全屏横屏这部分就不用再从头折腾了。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。