关于 phaser-mobile
phaser-mobile 是面向开发者的指南,介绍如何让 Phaser 4 游戏适配移动设备和不同屏幕尺寸。内容涵盖缩放、触控、性能、Capacitor 部署及 PWA 设置。
主要功能
- 01Scale Manager 配置
- 02触控操作与响应式布局
- 03阻止浏览器手势
- 04移动端性能建议
- 05Capacitor 部署步骤
- 06PWA 设置指南
适用场景
- 01让游戏界面适配不同分辨率
- 02为移动游戏添加触控操作
- 03准备通过 Capacitor 部署 Phaser 游戏
- 04设置可安装的 PWA
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ yakoub-ai/ phaser4-gamedev --skill phaser-mobile安装后在 Claude Code 中输入/phaser-mobile确认已加载。
附带文件references/device-profiles.mdreferences/mobile-patterns.md
详细介绍
Phaser 4 移动端与响应式
本指南介绍 Scale Manager 配置、触控操作、浏览器手势阻止、移动端性能、Capacitor 部署和 PWA 设置。
Scale Manager 配置
Scale Manager 控制 Phaser 如何将内部解析度映射到屏幕上。
// In GameConfig:
const config: Phaser.Types.Core.GameConfig = {
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
width: 800,
height: 600,
},
};
缩放模式
| 模式 | 行为 | 适用场景 |
|---|---|---|
Phaser.Scale.FIT | 保留宽高比,添加黑边 | 固定解析度游戏(最常见) |
Phaser.Scale.ENVELOP | 填满屏幕,边缘可能被裁切 | 背景、休闲游戏 |
Phaser.Scale.RESIZE | 画布调整为窗口的精确尺寸 | 自适应 UI 游戏(较复杂) |
Phaser.Scale.NONE | 不缩放,保持原始像素尺寸 | 仅限桌面端的游戏 |
对大多数游戏来说,FIT 是正确的选择。画布会缩放以适应容器,同时保持宽高比;必要时会添加黑边。
RESIZE 可让画布灵活适应尺寸,但要求所有 UI 和布局代码都能响应尺寸变化。只有确实需要让游戏填满各种形状的设备屏幕时才使用它。
使用动态布局的 RESIZE 模式
// GameConfig for RESIZE:
scale: {
mode: Phaser.Scale.RESIZE,
autoCenter: Phaser.Scale.CENTER_BOTH,
// No fixed width/height — canvas matches window
}
// In the scene:
create(): void {
this.layoutUI(this.scale.width, this.scale.height);
// Re-layout whenever window resizes (orientation change, keyboard appearing, etc.)
this.scale.on('resize', (size: Phaser.Structs.Size) => {
this.cameras.main.setSize(size.width, size.height);
this.layoutUI(size.width, size.height);
});
}
private layoutUI(w: number, h: number): void {
this.scoreText?.setPosition(w * 0.05, h * 0.05);
this.healthBar?.setPosition(w * 0.5, h * 0.95);
this.pauseBtn?.setPosition(w - 40, 40);
}
触控操作
Phaser 的指针事件会自动支持移动端——触摸时都会触发 pointerdown、pointerup 和 pointermove。
点击目标最小尺寸: 44×44 个逻辑像素。目标太小会导致移动端漏接点击。
// Simple tap interaction (works on both mouse and touch)
const btn = this.add.image(x, y, 'button')
.setInteractive()
.on('pointerdown', () => this.handleTap())
.on('pointerover', () => btn.setTint(0xdddddd))
.on('pointerout', () => btn.clearTint());
如需了解移动端方向控制,请参阅 references/mobile-patterns.md 中的 VirtualGamepad 类。
响应式布局
将 UI 元素的位置设置为屏幕尺寸的比例,以适配任意解析度。
create(): void {
const { width, height } = this.scale;
// Top-left HUD
this.scoreText = this.add.text(width * 0.05, height * 0.05, 'Score: 0', {
fontSize: `${Math.round(height * 0.05)}px`,
color: '#ffffff',
});
// Bottom-center action button
this.actionBtn = this.add.image(width * 0.5, height * 0.9, 'btn-action')
.setInteractive()
.setDisplaySize(width * 0.15, width * 0.15); // square button, proportional to width
}
字体大小提示: 将字体大小设为 height * 0.04–0.06。在小屏幕上,固定像素尺寸会显得过大。
阻止浏览器手势
移动浏览器会拦截触摸事件,用于缩放、滚动和显示上下文菜单。请阻止这些行为,让游戏体验更接近原生应用。
// In your game's index.html or a boot script (not inside Phaser scenes):
// Prevent pinch-to-zoom and scroll
document.addEventListener('touchmove', (e) => e.preventDefault(), { passive: false });
// Prevent double-tap zoom on iOS
let lastTap = 0;
document.addEventListener('touchend', (e) => {
const now = Date.now();
if (now - lastTap < 300) e.preventDefault();
lastTap = now;
});
// Prevent right-click / long-press context menu
document.addEventListener('contextmenu', (e) => e.preventDefault());
// Inside a Phaser scene:
// Disable right-click context menu on the canvas
this.input.mouse?.disableContextMenu();
// Lock to landscape orientation (for most action games)
// Must be triggered from a user interaction (button press), not on load
async lockOrientation(): Promise<void> {
try {
await screen.orientation?.lock?.('landscape-primary');
} catch {
// Not supported on all browsers/platforms — fail silently
}
}
解锁移动端音频
iOS 和 Android 会阻止音频播放,直到用户与页面交互。Phaser 会自动监听首次指针或按键事件,并在此时恢复 AudioContext。
对于加载后立即播放音频的游戏(开场音乐、环境音),请显示“点击开始”覆盖层:
create(): void {
this.bgMusic = this.sound.add('music-main', { loop: true, volume: 0.6 });
if (this.sound.locked) {
// Show overlay — audio will unlock when user taps
const overlay = this.add.rectangle(
this.scale.width / 2, this.scale.height / 2,
this.scale.width, this.scale.height,
0x000000, 0.75
).setInteractive().setDepth(100);
this.add.text(
this.scale.width / 2, this.scale.height / 2,
'TAP TO START', { fontSize: '32px', color: '#ffffff' }
).setOrigin(0.5).setDepth(101);
this.sound.once(Phaser.Sound.Events.UNLOCKED, () => {
overlay.destroy();
this.bgMusic.play();
});
} else {
this.bgMusic.play();
}
}
设备检测
create(): void {
const device = this.sys.game.device;
const isMobile = device.os.android || device.os.iOS;
const isTablet = isMobile && Math.min(window.innerWidth, window.innerHeight) > 600;
const hasTouch = device.input.touch;
if (isMobile) {
this.createVirtualControls();
} else {
// Show keyboard hint for desktop
this.add.text(16, this.scale.height - 30, 'WASD / Arrow Keys to move', {
fontSize: '14px', color: '#aaaaaa',
});
}
}
private createVirtualControls(): void {
// See references/mobile-patterns.md for VirtualGamepad class
}
其他实用的设备标志:
device.os.windows、device.os.macOS、device.os.linuxdevice.browser.chrome、device.browser.safari、device.browser.firefoxdevice.features.webGL— 检查是否支持 WebGL
移动端性能
移动端 GPU 和 CPU 的性能明显弱于桌面端。请遵守以下限制,以便在中端设备上保持 60fps。
纹理:
- 图集最大尺寸:2048×2048 px。为兼容较旧设备,建议使用 1024×1024。
- 使用纹理图集——这样可以尽量减少绘制调用之间的 GPU 状态切换。
- 减少不同纹理的总数:不同纹理越少,绘制调用就越少。
物理:
- 动态物体越少,FPS 越高。移动端同时存在的动态物体建议少于 50 个。
- 对屏幕外的物体禁用物理效果。
粒子:
- 为所有发射器设置
maxParticles。移动端粒子数量硬性上限为 50–100 个。 - 使用简单的粒子纹理(纯色方块或圆形)——复杂纹理的开销更大。
摄像机效果:
camera.shake()和快速调整camera.zoom对 GPU 开销很大。请谨慎使用。- 简单的屏幕闪光(
camera.flash())没有问题。
像素画游戏:
const config: Phaser.Types.Core.GameConfig = {
pixelArt: true, // disables antialiasing — required for crisp pixel art
roundPixels: true, // prevents sub-pixel rendering artifacts
// These two together are a free performance win for pixel-art games
};
调试版本中的 FPS 监控:
// Show FPS counter during development
create(): void {
if (import.meta.env.DEV) {
this.add.text(4, 4, '', { fontSize: '12px', color: '#00ff00' })
.setDepth(999)
.setScrollFactor(0);
// Update in update():
// fpsText.setText(`FPS: ${Math.round(this.game.loop.actualFps)}`);
}
}
目标为 60fps。如果无法稳定保持 60fps,可将 physics.arcade.fps: 30 和 this.game.loop.targetFps = 30 设为 30fps——不稳定的 45fps 不如流畅的 30fps。
Capacitor 部署(iOS / Android)
Capacitor 会将 Web 构建包封装在原生 WebView 中,生成可上传到应用商店的二进制文件。
# Install Capacitor
npm install @capacitor/core @capacitor/cli @capacitor/ios @capacitor/android
# Initialize (run once)
npx cap init "My Game" com.mygame.app --web-dir=dist
# Build the web project
npm run build
# Add native platforms (run once)
npx cap add ios
npx cap add android
# Sync web build into native projects
npx cap sync
# Open in Xcode (requires macOS + Xcode)
npx cap open ios
# Open in Android Studio
npx cap open android
执行 cap open ios 后,在 Xcode 的 Signing & Capabilities 标签页中设置团队,然后构建并部署到设备或模拟器。
Capacitor 配置(capacitor.config.ts)
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.mygame.app',
appName: 'My Game',
webDir: 'dist',
server: {
androidScheme: 'https',
},
plugins: {
SplashScreen: {
launchShowDuration: 0, // Hide splash immediately for games with their own loading screen
},
},
};
export default config;
通过 Capacitor 插件使用原生功能
import { Haptics, ImpactStyle } from '@capacitor/haptics';
import { ScreenOrientation } from '@capacitor/screen-orientation';
// Vibration on hit
await Haptics.impact({ style: ImpactStyle.Medium });
// Lock orientation (native — more reliable than screen.orientation.lock on mobile)
await ScreenOrientation.lock({ orientation: 'landscape' });
PWA 设置
只需两个文件,即可将游戏转变为可安装的渐进式 Web 应用(PWA)。
// public/manifest.json
{
"name": "My Game",
"short_name": "MyGame",
"description": "An awesome Phaser 4 game",
"display": "fullscreen",
"orientation": "landscape",
"background_color": "#000000",
"theme_color": "#000000",
"start_url": "./index.html",
"icons": [
{ "src": "icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
{ "src": "icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}
<link rel="manifest" href="./manifest.json">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="theme-color" content="#000000">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
如需支持离线使用的完整 PWA,请添加服务工作线程(Vite 的 vite-plugin-pwa 会自动处理)。
npm install -D vite-plugin-pwa
// vite.config.ts
import { VitePWA } from 'vite-plugin-pwa';
export default {
plugins: [
VitePWA({
registerType: 'autoUpdate',
manifest: false, // use your own public/manifest.json
workbox: {
globPatterns: ['**/*.{js,css,html,png,ogg,mp3,json}'],
},
}),
],
};
特定设备配置
完整配置(包括注意事项和推荐设置)请参阅 references/device-profiles.md。
快速决策矩阵
| 决策 | iOS Safari | Android Chrome | 桌面端 | Capacitor | PWA |
|---|---|---|---|---|---|
| 音频格式优先级 | mp3(不支持 ogg) | mp3+ogg | 任意 | mp3+ogg | mp3+ogg |
| 图集最大尺寸 | 2048x2048 | 2048x2048(低端设备) | 4096+ | 2048x2048 | 2048x2048 |
| 锁定屏幕方向 | 仅限 Capacitor | 全屏 API | 不适用 | 原生插件 | manifest.json |
| 虚拟控制 | 必需 | 必需 | 可选 | 必需 | 必需 |
| 触觉反馈 | 否 | 否 | 否 | 是(插件) | 否 |
| 离线支持 | 有限(50MB) | 是 | 不适用 | 内置 | 服务工作线程 |
| 安装提示 | 添加到主屏幕 | 安装横幅 | 不适用 | App Store | 浏览器提示 |
选择主要平台
- 仅限 Web(itch.io、作品集): 桌面优先配置,FIT 缩放模式,800x600 或 1280x720
- 移动设备优先: FIT 缩放模式,640x360 横屏或 360x640 竖屏,虚拟控制
- 跨平台: FIT 缩放模式,运行时检测设备,并调整控制方式和资源质量
- 原生应用(App Store / Play Store): Capacitor + 平台专属优化
更多资源
参考文件
references/mobile-patterns.md— 完整的响应式 GameConfig、VirtualGamepad 类(摇杆 + 按钮)、OrientationGuard 覆盖层、MobileAudioUnlock 组件、DeviceCapabilities 检测、Capacitor 原生插件集成references/device-profiles.md— iOS Safari、Android Chrome、桌面端、Capacitor(iOS/Android)和 PWA 的特定设备优化配置,以及适用于各目标平台的推荐 GameConfig