关于 scale-and-responsive
面向 Phaser 4 游戏开发者的缩放与响应式布局技能指南,介绍 ScaleManager 的配置和使用。涵盖画布缩放、居中、全屏、方向变化及尺寸调整事件。
主要功能
- 01多种画布缩放模式
- 02画布自动居中与缩放
- 03响应式尺寸调整与事件
- 04全屏切换
- 05方向变化处理
适用场景
- 01让游戏画布适配容器
- 02创建居中的等比布局
- 03窗口尺寸变化时调整场景
- 04响应移动设备方向变化
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ phaserjs/ phaser --skill scale-and-responsive安装后在 Claude Code 中输入/scale-and-responsive确认已加载。
详细介绍
缩放与响应式设计
如何在 Phaser 4 中使用 ScaleManager 进行缩放、居中、全屏、方向处理以及响应式调整大小。
关键源码路径: src/scale/ScaleManager.js、src/scale/const/、src/scale/events/、src/core/typedefs/ScaleConfig.js
相关技能: ../game-setup-and-config/SKILL.md
快速开始
缩放以适应并居中——响应式游戏最常见的配置:
const config = {
type: Phaser.AUTO,
scale: {
parent: 'game-container',
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
width: 800,
height: 600
},
scene: MyScene
};
const game = new Phaser.Game(config);
scale 配置对象会被解析为一个 Phaser.Core.Config 实例,ScaleManager 在启动期间读取该实例。ScaleManager 会设置 canvas 元素的尺寸,并应用 CSS 缩放使其适配父容器。
核心概念
ScaleManager (src/scale/ScaleManager.js)
ScaleManager 在 Game 启动序列期间创建,可通过 game.scale 或在 Scene 内通过 this.scale 访问。它继承自 EventEmitter。
三个内部 Size 组件驱动所有计算:
| 组件 | 属性 | 用途 |
|---|---|---|
gameSize | game.scale.gameSize | 来自配置的未经修改的游戏尺寸。用于世界边界、摄像机。通过 game.scale.width / game.scale.height 读取。 |
baseSize | game.scale.baseSize | 自动取整后的 gameSize。设置实际的 canvas.width 和 canvas.height 属性。 |
displaySize | game.scale.displaySize | 应用缩放模式、父容器边界和缩放后,经 CSS 缩放的 canvas 尺寸。设置 canvas.style.width / canvas.style.height。 |
缩放的工作原理是保持 canvas 元素尺寸固定(baseSize),并通过 CSS 属性(displaySize)将其拉伸。这相当于 CSS transform-scale,但没有浏览器前缀问题。
displayScale 属性(Phaser.Math.Vector2)保存比率 baseSize / canvasBounds,在内部用于输入坐标转换。
缩放模式 (src/scale/const/SCALE_MODE_CONST.js)
所有模式都在 Phaser.Scale.ScaleModes 上,并通过配置中的 scale.mode 设置:
| 常量 | 值 | 行为 |
|---|---|---|
NONE | 0 | 不自动缩放。Canvas 使用配置的宽度/高度。由你自行管理尺寸。如果你在外部调整 canvas 大小,请调用 game.scale.resize(w, h) 来更新内部状态。 |
WIDTH_CONTROLS_HEIGHT | 1 | 高度根据宽度自动调整以保持宽高比。 |
HEIGHT_CONTROLS_WIDTH | 2 | 宽度根据高度自动调整以保持宽高比。 |
FIT | 3 | 缩放以适应父容器内部,同时保持宽高比。可能留有空白区域(letterbox/pillarbox)。最常用的模式。 |
ENVELOP | 4 | 缩放以覆盖整个父容器,同时保持宽高比。可能超出父容器边界(内容会被裁剪)。 |
RESIZE | 5 | canvas 元素本身被调整大小以填满父容器。无 CSS 缩放——1:1 像素映射。gameSize、baseSize 和 displaySize 都会随之改变以匹配父容器。注意大尺寸显示器上的 GPU 填充率问题。 |
EXPAND | 6 | RESIZE 和 FIT 的混合体。可见区域调整大小以填满父容器,canvas 缩放以适应该区域内。v3.80 新增。 |
简写常量也可直接在 Phaser.Scale 上使用:Phaser.Scale.FIT、Phaser.Scale.RESIZE 等。
居中模式 (src/scale/const/CENTER_CONST.js)
通过配置中的 scale.autoCenter 设置。居中是通过在 canvas 上设置 CSS marginLeft 和 marginTop 实现的:
| 常量 | 值 | 行为 |
|---|---|---|
NO_CENTER | 0 | 不自动居中(默认)。 |
CENTER_BOTH | 1 | 在父容器内水平和垂直居中。 |
CENTER_HORIZONTALLY | 2 | 仅水平居中。 |
CENTER_VERTICALLY | 3 | 仅垂直居中。 |
父元素必须具有可计算的边界。如果父元素没有定义宽度/高度,居中将无法正常工作。
缩放 (src/scale/const/ZOOM_CONST.js)
通过配置中的 scale.zoom 设置。它会放大显示尺寸:
| 常量 | 值 | 行为 |
|---|---|---|
NO_ZOOM | 1 | 不缩放(默认)。 |
ZOOM_2X | 2 | 2 倍缩放 -- 适合低基础分辨率的像素画。 |
ZOOM_4X | 4 | 4 倍缩放。 |
MAX_ZOOM | -1 | 自动计算能放入父容器的最大整数缩放倍数。 |
你也可以传入任意数值。缩放会影响 CSS 显示尺寸,但不会影响画布分辨率。
方向 (src/scale/const/ORIENTATION_CONST.js)
通过 game.scale.orientation 读取。值为字符串:
Phaser.Scale.Orientation.LANDSCAPE='landscape-primary'Phaser.Scale.Orientation.LANDSCAPE_SECONDARY='landscape-secondary'Phaser.Scale.Orientation.PORTRAIT='portrait-primary'Phaser.Scale.Orientation.PORTRAIT_SECONDARY='portrait-secondary'
便捷布尔值:game.scale.isPortrait、game.scale.isLandscape(设备方向),game.scale.isGamePortrait、game.scale.isGameLandscape(游戏尺寸)。
常见模式
居中显示的 FIT 模式(最常用)
scale: {
parent: 'game-container',
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
width: 1280,
height: 720
}
游戏会保持其 16:9 宽高比,并在父容器内居中。空白区域会显示为黑边/竖黑边条。通过设置父容器的背景颜色来控制黑边的外观。
响应式尺寸调整(动态画布尺寸)
scale: {
parent: 'game-container',
mode: Phaser.Scale.RESIZE,
width: '100%',
height: '100%'
}
// In your scene -- respond to size changes:
create() {
this.scale.on('resize', this.handleResize, this);
}
handleResize(gameSize, baseSize, displaySize) {
this.cameras.resize(gameSize.width, gameSize.height);
// Reposition UI elements based on new dimensions
}
宽度/高度接受百分比字符串(例如 '100%'),它们会相对于父容器尺寸进行解析。如果父容器没有尺寸,则会回退到 window.innerWidth / window.innerHeight。
全屏切换
// Must be called from a pointerup gesture (not pointerdown)
this.input.on('pointerup', () => {
if (this.scale.isFullscreen) {
this.scale.stopFullscreen();
} else {
this.scale.startFullscreen();
}
});
// Or use the convenience method:
this.input.on('pointerup', () => {
this.scale.toggleFullscreen();
});
startFullscreen(fullscreenOptions)-- 请求浏览器全屏。默认选项:{ navigationUI: 'hide' }。stopFullscreen()-- 退出全屏模式。toggleFullscreen(fullscreenOptions)-- 在两者之间切换。isFullscreen-- 表示当前状态只读布尔值。
如果未配置 fullscreenTarget,Phaser 会创建一个临时 <div>,将画布移入其中,并将该 div 全屏显示。退出全屏时该 div 会被移除。
对于 iframe,必须包含 allowfullscreen 属性。
移动端方向处理
create() {
this.scale.on('orientationchange', (orientation) => {
if (orientation === Phaser.Scale.LANDSCAPE) {
// Show game UI
} else {
// Show "rotate device" message
}
});
// Lock orientation (mobile browsers only, limited support):
this.scale.lockOrientation('landscape');
}
固定尺寸(不缩放)
scale: {
mode: Phaser.Scale.NONE,
width: 800,
height: 600
}
在 NONE 模式下,如果你在外部更改画布尺寸,必须调用 game.scale.resize(newWidth, newHeight) 来更新所有内部组件,包括输入坐标。
使用最大缩放的像素画
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
width: 320,
height: 240,
zoom: Phaser.Scale.MAX_ZOOM
},
pixelArt: true
MAX_ZOOM 会计算能放入父容器的最大整数倍数。结合 pixelArt: true(会禁用抗锯齿),可以获得清晰的像素渲染效果。
用于网格对齐的吸附值
scale: {
mode: Phaser.Scale.FIT,
width: 800,
height: 600,
snap: { width: 16, height: 16 }
}
当浏览器尺寸变化时,尺寸会向下吸附到最近的网格倍数(使用向下取整)。最适合与 FIT 模式配合使用。也可以在运行时设置:game.scale.setSnap(16, 16),或用 game.scale.setSnap() 重置。
最小/最大尺寸约束
scale: {
mode: Phaser.Scale.FIT,
width: 800,
height: 600,
min: { width: 400, height: 300 },
max: { width: 1600, height: 1200 }
}
在缩放计算期间,显示尺寸会被限制在这些边界内。
配置参考
ScaleConfig (src/core/typedefs/ScaleConfig.js)
所有属性都放在游戏配置中的 scale 对象内:
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
width | number|string | 1024 | 游戏基础宽度。像 '100%' 这样的字符串会基于父级尺寸解析。 |
height | number|string | 768 | 游戏基础高度。像 '100%' 这样的字符串会基于父级尺寸解析。 |
zoom | number|ZoomType | 1 | 画布缩放因子。使用 Phaser.Scale.MAX_ZOOM 表示自动。 |
parent | HTMLElement|string|null | undefined | 父级 DOM 元素或其 ID。undefined = document.body。null = 无父级(由你管理画布放置)。 |
expandParent | boolean | true | 允许 ScaleManager 将父级/body 的 CSS 高度设置为 100%。 |
mode | ScaleModeType | NONE | 缩放模式常量(见上表)。 |
min | {width, height} | - | 最小画布尺寸。 |
max | {width, height} | - | 最大画布尺寸。 |
snap | {width, height} | - | 用于调整尺寸取整的对齐值。 |
autoRound | boolean | false | 将显示/样式尺寸向下取整,以提升低性能设备的性能。 |
autoCenter | CenterType | NO_CENTER | 自动居中模式(见上表)。 |
resizeInterval | number | 500 | 父级尺寸检查之间的毫秒数(回退轮询)。 |
fullscreenTarget | HTMLElement|string | undefined | 要发送到全屏的元素。如果未设置,Phaser 会创建一个包装 div。 |
事件 (src/scale/events/)
所有事件都位于 Phaser.Scale.Events 上,并由 ScaleManager 实例(game.scale)发出:
| 事件 | 字符串值 | 回调签名 | 何时 |
|---|---|---|---|
RESIZE | 'resize' | (gameSize, baseSize, displaySize, previousWidth, previousHeight) | 任何调整尺寸、刷新或缩放变化时。三个 Size 参数都有 .width、.height、.aspectRatio。 |
ORIENTATION_CHANGE | 'orientationchange' | (orientation) | 设备方向变化时。orientation 是来自 Phaser.Scale.Orientation 的字符串常量。 |
ENTER_FULLSCREEN | 'enterfullscreen' | () | 浏览器成功进入全屏时。 |
LEAVE_FULLSCREEN | 'leavefullscreen' | () | 浏览器离开全屏时(通过代码或用户按 ESC)。 |
FULLSCREEN_FAILED | 'fullscreenfailed' | (error) | 全屏请求被浏览器拒绝时。 |
FULLSCREEN_UNSUPPORTED | 'fullscreenunsupported' | () | 浏览器不支持 Fullscreen API 时。 |
API 快速参考
关键属性(除非另有说明,否则为只读)
| 属性 | 类型 | 描述 |
|---|---|---|
width | number | 游戏宽度(来自 gameSize.width)。 |
height | number | 游戏高度(来自 gameSize.height)。 |
isFullscreen | boolean | 当前是否处于全屏模式。 |
isPortrait | boolean | 设备是否处于竖屏方向。 |
isLandscape | boolean | 设备是否处于横屏方向。 |
isGamePortrait | boolean | 游戏尺寸是否高于宽。 |
isGameLandscape | boolean | 游戏尺寸是否宽于高。 |
scaleMode | number | 当前缩放模式常量。 |
orientation | string | 当前方向字符串。 |
zoom | number | 当前缩放因子。 |
autoRound | boolean | 尺寸是否自动向下取整。 |
autoCenter | number | 当前居中模式常量。 |
parentSize | Size | 计算得出的父容器尺寸。 |
gameSize | Size | 未修改的游戏尺寸。 |
baseSize | Size | Canvas 元素尺寸。 |
displaySize | Size | CSS 缩放后的显示尺寸。 |
displayScale | Vector2 | baseSize 与 canvasBounds 的比值(用于输入映射)。 |
canvas | HTMLCanvasElement | 游戏 canvas 元素。 |
canvasBounds | Rectangle | canvas 的 DOM 边界矩形。 |
parent | HTMLElement | 父 DOM 元素。 |
parentIsWindow | boolean | 如果父容器是 document.body 则为 true。 |
resizeInterval | number | 轮询间隔,单位为 ms(可写)。 |
关键方法
| 方法 | 返回 | 描述 |
|---|---|---|
resize(width, height) | this | 用于 NONE 模式:直接设置游戏、基础和显示尺寸。更新 canvas 元素和 CSS。 |
setGameSize(width, height) | this | 用于缩放模式(FIT 等):更改基础游戏尺寸并重新应用缩放。 |
setParentSize(width, height) | this | 手动设置父容器尺寸(当 parent: null 时有用)。 |
setZoom(value) | this | 在运行时更改缩放因子。 |
setMaxZoom() | this | 将缩放设置为适合父容器的最大整数。 |
setSnap(snapWidth, snapHeight) | this | 设置或重置吸附网格值。 |
getMaxZoom() | number | 计算(但不应用)最大缩放。 |
getViewPort(camera?, out?) | Rectangle | 获取可见区域矩形,可选针对特定相机。 |
refresh(prevW?, prevH?) | this | 强制重新计算缩放、边界、方向。发出 RESIZE。 |
startFullscreen(options?) | void | 进入全屏。必须从 pointerup 中调用。 |
stopFullscreen() | void | 退出全屏。 |
toggleFullscreen(options?) | void | 切换全屏状态。 |
lockOrientation(orientation) | boolean | 尝试锁定屏幕方向(仅限移动设备)。 |
transformX(pageX) | number | 将 DOM pageX 转换为游戏坐标。 |
transformY(pageY) | number | 将 DOM pageY 转换为游戏坐标。 |
getParentBounds() | boolean | 重新计算父容器尺寸。如果发生变化则返回 true。 |
updateCenter() | void | 重新计算并应用居中边距。 |
updateBounds() | void | 从 canvas 的边界客户端矩形更新 canvasBounds。 |
注意事项
-
父元素必须有尺寸。 ScaleManager 依赖父元素计算后的 CSS 尺寸。未设置样式的
<div>高度为零,会破坏居中和缩放。请为其指定明确的 CSS 尺寸,或让expandParent: true(默认值)将 body/父元素设置为 100% 高度。 -
父元素不能有内边距。 ScaleManager 不会考虑父元素的内边距。请将内边距应用到包裹元素上,或在父元素的父元素上使用外边距。
-
不要直接为 canvas 设置样式。 ScaleManager 会控制
canvas.style.width、canvas.style.height、marginLeft和marginTop。外部 CSS 作用于 canvas 会产生冲突。 -
全屏需要
pointerup,而非pointerdown。 在触摸设备上,除非文档已经接收过触摸输入,否则pointerdown全屏请求会被阻止。请始终使用pointerup以确保行为可靠。 -
iframe 中的全屏需要
allowfullscreen。 如果 iframe 元素上没有此属性,全屏请求会静默失败。 -
RESIZE 模式与 GPU 填充率。 RESIZE 会创建与父元素匹配的 1:1 像素 canvas。在高分辨率显示器上,这可能非常大。请在低端设备上监控性能。
-
resize()与setGameSize()。 仅在 NONE 模式下使用resize()(它会直接设置所有内容)。在使用 FIT/ENVELOP/等模式时使用setGameSize()(它会保留缩放模式的计算)。 -
iOS 高度问题。 当父元素是 iOS 上的 window 时,ScaleManager 会使用
GetInnerHeight来规避 Safari 动态工具栏影响getBoundingClientRect的问题。 -
百分比字符串需要父元素有尺寸。 设置
width: '100%'或height: '100%'会基于parentSize进行解析。如果父元素没有尺寸,则会回退到window.innerWidth/innerHeight。 -
resizeInterval是后备轮询。 现代浏览器会派发resize事件,但 ScaleManager 也会每隔resizeInterval毫秒(默认 500)轮询一次,以捕获旧版浏览器上的边缘情况。
源文件映射
| File | Purpose |
|---|---|
src/scale/ScaleManager.js | 主类——所有缩放、居中、全屏、方向逻辑。通过 game.scale 访问。 |
src/scale/const/SCALE_MODE_CONST.js | 缩放模式常量:NONE、WIDTH_CONTROLS_HEIGHT、HEIGHT_CONTROLS_WIDTH、FIT、ENVELOP、RESIZE、EXPAND。 |
src/scale/const/CENTER_CONST.js | 居中模式常量:NO_CENTER、CENTER_BOTH、CENTER_HORIZONTALLY、CENTER_VERTICALLY。 |
src/scale/const/ZOOM_CONST.js | 缩放常量:NO_ZOOM、ZOOM_2X、ZOOM_4X、MAX_ZOOM。 |
src/scale/const/ORIENTATION_CONST.js | 方向字符串常量:LANDSCAPE、LANDSCAPE_SECONDARY、PORTRAIT、PORTRAIT_SECONDARY。 |
src/scale/events/RESIZE_EVENT.js | 缩放事件定义。回调接收 gameSize、baseSize、displaySize、previousWidth、previousHeight。 |
src/scale/events/ENTER_FULLSCREEN_EVENT.js | 成功进入全屏时触发。 |
src/scale/events/LEAVE_FULLSCREEN_EVENT.js | 离开全屏时触发。 |
src/scale/events/FULLSCREEN_FAILED_EVENT.js | 全屏请求被拒绝时触发。 |
src/scale/events/FULLSCREEN_UNSUPPORTED_EVENT.js | 浏览器不支持 Fullscreen API 时触发。 |
src/scale/events/ORIENTATION_CHANGE_EVENT.js | 设备方向变化时触发。回调接收方向字符串。 |
src/core/typedefs/ScaleConfig.js | scale 配置对象的 JSDoc typedef。 |
src/structs/Size.js | gameSize、baseSize、displaySize 使用的 Size 组件类。 |
src/dom/GetInnerHeight.js | 用于获取准确视口高度的 iOS 专用变通方案。 |
src/dom/GetScreenOrientation.js | 根据尺寸判断当前屏幕方向。 |