BMAD-METHOD/expansion-packs/bmad-wechat-mini-game-dev/data/development-guidelines.md

11 KiB

Game Development Guidelines (WeChat Mini Game, JavaScript)

Overview

This document establishes coding standards, architectural patterns, and development practices for game development using the WeChat Mini Game framework with JavaScript. These guidelines ensure consistency, performance (60+ FPS target), maintainability, and enforce Test-Driven Development (TDD) across all game development stories.

Performance Philosophy

  • "Measure, don't guess" - Profile everything with the WeChat Mini Game profiler
  • "Focus on what matters: framerate and responsiveness" - 60+ FPS is the minimum, not the target
  • "The best code is no code" - Simplicity beats cleverness
  • "Think about cache misses, not instruction counts" - Memory access patterns matter most

JavaScript Standards

Naming Conventions

Classes and Scripts:

  • PascalCase for class names: PlayerController, GameData, InventorySystem
  • Snake_case for file names: player_controller.js, game_data.js
  • Descriptive names that indicate purpose: GameStateManager not GSM

Functions and Methods:

  • camelCase for functions: calculateDamage(), processInput()
  • Descriptive verb phrases: activateShield() not shield()
  • Private methods prefix with underscore: _updateHealth()

Variables and Properties:

  • camelCase for variables: playerHealth, movementSpeed
  • Constants in UPPER_SNAKE_CASE: MAX_HEALTH, GRAVITY_FORCE
  • Boolean variables with is/has/can prefix: isAlive, hasKey, canJump
  • Event names in snake_case: health_changed, level_completed

WeChat Mini Game Architecture Patterns

Object-Based Architecture

Scene Composition Over Inheritance:

// Player.js - Single responsibility component
class PlayerHealth {
    constructor(maxHealth) {
        this.maxHealth = maxHealth;
        this.currentHealth = maxHealth;
    }

    takeDamage(amount) {
        this.currentHealth = Math.max(0, this.currentHealth - amount);
        if (this.currentHealth === 0) {
            // emit 'died' event
        }
    }
}

Event-Based Communication

Decouple Systems with Events:

// GameManager.js - Global manager
class GameManager {
    constructor() {
        this.score = 0;
        this.currentLevel = 1;
    }

    startGame() {
        this.score = 0;
        this.currentLevel = 1;
        // emit 'game_started' event
    }
}

// Player.js - Connects to events
class Player {
    constructor(gameManager) {
        gameManager.on('game_over', this._onGameOver.bind(this));
    }

    _onGameOver() {
        // Stop player movement
    }
}

JSON-Based Data Management

Use JSON for Game Data:

// weapon_data.json
{
    "weaponName": "Sword",
    "damage": 10,
    "attackSpeed": 1.0,
    "sprite": "images/sword.png"
}

// Weapon.js - Uses the data
class Weapon {
    constructor(weaponData) {
        this.weaponData = weaponData;
    }

    attack() {
        return this.weaponData ? this.weaponData.damage : 0;
    }
}

Performance Optimization

Object Pooling (MANDATORY for Spawned Objects)

// ObjectPool.js - Generic pooling system
class ObjectPool {
    constructor(scene, initialSize) {
        this.scene = scene;
        this._pool = [];

        for (let i = 0; i < initialSize; i++) {
            let instance = this.scene.create();
            instance.visible = false;
            this._pool.push(instance);
        }
    }

    getObject() {
        for (let obj of this._pool) {
            if (!obj.visible) {
                obj.visible = true;
                return obj;
            }
        }

        // Expand pool if needed
        let newObj = this.scene.create();
        this._pool.push(newObj);
        return newObj;
    }

    returnObject(obj) {
        obj.visible = false;
    }
}

Process Optimization

Use Appropriate Process Methods:

// For physics calculations (fixed timestep)
function _physics_process(delta) {
    // Movement, collision detection
}

// For visual updates and input
function _process(delta) {
    // Animations, UI updates
}

// Use timers or events instead of checking every frame
function _ready() {
    // Use setTimeout or setInterval
}

Memory Management

Prevent Memory Leaks:

// Clean up event listeners
function _destroy() {
    gameManager.off('score_changed', this._onScoreChanged);
}

Test-Driven Development (MANDATORY)

JavaScript Testing

Write Tests FIRST:

// test/unit/test_player_health.js
const assert = require('assert');
const PlayerHealth = require('../../js/player/player_health.js');

describe('PlayerHealth', function() {
    let playerHealth;

    beforeEach(function() {
        playerHealth = new PlayerHealth(100);
    });

    it('should reduce health when taking damage', function() {
        playerHealth.takeDamage(30);
        assert.strictEqual(playerHealth.currentHealth, 70);
    });

    it('should not go below zero health', function() {
        playerHealth.takeDamage(120);
        assert.strictEqual(playerHealth.currentHealth, 0);
    });
});

Input Handling

WeChat Mini Game Input System

// Handle touch events
wx.onTouchStart(function(res) {
    // Handle touch start
});

wx.onTouchMove(function(res) {
    // Handle touch move
});

