Files
mp-xieyingeng/GAME_ANALYSIS.md
2026-04-05 13:37:58 +08:00

13 KiB

Cocos Creator Game - Complete Point/Score System Analysis

Project Overview

This is a WeChat Mini-Game built with Cocos Creator. It's a word puzzle game where players guess answers to images within a 60-second time limit. The game uses a "lives" system instead of traditional points/coins.


1. LIVES/RESOURCE SYSTEM (The Currency/Points Equivalent)

Storage & Persistence

File: StorageManager.ts

  • Storage Key: game_lives
  • Default Lives: 10 (for new users)
  • Minimum Lives: 0
  • Storage Method: sys.localStorage (Cocos local storage)

Lives Management Methods:

// Core Methods:
- getLives(): number           // Get current lives (default 10 if not set)
- setLives(lives: number)      // Set lives value (min 0)
- consumeLife(): boolean       // Deduct 1 life, returns success status
- addLife(): void              // Add 1 life
- hasLives(): boolean          // Check if lives > 0
- resetLives(): void           // Reset to default 10

Lives Usage:

  1. Unlocking Hints (Clues): Each unlock costs 1 life
    • Clue 1: Free (always unlocked)
    • Clue 2: Costs 1 life to unlock
    • Clue 3: Costs 1 life to unlock

2. LEVEL PROGRESSION SYSTEM

File: StorageManager.ts (User Progress section)

Progress Data Structure:

interface UserProgress {
    currentLevelIndex: number;        // Current level (0-based)
    maxUnlockedLevelIndex: number;    // Highest level player reached
}

Progress Storage:

  • Storage Key: game_progress
  • Default: { currentLevelIndex: 0, maxUnlockedLevelIndex: 0 }
  • Caching: Cached in memory (_progressCache) to avoid repeated localStorage reads

Progression Methods:

- getCurrentLevelIndex(): number           // Get current level
- setCurrentLevelIndex(index): void        // Set current level
- getMaxUnlockedLevelIndex(): number       // Get highest unlocked level
- isLevelUnlocked(levelIndex): boolean    // Check if level is playable
- onLevelCompleted(completedLevelIndex)  // Called when level is beaten
- resetProgress(): void                    // Reset to level 1

Level Unlock Logic:

  • Level 1 is always unlocked
  • When player completes level N:
    • Current level → N+1
    • Max unlocked → max(maxUnlocked, N)
    • This allows replaying lower levels while progressing forward

3. GAME LEVEL DATA & API

File: LevelDataManager.ts

API Configuration:

API_URL = 'https://ilookai.cn/api/v1/wechat-game/levels'
REQUEST_TIMEOUT = 8000ms
API_RETRY_COUNT = 2 (retries on failure)

Level Data Structure (from API):

interface ApiLevelData {
    id: string;              // UUID
    level: number;           // Level number
    imageUrl: string;        // Main image URL
    hint1: string;           // Free clue
    hint2: string;           // Paid clue (costs 1 life)
    hint3: string;           // Paid clue (costs 1 life)
    answer: string;          // The correct answer
    sortOrder: number;       // Sorting order
}

API Response:

interface ApiResponse {
    success: boolean;
    message: string | null;
    data: {
        levels: ApiLevelData[];
        total: number;
    }
}

Image Loading:

  • Remote images loaded via assetManager.loadRemote()
  • Cached in memory (_imageCache: Map<URL, SpriteFrame>)
  • First level image preloaded during app initialization
  • Next level image preloaded silently after entering current level

