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:
GameStateManagernotGSM
Functions and Methods:
- camelCase for functions:
calculateDamage(),processInput() - Descriptive verb phrases:
activateShield()notshield() - 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
-
Read Story Requirements:
- Understand acceptance criteria
- Identify performance requirements (60+ FPS)
-
Write Tests FIRST (Red Phase):
- Write failing unit tests
- Define expected behavior
- Run tests to confirm they fail
-
Implement Feature (Green Phase):
- Write minimal code to pass tests
- Follow WeChat Mini Game patterns and conventions
-
Refactor (Refactor Phase):
- Optimize for performance
- Clean up code structure
- Ensure 60+ FPS maintained
- Run profiler to validate
-
Integration Testing:
- Test scene interactions
- Validate performance targets
- Test on all platforms
-
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
- Profile First: Use the WeChat Mini Game profiler to identify bottlenecks
- Optimize Algorithms: Better algorithms beat micro-optimizations
- Reduce Draw Calls: Batch rendering, use atlases
General Optimization
Anti-Patterns
-
Security Holes
- Unvalidated user input
- Memory disclosure
-
Platform Sabotage
- Fighting the WeChat Mini Game's scene system
- Reimplementing platform features
- Ignoring hardware capabilities
JavaScript Optimization
Performance Destroyers
-
Allocation Disasters
- Creating Arrays/Objects in loops
- String concatenation with +
- Unnecessary object instantiation
- Resource loading in game loop
- Event connections without caching
-
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
- NEVER create objects without pooling - Pool or die
- NEVER concatenate strings in loops - Array.join()
- ALWAYS batch similar operations - One update, not many
- NEVER check conditions every frame - Use events and timers