yakoub-ai/phaser4-gamedev

phaser-mobile

This 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.

First seen Apr 12, 2026

Installation

$ npx skills add yakoub-ai/phaser4-gamedev --skill phaser-mobile

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from yakoub-ai/phaser4-gamedev · top by installs.

npx skills add yakoub-ai/phaser4-gamedev

Browse all from yakoub-ai/phaser4-gamedev

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 22
License MIT
Default branch main
Open issues 0
Status Active

Skill metadata

Parsed from SKILL.md frontmatter.

Version0.7.0

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 13,154 B
  • docs SUMMARY.md 271 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 140 installs

SKILL.md

Phaser 4 Mobile & Responsive

This guide covers Scale Manager configuration, touch controls, browser gesture prevention, mobile performance, Capacitor deployment, and PWA setup.

Scale Manager Configuration

The Scale Manager controls how Phaser maps its internal resolution to the screen.

// In GameConfig:
const config: Phaser.Types.Core.GameConfig = {
  scale: {
    mode: Phaser.Scale.FIT,
    autoCenter: Phaser.Scale.CENTER_BOTH,
    width: 800,
    height: 600,
  },
};

Scale Modes

Mode Behavior Use case
Phaser.Scale.FIT Letterboxed, preserves aspect ratio Fixed-resolution games (most common)
Phaser.Scale.ENVELOP Fills screen, may crop edges Backgrounds, casual games
Phaser.Scale.RESIZE Canvas resizes to exact window size Adaptive UI games (complex)
Phaser.Scale.NONE No scaling, original pixel size Desktop-only games

FIT is correct for most games. The canvas scales to fit the container while preserving your aspect ratio, adding letterbox bars if needed.

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.

RESIZE Mode with Dynamic Layout

// 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);
}

Touch Controls

Phaser pointer events work on mobile automatically — pointerdown, pointerup, pointermove all fire for touch.

Tap target minimum: 44×44 logical pixels. Smaller targets cause missed taps on mobile.

// 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());

For directional control on mobile, see references/mobile-patterns.mdVirtualGamepad class.

Responsive Layout

Position UI elements as fractions of screen dimensions so they work on any resolution.

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
}

Font size tip: Scale font sizes to height * 0.04–0.06. Fixed pixel sizes look enormous on small screens.

Preventing Browser Gestures

Mobile browsers intercept touch events for zoom, scroll, and context menus. Prevent these for a native-feeling game.

// 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
  }
}

Mobile Audio Unlock

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.

For games that play audio immediately on load (opening jingle, ambient sound), show a "Tap to Start" overlay:

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();
  }
}

Device Detection

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
}

Other useful device flags:

  • device.os.windows, device.os.macOS, device.os.linux
  • device.browser.chrome, device.browser.safari, device.browser.firefox
  • device.features.webGL — WebGL support check

Performance on Mobile

Mobile GPUs and CPUs are significantly weaker than desktop. Apply these constraints to maintain 60fps on mid-range devices.

Textures:

  • Maximum atlas size: 2048×2048 px. Prefer 1024×1024 for compatibility with older devices.
  • Use texture atlases — minimizes GPU state switches between draw calls.
  • Reduce total unique textures: fewer unique textures = fewer draw calls.

Physics:

  • Fewer dynamic bodies = better FPS. Target under 50 simultaneous dynamic bodies on mobile.
  • Disable physics on objects that are off-screen.

Particles:

  • Set maxParticles on all emitters. Hard limit: 50–100 particles max on mobile.
  • Use simple particle textures (single-color squares/circles) — complex textures cost more.

Camera effects:

  • camera.shake() and rapid camera.zoom are GPU-heavy. Use sparingly.
  • Simple screen flash (camera.flash()) is fine.

Pixel art games:

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 monitoring in debug builds:

// 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)}`);
  }
}

Target 60fps. If you can't sustain 60, accept 30fps with physics.arcade.fps: 30 and this.game.loop.targetFps = 30 — choppy 45fps is worse than smooth 30fps.

Capacitor Deployment (iOS / Android)

Capacitor wraps your web build in a native WebView, giving you a real app store binary.

# 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

After cap open ios, set your Team in Xcode's Signing & Capabilities tab, then build to device or simulator.

Capacitor Config (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;

Native Features via Capacitor Plugins

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 Setup

Turn your game into an installable Progressive Web App with two files.

// 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" }
  ]
}
<!-- In index.html <head>: -->
<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">

For full PWA with offline support, add a service worker (Vite's vite-plugin-pwa handles this automatically).

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}'],
      },
    }),
  ],
};

Device-Specific Profiles

See references/device-profiles.md for complete profiles with gotchas and recommended configs.

Quick Decision Matrix

Decision iOS Safari Android Chrome Desktop Capacitor PWA
Audio format priority mp3 (ogg unsupported) mp3+ogg any mp3+ogg mp3+ogg
Max atlas size 2048x2048 2048x2048 (low-end) 4096+ 2048x2048 2048x2048
Orientation lock Capacitor only Fullscreen API N/A Native plugin manifest.json
Virtual controls Required Required Optional Required Required
Haptic feedback No No No Yes (plugin) No
Offline support Limited (50MB) Yes N/A Built-in Service worker
Install prompt Add to Home Screen Install banner N/A App Store Browser prompt

Choosing Your Primary Platform

  • Web-only (itch.io, portfolio): Desktop-first config, FIT scale mode, 800x600 or 1280x720
  • Mobile-first: FIT scale mode, 640x360 landscape or 360x640 portrait, virtual controls
  • Cross-platform: FIT scale mode, detect device at runtime, adapt controls and asset quality
  • Native app (App Store / Play Store): Capacitor + platform-specific optimizations

Additional Resources

Reference Files

  • references/mobile-patterns.md — Full responsive GameConfig, VirtualGamepad class (joystick + buttons), OrientationGuard overlay, MobileAudioUnlock component, DeviceCapabilities detection, Capacitor native plugin integration
  • references/device-profiles.md — Device-specific optimization profiles for iOS Safari, Android Chrome, Desktop, Capacitor (iOS/Android), and PWA with recommended GameConfig per target