wx.onTouchEnd(function(res) {
    // Handle touch end
});

Scene Management

Scene Loading and Transitions

// SceneManager.js - Global manager
class SceneManager {
    constructor() {
        this.currentScene = null;
    }

    changeScene(path) {
        // Unload current scene
        // Load new scene
    }
}

Project Structure

WeChatMiniGameProject/
├── images/             # Image assets
├── js/                 # JavaScript scripts
│   ├── libs/           # Third-party libraries
│   ├── base/           # Base classes
│   ├── player/         # Player-related scripts
│   ├── enemies/        # Enemy scripts
│   ├── systems/        # Game systems
│   ├── ui/             # UI scripts
│   └── utils/          # Utility scripts
├── audio/              # Audio assets
├── game.js             # Game entry point
├── game.json           # Game configuration
├── project.config.json # Project configuration
└── project.private.config.json # Private project configuration

Development Workflow

TDD Story Implementation Process

  1. Read Story Requirements:

    • Understand acceptance criteria
    • Identify performance requirements (60+ FPS)
  2. Write Tests FIRST (Red Phase):

    • Write failing unit tests
    • Define expected behavior
    • Run tests to confirm they fail
  3. Implement Feature (Green Phase):

    • Write minimal code to pass tests
    • Follow WeChat Mini Game patterns and conventions
  4. Refactor (Refactor Phase):

    • Optimize for performance
    • Clean up code structure
    • Ensure 60+ FPS maintained
    • Run profiler to validate
  5. Integration Testing:

    • Test scene interactions
    • Validate performance targets
    • Test on all platforms
  6. Update Documentation:

    • Mark story checkboxes complete
    • Document performance metrics
    • Update File List

Performance Checklist

  • Stable 60+ FPS achieved
  • Object pooling for spawned entities
  • No memory leaks detected
  • Draw calls optimized
  • Appropriate process methods used
  • Events properly connected/disconnected
  • Tests written FIRST (TDD)
  • 80%+ test coverage

Performance Targets

Frame Rate Requirements

  • Mobile: 60 FPS on mid-range devices
  • Frame Time: <16.67ms consistently

Memory Management

  • Scene Memory: Keep under platform limits
  • Texture Memory: Optimize imports, use compression
  • Object Pooling: Required for bullets, particles, enemies
  • Reference Cleanup: Prevent memory leaks

Optimization Priorities

  1. Profile First: Use the WeChat Mini Game profiler to identify bottlenecks
  2. Optimize Algorithms: Better algorithms beat micro-optimizations
  3. Reduce Draw Calls: Batch rendering, use atlases

General Optimization

Anti-Patterns

  1. Security Holes

    • Unvalidated user input
    • Memory disclosure
  2. Platform Sabotage

    • Fighting the WeChat Mini Game's scene system
    • Reimplementing platform features
    • Ignoring hardware capabilities

JavaScript Optimization

Performance Destroyers

  1. Allocation Disasters

    • Creating Arrays/Objects in loops
    • String concatenation with +
    • Unnecessary object instantiation
    • Resource loading in game loop
    • Event connections without caching
  2. Process Method Abuse

    • Frame-by-frame checks for rare events
    • Unnecessary process enabling

JavaScript Death Sentences

// CRIME: String concatenation in loop
for (let i = 0; i < 1000; i++) {
    text += i;  // Dies. Use Array.join()
}

// CRIME: Creating objects in loop
for (const enemy of enemies) {
    let bullet = new Bullet();  // Dies. Object pool
}

// CRIME: Checking rare conditions every frame
function _process(delta) {
    if (playerDied) {  // Dies. Use events
        gameOver();
    }
}

// CRIME: Object creation without pooling
function spawnParticle() {
    let p = new Particle();  // Dies. Pool everything spawned
    this.addChild(p);
}

The Only Acceptable JavaScript Patterns

// GOOD: Cached object references
let scoreLabel = this.getChildByName('score');

// GOOD: Object pooling
let bulletPool = [];
function _ready() {
    for (let i = 0; i < 50; i++) {
        let bullet = new Bullet();
        bullet.visible = false;
        bulletPool.push(bullet);
    }
}

// GOOD: Efficient string building
function buildText(count) {
    let parts = [];
    for (let i = 0; i < count; i++) {
        parts.push(i);
    }
    return parts.join('');
}

// GOOD: Timer-based checks
function _ready() {
    setInterval(this._checkRareCondition.bind(this), 1000);
}

// GOOD: Batch operations
let updatesPending = [];
function queueUpdate(value) {
    updatesPending.push(value);
    if (updatesPending.length === 1) {
        setTimeout(this._processUpdates.bind(this), 0);
    }
}

function _processUpdates() {
    // Process all updates at once
    for (const value of updatesPending) {
        // Do work
    }
    updatesPending = [];
}

JavaScript-Specific Optimization Rules

  1. NEVER create objects without pooling - Pool or die
  2. NEVER concatenate strings in loops - Array.join()
  3. ALWAYS batch similar operations - One update, not many
  4. NEVER check conditions every frame - Use events and timers