$npx -y skills add Yakoub-ai/phaser4-gamedev --skill phaser-mobileThis skill should be used when the user asks to "mobile game", "responsive game", "touch controls", "deploy to phone", "Capacitor", "PWA game", "scale manager", "orientation", "iOS game", "Android game", "full screen game", "mobile performance", etc.
| 1 | # Phaser 4 Mobile & Responsive |
| 2 | |
| 3 | This guide covers Scale Manager configuration, touch controls, browser gesture prevention, mobile performance, Capacitor deployment, and PWA setup. |
| 4 | |
| 5 | ## Scale Manager Configuration |
| 6 | |
| 7 | The Scale Manager controls how Phaser maps its internal resolution to the screen. |
| 8 | |
| 9 | ```typescript |
| 10 | // In GameConfig: |
| 11 | const config: Phaser.Types.Core.GameConfig = { |
| 12 | scale: { |
| 13 | mode: Phaser.Scale.FIT, |
| 14 | autoCenter: Phaser.Scale.CENTER_BOTH, |
| 15 | width: 800, |
| 16 | height: 600, |
| 17 | }, |
| 18 | }; |
| 19 | ``` |
| 20 | |
| 21 | ### Scale Modes |
| 22 | |
| 23 | | Mode | Behavior | Use case | |
| 24 | |---|---|---| |
| 25 | | `Phaser.Scale.FIT` | Letterboxed, preserves aspect ratio | Fixed-resolution games (most common) | |
| 26 | | `Phaser.Scale.ENVELOP` | Fills screen, may crop edges | Backgrounds, casual games | |
| 27 | | `Phaser.Scale.RESIZE` | Canvas resizes to exact window size | Adaptive UI games (complex) | |
| 28 | | `Phaser.Scale.NONE` | No scaling, original pixel size | Desktop-only games | |
| 29 | |
| 30 | **FIT** is correct for most games. The canvas scales to fit the container while preserving your aspect ratio, adding letterbox bars if needed. |
| 31 | |
| 32 | **RESIZE** gives you a fluid canvas but requires all UI and layout code to respond to size changes. Use it only when you genuinely need the game to fill every device shape. |
| 33 | |
| 34 | ### RESIZE Mode with Dynamic Layout |
| 35 | |
| 36 | ```typescript |
| 37 | // GameConfig for RESIZE: |
| 38 | scale: { |
| 39 | mode: Phaser.Scale.RESIZE, |
| 40 | autoCenter: Phaser.Scale.CENTER_BOTH, |
| 41 | // No fixed width/height — canvas matches window |
| 42 | } |
| 43 | |
| 44 | // In the scene: |
| 45 | create(): void { |
| 46 | this.layoutUI(this.scale.width, this.scale.height); |
| 47 | |
| 48 | // Re-layout whenever window resizes (orientation change, keyboard appearing, etc.) |
| 49 | this.scale.on('resize', (size: Phaser.Structs.Size) => { |
| 50 | this.cameras.main.setSize(size.width, size.height); |
| 51 | this.layoutUI(size.width, size.height); |
| 52 | }); |
| 53 | } |
| 54 | |
| 55 | private layoutUI(w: number, h: number): void { |
| 56 | this.scoreText?.setPosition(w * 0.05, h * 0.05); |
| 57 | this.healthBar?.setPosition(w * 0.5, h * 0.95); |
| 58 | this.pauseBtn?.setPosition(w - 40, 40); |
| 59 | } |
| 60 | ``` |
| 61 | |
| 62 | ## Touch Controls |
| 63 | |
| 64 | Phaser pointer events work on mobile automatically — `pointerdown`, `pointerup`, `pointermove` all fire for touch. |
| 65 | |
| 66 | **Tap target minimum:** 44×44 logical pixels. Smaller targets cause missed taps on mobile. |
| 67 | |
| 68 | ```typescript |
| 69 | // Simple tap interaction (works on both mouse and touch) |
| 70 | const btn = this.add.image(x, y, 'button') |
| 71 | .setInteractive() |
| 72 | .on('pointerdown', () => this.handleTap()) |
| 73 | .on('pointerover', () => btn.setTint(0xdddddd)) |
| 74 | .on('pointerout', () => btn.clearTint()); |
| 75 | ``` |
| 76 | |
| 77 | For directional control on mobile, see `references/mobile-patterns.md` — `VirtualGamepad` class. |
| 78 | |
| 79 | ## Responsive Layout |
| 80 | |
| 81 | Position UI elements as fractions of screen dimensions so they work on any resolution. |
| 82 | |
| 83 | ```typescript |
| 84 | create(): void { |
| 85 | const { width, height } = this.scale; |
| 86 | |
| 87 | // Top-left HUD |
| 88 | this.scoreText = this.add.text(width * 0.05, height * 0.05, 'Score: 0', { |
| 89 | fontSize: `${Math.round(height * 0.05)}px`, |
| 90 | color: '#ffffff', |
| 91 | }); |
| 92 | |
| 93 | // Bottom-center action button |
| 94 | this.actionBtn = this.add.image(width * 0.5, height * 0.9, 'btn-action') |
| 95 | .setInteractive() |
| 96 | .setDisplaySize(width * 0.15, width * 0.15); // square button, proportional to width |
| 97 | } |
| 98 | ``` |
| 99 | |
| 100 | **Font size tip:** Scale font sizes to `height * 0.04–0.06`. Fixed pixel sizes look enormous on small screens. |
| 101 | |
| 102 | ## Preventing Browser Gestures |
| 103 | |
| 104 | Mobile browsers intercept touch events for zoom, scroll, and context menus. Prevent these for a native-feeling game. |
| 105 | |
| 106 | ```typescript |
| 107 | // In your game's index.html or a boot script (not inside Phaser scenes): |
| 108 | |
| 109 | // Prevent pinch-to-zoom and scroll |
| 110 | document.addEventListener('touchmove', (e) => e.preventDefault(), { passive: false }); |
| 111 | |
| 112 | // Prevent double-tap zoom on iOS |
| 113 | let lastTap = 0; |
| 114 | document.addEventListener('touchend', (e) => { |
| 115 | const now = Date.now(); |
| 116 | if (now - lastTap < 300) e.preventDefault(); |
| 117 | lastTap = now; |
| 118 | }); |
| 119 | |
| 120 | // Prevent right-click / long-press context menu |
| 121 | document.addEventListener('contextmenu', (e) => e.preventDefault()); |
| 122 | ``` |
| 123 | |
| 124 | ```typescript |
| 125 | // Inside a Phaser scene: |
| 126 | // Disable right-click context menu on the canvas |
| 127 | this.input.mouse?.disableContextMenu(); |
| 128 | ``` |
| 129 | |
| 130 | ```typescript |
| 131 | // Lock to landscape orientation (for most action games) |
| 132 | // Must be triggered from a user interaction (button press), not on load |
| 133 | async lockOrientation(): Promise<void> { |
| 134 | try { |
| 135 | await screen.orientation?.lock?.('landscape-primary'); |
| 136 | } catch { |
| 137 | // Not supported on all browsers/platforms — fail silently |
| 138 | } |
| 139 | } |
| 140 | ``` |
| 141 | |
| 142 | ## Mobile Audio Unlock |
| 143 | |
| 144 | iOS and Android block audio playback until the user interacts with the page. Phaser handles this automatically by listening for the first pointer/key event and resuming `AudioContext` at that moment. |
| 145 | |
| 146 | For games that play audio im |