Loading Strategy:

  1. On App Start: Load all level metadata + first level image (80% of loading bar)
  2. On Level Enter: Load current level image if needed
  3. After Level Completion: Preload next level asynchronously (doesn't block gameplay)

4. GAMEPLAY LOOP

File: PageLevel.ts

Game Sequence:

  1. Player enters level
  2. Main image displays with hint 1 visible
  3. 60-second countdown starts
  4. Player enters answer in single EditBox
  5. Player can unlock hints 2 & 3 by spending lives
  6. Player submits answer

Time Limit:

  • Duration: 60 seconds per level
  • Implementation: schedule(this.onCountdownTick, 1) (1-second interval)
  • On Time Up: Plays fail sound, but doesn't force level exit

Input System:

  • Type: Single EditBox (not multi-input per character)
  • Width: Dynamic (based on answer length)
    • Formula: Math.min(600, Math.max(200, answerLength * 60 + 40)) pixels
  • Max Length: Match answer length
  • Placeholder: Shows answer length as hint

Answer Processing:

getAnswer(): string {
    const editBox = this._inputNodes[0].getComponent(EditBox);
    return (editBox?.string ?? '').trim();  // Trimmed
}

// Comparison is case-sensitive:
if (userAnswer === this._currentConfig.answer) {
    // WIN
} else {
    // LOSE
}

5. WINNING & REWARDS

File: PageLevel.ts

On Correct Answer:

  1. Stop Timer: Countdown stops
  2. Play Sound: Success audio plays
  3. Award 1 Life: addLife() called
  4. Show Modal: PassModal displays with buttons

Pass Modal (Victory Screen):

  • Shows "Next Level" button
  • Shows "Share with Friends" button
  • On "Next Level": Progress to next level (calls onLevelCompleted())
  • On "Share": Triggers WeChat share with query param ?level=<levelIndex>

Progression on Pass:

StorageManager.onLevelCompleted(currentLevelIndex);
// Sets:
// - currentLevelIndex → currentLevelIndex + 1
// - maxUnlockedLevelIndex → max(maxUnlocked, currentLevelIndex)

Game End Condition:

  • When currentLevelIndex >= totalLevels, player has beaten all levels
  • Game returns to home page

6. LOSING & CONSEQUENCES

File: PageLevel.ts

On Wrong Answer:

  1. Play Sound: Fail audio plays
  2. Vibration: WxSDK.vibrateLong() (400ms vibration on WeChat)
  3. Toast Message: "答案错误,再试试吧!" (Answer wrong, try again!)
  4. No Penalty: No life deducted, level doesn't change

On Time Up:

  1. Play Sound: Fail audio plays
  2. Countdown Stops: _isTimeUp = true
  3. No Forced Exit: Player can continue typing and submitting
  4. No Life Penalty: Still can retry

7. HINT/CLUE SYSTEM

File: PageLevel.ts

Clue Mechanics:

  1. Clue 1: Always visible, FREE
  2. Clue 2: Hidden by default, costs 1 life to unlock
  3. Clue 3: Hidden by default, costs 1 life to unlock

Unlocking Process:

onUnlockClue(index: number) {
    // 1. Check if lives available
    if (!this.hasLives()) return;
    
    // 2. Consume 1 life
    if (!this.consumeLife()) return;
    
    // 3. Play click sound
    this.playClickSound();
    
    // 4. Hide unlock button
    this.hideUnlockButton(index);
    
    // 5. Show clue content
    this.showClue(index);
    this.setClue(index, clueContent);
}

Clue Cost Implications:

  • Player starts with 10 lives
  • Can unlock both clues 2 & 3 = 2 lives spent minimum
  • But only 8 lives remain per level if both used
  • Player can conserve lives by solving without clues

8. NETWORK & API COMMUNICATION

File: HttpUtil.ts

HTTP Methods:

// GET request
HttpUtil.get<T>(url: string, timeout: number = 10000): Promise<T>

// POST request
HttpUtil.post<T>(url: string, data: object, timeout: number = 10000): Promise<T>

Implementation:

  • Uses XMLHttpRequest
  • Supports JSON responses
  • Default timeout: 10 seconds
  • Error handling: Rejects on HTTP errors, timeouts, or network failures

Used By:

  • LevelDataManager uses HttpUtil.get() to fetch level data from API
  • No POST requests currently used

9. WECHAT SDK INTEGRATION

File: WxSDK.ts

WeChat Features Used:

1. Platform Detection:

isWechat(): boolean
// Returns: sys.platform === sys.Platform.WECHAT_GAME

2. Sharing:

  • Share Menu: showShareMenu() - Enables share button in header
  • Friend Share: onShareAppMessage(config) - Right-click "Share" message
  • Timeline Share: onShareTimeline(config) - Moments sharing
  • Active Share: shareAppMessage(config) - Trigger share dialog

3. Vibration:

  • Short Vibrate: vibrateShort() - 15ms, for button clicks
  • Long Vibrate: vibrateLong() - 400ms, for errors

4. Share Configuration:

interface WxShareConfig {
    title: string;           // Share title: "写英语"
    imageUrl?: string;       // Share image (optional)
    query?: string;          // Query params (e.g., "level=5")
}

5. Initialization:

WxSDK.initShare(config) {
    // Calls in sequence:
    // 1. showShareMenu()
    // 2. onShareAppMessage(config)
    // 3. onShareTimeline(config)
}

Called in: PageHome on game start


10. GAME STATE MANAGEMENT

File: ViewManager.ts (Page Stack) + StorageManager.ts (Data)

View Stack (Navigation):

  • Maintains page stack for navigation
  • PageHome (z-index 0) - Main menu
  • PageLevel (z-index 1) - Game level
  • PassModal (z-index 999) - Victory overlay

Persistent State:

  • Lives stored in localStorage with key game_lives
  • Progress stored in localStorage with key game_progress
  • Both persist across app sessions
  • Data survives app closure and reopening

Runtime State:

  • Current countdown timer
  • Current input box content
  • Unlocked clues state (reset each level)
  • Current level config (API data)

11. LOADING PAGE FLOW

File: PageLoading.ts

Initialization Sequence:

  1. Stage 1 (0-30%): Fetch all levels from API via LevelDataManager.initialize()

    • API call with retry logic
    • Parse level metadata
    • NOT loading all images yet
  2. Stage 2 (30-80%): Preload first level image

    • Uses LevelDataManager.ensureLevelReady(0)
    • Shows "正在加载游戏必备资源..." message
  3. Stage 3 (80-100%): Preload PageHome view

    • ViewManager.preload('PageHome')
    • Shows "正在加载界面资源..." message
  4. Completion (100%): Open PageHome and destroy loading page


12. COMPLETE POINTS FLOW DIAGRAM

START GAME
    ↓
[10 Lives] (default)
    ↓
LEVEL 1 STARTS
    ├─ View Clue 1 (FREE)
    ├─ Option: Unlock Clue 2 (-1 Life) → [9 Lives]
    ├─ Option: Unlock Clue 3 (-1 Life) → [8 Lives]
    ├─ Player submits answer
    │
    ├─ IF CORRECT:
    │  ├─ Add 1 Life → [9 or 10+ Lives]
    │  ├─ Show PassModal
    │  └─ Move to LEVEL 2
    │
    └─ IF WRONG:
       ├─ Play fail sound & vibrate
       ├─ Show toast message
       ├─ Lives unchanged
       └─ Can retry (no level exit)

IF ALL LEVELS COMPLETE:
    └─ Return to home

IF TIME UP:
    ├─ Play fail sound
    ├─ Can still submit (lives unchanged)
    └─ No forced exit

13. KEY FILES SUMMARY

File Purpose Key Components
StorageManager.ts Data persistence Lives + Progress storage
LevelDataManager.ts Level data loading API calls + Image caching
PageLevel.ts Main game logic Countdown, input, hints, validation
PageLoading.ts App initialization Loading bar + progress
PageHome.ts Home screen Start game button
PassModal.ts Victory screen Next/Share buttons
ViewManager.ts Page navigation View stack + caching
WxSDK.ts WeChat API Share + vibration
HttpUtil.ts Network requests GET/POST + error handling
ToastManager.ts Notifications Brief toast messages

14. IMPORTANT CONSTANTS

Game Constants:

  • Level Time Limit: 60 seconds
  • Default Lives: 10
  • Life Cost per Hint: 1 life per hint (hints 2 & 3)
  • Reward per Level: +1 life

API Constants:

  • Endpoint: https://ilookai.cn/api/v1/wechat-game/levels
  • Timeout: 8000ms
  • Retry Count: 2

UI Constants:

  • PageHome z-index: 0
  • PageLevel z-index: 1
  • PassModal z-index: 999

15. MISSING FEATURES (Observations)

  1. No User Authentication: No wx.login call visible
  2. No Backend Sync: No calls to save progress to server
  3. No Ads/IAP: No monetization system
  4. No Leaderboards: No score submission to WeChat
  5. No Analytics: No tracking events beyond console logs
  6. No Life Refill: No premium way to get more lives
  7. No Difficulty Levels: All players see same levels
  8. No Sound Toggle: Sound plays automatically

16. DATA FLOW SUMMARY

WeChat API: https://ilookai.cn/api/v1/wechat-game/levels
    ↓
LevelDataManager (fetch + cache)
    ↓
PageLevel (display + gameplay)
    ├─ InputBox (player answer)
    ├─ Clues (cost lives to unlock)
    └─ Timer (60 second countdown)
    ↓
StorageManager (save lives + progress)
    ↓
localStorage
    ├─ game_lives: number
    └─ game_progress: UserProgress (JSON)

17. CRITICAL BUSINESS LOGIC

Win Condition:

userAnswer (trimmed) === correctAnswer (from API)
→ Award +1 life
→ Save progress
→ Move to next level

Lose Condition:

userAnswer !== correctAnswer
→ No penalty
→ Can retry immediately
→ Timer continues (even after time up)

Progression:

Beat Level N
→ currentLevel = N + 1
→ maxUnlocked = max(maxUnlocked, N)
→ Reward: +1 life (so levels can chain profitably)

Economy Balance:

  • Start: 10 lives
  • Per level: Can spend 0-2 lives (hints) or 0 lives (no hints)
  • Per level: Earn +1 life (net: -1 or +1 lives)
  • Average player with no hints: +1 life/level → infinite scaling
  • Average player with 1 hint: 0 lives/level → stable
  • Hardcore with 2 hints: -1 life/level → finite runway