505 lines
13 KiB
Markdown
505 lines
13 KiB
Markdown
# 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:
|
|
```typescript
|
|
// 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:
|
|
```typescript
|
|
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:
|
|
```typescript
|
|
- 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:
|
|
```typescript
|
|
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):
|
|
```typescript
|
|
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:
|
|
```typescript
|
|
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:
|
|
```typescript
|
|
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:
|
|
```typescript
|
|
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:
|
|
```typescript
|
|
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:
|
|
```typescript
|
|
// 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:
|
|
```typescript
|
|
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:
|
|
```typescript
|
|
interface WxShareConfig {
|
|
title: string; // Share title: "写英语"
|
|
imageUrl?: string; // Share image (optional)
|
|
query?: string; // Query params (e.g., "level=5")
|
|
}
|
|
```
|
|
|
|
#### 5. Initialization:
|
|
```typescript
|
|
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
|
|
|