447 lines
11 KiB
Markdown
447 lines
11 KiB
Markdown
# 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:**
|
|
|
|
```javascript
|
|
// 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:**
|
|
|
|
```javascript
|
|
// 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:**
|
|
|
|
```javascript
|
|
// 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)
|
|
|
|
```javascript
|
|
// 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:**
|
|
|
|
```javascript
|
|
// 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:**
|
|
|
|
```javascript
|
|
// Clean up event listeners
|
|
function _destroy() {
|
|
gameManager.off('score_changed', this._onScoreChanged);
|
|
}
|
|
```
|
|
|
|
## Test-Driven Development (MANDATORY)
|
|
|
|
### JavaScript Testing
|
|
|
|
**Write Tests FIRST:**
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
```
|