497 lines
26 KiB
Markdown
497 lines
26 KiB
Markdown
# Architecture Overview
|
|
|
|
## System Architecture Diagram
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ COCOS CREATOR GAME │
|
|
│ WeChat Mini-Game Version 3.8.8 │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ PRESENTATION LAYER (UI) │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ PageLoading.ts ──────┐ │
|
|
│ (Loading Screen) │ │
|
|
│ │ │
|
|
│ ┌──────────┐ │ ┌──────────┐ ┌──────────┐ │
|
|
│ │PageHome │─────────┴──────│PageLevel │──────│PassModal │ │
|
|
│ │(Menu) │ │ (Play) │ │(Victory) │ │
|
|
│ └──────────┘ └──────────┘ └──────────┘ │
|
|
│ ▲ ▲ │ │
|
|
│ │ │ │ │
|
|
│ └────────────────┬───────────┘ │ │
|
|
│ │ (Back button) │ │
|
|
│ └─────────────────────────────┘ │
|
|
│ │
|
|
│ Toast.ts ────────────────────────────────────────────────────────────│
|
|
│ (Notifications) │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ CORE LOGIC LAYER │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────────────┐ ┌──────────────────────┐ │
|
|
│ │ PageLevel.ts │ │ PassModal.ts │ │
|
|
│ ├──────────────────────┤ ├──────────────────────┤ │
|
|
│ │ • Answer validation │ │ • Next level │ │
|
|
│ │ • Hint unlock (-life)│ │ • Share button │ │
|
|
│ │ • Countdown (60s) │ │ • Callbacks │ │
|
|
│ │ • Sound/Vibration │ │ │ │
|
|
│ │ • Life display │ │ │ │
|
|
│ └──────────────────────┘ └──────────────────────┘ │
|
|
│ ▲ ▲ │
|
|
│ │ │ │
|
|
│ └────────────────┬───────────────┘ │
|
|
│ │ │
|
|
│ ┌──────────────────────────┴──────────────────────────────────┐ │
|
|
│ │ ViewManager.ts (View Navigation) │ │
|
|
│ ├──────────────────────────────────────────────────────────────┤ │
|
|
│ │ • Page registration & caching │ │
|
|
│ │ • Page stack management (push/pop/replace) │ │
|
|
│ │ • Lifecycle: onViewLoad → onViewShow → onViewHide → destroy │ │
|
|
│ │ • Parameter passing between pages │ │
|
|
│ └──────────────────────────────────────────────────────────────┘ │
|
|
│ ▲ │
|
|
│ │ │
|
|
│ BaseView.ts (Abstract base class for all pages) │
|
|
│ • Lifecycle hooks │
|
|
│ • Page state management │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ DATA & STATE MANAGEMENT LAYER │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────────┐ │
|
|
│ │ StorageManager.ts (Single Source of Truth) │ │
|
|
│ ├──────────────────────────────────────────────────────────────┤ │
|
|
│ │ Lives Management: │ │
|
|
│ │ • getLives() / setLives() / consumeLife() / addLife() │ │
|
|
│ │ │ │
|
|
│ │ Progress Management: │ │
|
|
│ │ • getCurrentLevelIndex() / getMaxUnlockedLevelIndex() │ │
|
|
│ │ • onLevelCompleted(levelIndex) │ │
|
|
│ │ • isLevelUnlocked(levelIndex) │ │
|
|
│ │ │ │
|
|
│ │ Storage Backend: sys.localStorage │ │
|
|
│ │ └─ game_lives: string number (default: "10") │ │
|
|
│ │ └─ game_progress: JSON with currentIndex & maxUnlockedIdx │ │
|
|
│ └──────────────────────────────────────────────────────────────┘ │
|
|
│ ▲ │
|
|
│ │ │
|
|
│ ┌──────────────────────────────────────────────────────────────┐ │
|
|
│ │ LevelDataManager.ts (Level Data & Assets) │ │
|
|
│ ├──────────────────────────────────────────────────────────────┤ │
|
|
│ │ • initialize(onProgress) - Fetch API & load first level │ │
|
|
│ │ • getLevelConfig(index) - Get level data + image │ │
|
|
│ │ • ensureLevelReady(index) - On-demand level loading │ │
|
|
│ │ • preloadNextLevel(currentIndex) - Async preload │ │
|
|
│ │ │ │
|
|
│ │ Memory Caches: │ │
|
|
│ │ • _apiData: All levels from server │ │
|
|
│ │ • _levelConfigs: Map of loaded level configs │ │
|
|
│ │ • _imageCache: Map of loaded images (SpriteFrames) │ │
|
|
│ │ • _loadingLevels: Set of levels being loaded │ │
|
|
│ └──────────────────────────────────────────────────────────────┘ │
|
|
│ ▲ │
|
|
│ │ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ UTILITY & SDK LAYER │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌────────────────────┐ ┌────────────────────┐ ┌──────────────────┐ │
|
|
│ │ HttpUtil.ts │ │ WxSDK.ts │ │ToastManager.ts │ │
|
|
│ ├────────────────────┤ ├────────────────────┤ ├──────────────────┤ │
|
|
│ │ • get<T>() │ │ • isWechat() │ │ • init() │ │
|
|
│ │ • post<T>() │ │ • initShare() │ │ • show() │ │
|
|
│ │ │ │ • shareAppMessage()│ │ │ │
|
|
│ │ Timeout: 10s │ │ • onShareAppMsg() │ │ Display duration │ │
|
|
│ │ Error handling │ │ • vibrateShort() │ │ Fade out anim │ │
|
|
│ │ │ │ • vibrateLong() │ │ │ │
|
|
│ └────────────────────┘ └────────────────────┘ └──────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ EXTERNAL SYSTEMS │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────────────────────────────┐ │
|
|
│ │ WeChat Mini-Game SDK (wx global) │ │
|
|
│ ├──────────────────────────────────────┤ │
|
|
│ │ • wx.shareAppMessage() │ │
|
|
│ │ • wx.onShareAppMessage() │ │
|
|
│ │ • wx.showShareMenu() │ │
|
|
│ │ • wx.vibrateShort() │ │
|
|
│ │ • wx.vibrateLong() │ │
|
|
│ └──────────────────────────────────────┘ │
|
|
│ ▲ │
|
|
│ │ │
|
|
│ │ │
|
|
│ ┌──────────────────────────────────────┐ │
|
|
│ │ Backend API Server │ │
|
|
│ │ https://ilookai.cn │ │
|
|
│ ├──────────────────────────────────────┤ │
|
|
│ │ GET /api/v1/wechat-game/levels │ │
|
|
│ │ • Returns: {success, data, message} │ │
|
|
│ │ • Retry: 2x with 1s delay │ │
|
|
│ │ • Timeout: 8s │ │
|
|
│ └──────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Data Flow Diagram: Complete User Session
|
|
|
|
```
|
|
START
|
|
│
|
|
├─ [main.ts] onLoad()
|
|
│ └─ ViewManager.init(canvas)
|
|
│ └─ ToastManager.init(toastPrefab)
|
|
│ └─ Register PageHome & PageLevel
|
|
│
|
|
├─ [PageLoading.ts] start()
|
|
│ ├─ LevelDataManager.initialize()
|
|
│ │ ├─ HttpUtil.get() → API call
|
|
│ │ │ └─ https://ilookai.cn/api/v1/wechat-game/levels
|
|
│ │ ├─ Cache response → _apiData
|
|
│ │ ├─ Load first level image
|
|
│ │ │ └─ assetManager.loadRemote() → SpriteFrame
|
|
│ │ │ └─ Cache in _imageCache
|
|
│ │ └─ Return success
|
|
│ │
|
|
│ └─ ViewManager.preload('PageHome')
|
|
│ └─ ViewManager.open('PageHome')
|
|
│ └─ PageHome.onViewLoad()
|
|
│ └─ WxSDK.initShare()
|
|
│
|
|
├─ [USER CLICKS "START GAME"]
|
|
│
|
|
├─ ViewManager.open('PageLevel', {params: {levelIndex: 0}})
|
|
│ └─ PageLevel.onViewLoad()
|
|
│ ├─ StorageManager.getCurrentLevelIndex()
|
|
│ ├─ PageLevel.initLevel()
|
|
│ │ ├─ LevelDataManager.ensureLevelReady(levelIndex)
|
|
│ │ │ ├─ Check cache (_levelConfigs)
|
|
│ │ │ ├─ If not cached: load image + create config
|
|
│ │ │ └─ Store in cache
|
|
│ │ │
|
|
│ │ └─ PageLevel._applyLevelConfig()
|
|
│ │ ├─ Display main image (sprite)
|
|
│ │ ├─ Show Hint 1 (free)
|
|
│ │ ├─ Hide Hints 2, 3 (show unlock buttons)
|
|
│ │ ├─ Create input field based on answer.length
|
|
│ │ ├─ Display lives: StorageManager.getLives()
|
|
│ │ ├─ Start countdown (60s)
|
|
│ │ └─ LevelDataManager.preloadNextLevel() [async]
|
|
│
|
|
├─ [USER INTERACTS]
|
|
│
|
|
├─ Option A: UNLOCK HINT
|
|
│ ├─ PageLevel.onUnlockClue(2 or 3)
|
|
│ ├─ Check: StorageManager.hasLives()?
|
|
│ ├─ Yes → StorageManager.consumeLife() → display hint
|
|
│ └─ No → Show "生命值不足" message
|
|
│
|
|
├─ Option B: SUBMIT ANSWER
|
|
│ ├─ PageLevel.onSubmitAnswer()
|
|
│ ├─ Validate: userAnswer === correctAnswer?
|
|
│ │
|
|
│ ├─ YES (Correct)
|
|
│ │ ├─ PageLevel.showSuccess()
|
|
│ │ ├─ Stop countdown
|
|
│ │ ├─ Play successAudio
|
|
│ │ ├─ StorageManager.addLife() [+1]
|
|
│ │ ├─ PageLevel._showPassModal()
|
|
│ │ │ ├─ Instantiate PassModal prefab
|
|
│ │ │ ├─ PassModal.onViewLoad()
|
|
│ │ │ ├─ PassModal.onViewShow()
|
|
│ │ │ │ └─ Play success sound
|
|
│ │ │ │ └─ Adjust widget to full screen
|
|
│ │ │ │
|
|
│ │ │ └─ User clicks button:
|
|
│ │ │ ├─ "Next Level" → PageLevel.nextLevel()
|
|
│ │ │ │ ├─ StorageManager.onLevelCompleted(currentIndex)
|
|
│ │ │ │ │ ├─ currentLevelIndex++
|
|
│ │ │ │ │ ├─ maxUnlockedLevelIndex = max(prev, current)
|
|
│ │ │ │ │ └─ Save to localStorage
|
|
│ │ │ │ ├─ Reload PageLevel with new index
|
|
│ │ │ │ │ └─ Repeat from Level Setup
|
|
│ │ │ │ │
|
|
│ │ │ │ └─ If last level:
|
|
│ │ │ │ └─ ViewManager.back() → return to PageHome
|
|
│ │ │ │
|
|
│ │ │ └─ "Share" → WxSDK.shareAppMessage()
|
|
│ │ │ └─ query: `level=${currentIndex+1}`
|
|
│ │
|
|
│ ├─ NO (Wrong)
|
|
│ │ ├─ PageLevel.showError()
|
|
│ │ ├─ Play failAudio
|
|
│ │ ├─ WxSDK.vibrateLong() [device vibration]
|
|
│ │ ├─ ToastManager.show("答案错误,再试试吧!")
|
|
│ │ │ ├─ Create Toast node
|
|
│ │ │ ├─ Show for 2000ms
|
|
│ │ │ ├─ Fade out animation (300ms)
|
|
│ │ │ └─ Destroy node
|
|
│ │ │
|
|
│ │ └─ Continue playing (allow retry)
|
|
│
|
|
├─ Option C: TIMEOUT
|
|
│ └─ Countdown reaches 0
|
|
│ ├─ PageLevel.onTimeUp()
|
|
│ ├─ Play failAudio
|
|
│ └─ [Incomplete: can still submit after timeout]
|
|
│
|
|
├─ [USER CLICKS BACK BUTTON]
|
|
│ ├─ ViewManager.back()
|
|
│ │ ├─ PageLevel._doHide()
|
|
│ │ └─ PageHome._doShow()
|
|
│ │
|
|
│ └─ Return to home (progress saved in localStorage)
|
|
│
|
|
└─ END
|
|
```
|
|
|
|
---
|
|
|
|
## State Management Flow
|
|
|
|
```
|
|
USER PROGRESS STATE
|
|
│
|
|
├─ localStorage: game_progress
|
|
│ └─ {
|
|
│ "currentLevelIndex": 5,
|
|
│ "maxUnlockedLevelIndex": 5
|
|
│ }
|
|
│
|
|
├─ Memory Cache (LevelDataManager)
|
|
│ ├─ _apiData: ApiLevelData[] (all levels)
|
|
│ ├─ _levelConfigs: Map<levelIndex, RuntimeLevelConfig>
|
|
│ ├─ _imageCache: Map<url, SpriteFrame>
|
|
│ └─ _loadingLevels: Set<levelIndex> (currently loading)
|
|
│
|
|
└─ Session State (PageLevel component)
|
|
├─ currentLevelIndex: number
|
|
├─ _currentConfig: RuntimeLevelConfig
|
|
├─ _countdown: number (60 → 0)
|
|
├─ _isTimeUp: boolean
|
|
├─ _isTransitioning: boolean
|
|
└─ _passModalNode: Node | null
|
|
|
|
|
|
LIVES STATE
|
|
│
|
|
├─ localStorage: game_lives
|
|
│ └─ "10" (string representation of number)
|
|
│
|
|
├─ Cached value (StorageManager._progressCache)
|
|
│ └─ Read on first access, cached for performance
|
|
│
|
|
└─ Operations (immediate storage update)
|
|
├─ getLives() → read from storage
|
|
├─ setLives(n) → validate & write to storage
|
|
├─ consumeLife() → getLives() - 1, setLives()
|
|
├─ addLife() → getLives() + 1, setLives()
|
|
└─ hasLives() → getLives() > 0
|
|
|
|
|
|
API CACHE
|
|
│
|
|
├─ First Load (Startup)
|
|
│ ├─ HttpUtil.get(apiUrl, 8000ms timeout)
|
|
│ ├─ Retry: 2 times (1s delay between)
|
|
│ └─ Cache in LevelDataManager._apiData
|
|
│
|
|
└─ Per-Level Resources (On-Demand)
|
|
├─ Check _levelConfigs cache
|
|
├─ If missing: assetManager.loadRemote(imageUrl)
|
|
├─ Create SpriteFrame
|
|
└─ Cache in _imageCache
|
|
```
|
|
|
|
---
|
|
|
|
## View Stack & Navigation
|
|
|
|
```
|
|
VIEW STACK (LIFO - Last In First Out)
|
|
│
|
|
├─ Level 0 (Bottom)
|
|
│ └─ PageHome
|
|
│
|
|
├─ Level 1 (Middle)
|
|
│ └─ PageLevel
|
|
│
|
|
├─ Level 2 (Top - Current)
|
|
│ └─ PassModal (temporary, on PageLevel)
|
|
│
|
|
└─ Operations
|
|
├─ open(viewId) → push to stack + show
|
|
├─ back() → pop from stack + hide + show prev
|
|
├─ replace(viewId) → pop + push new view
|
|
└─ close() → pop + show previous
|
|
|
|
|
|
CACHING BEHAVIOR
|
|
│
|
|
├─ PageHome
|
|
│ ├─ cache: true
|
|
│ └─ Cached after first open, reused on back
|
|
│
|
|
├─ PageLevel
|
|
│ ├─ cache: true
|
|
│ └─ Cached per instance (but reinitializes on each open)
|
|
│
|
|
└─ PassModal
|
|
├─ Dynamically instantiated
|
|
├─ Not cached
|
|
└─ Destroyed after close
|
|
```
|
|
|
|
---
|
|
|
|
## Class Hierarchy
|
|
|
|
```
|
|
Component (Cocos)
|
|
│
|
|
├─ BaseView (Abstract base)
|
|
│ ├─ PageHome
|
|
│ ├─ PageLevel
|
|
│ └─ PassModal
|
|
│
|
|
└─ Toast
|
|
|
|
|
|
Managers (Singleton)
|
|
│
|
|
├─ ViewManager
|
|
│ └─ Manages page lifecycle & navigation
|
|
│
|
|
├─ LevelDataManager
|
|
│ └─ Manages API data & asset loading
|
|
│
|
|
├─ StorageManager
|
|
│ └─ Manages user data persistence
|
|
│
|
|
├─ ToastManager
|
|
│ └─ Manages toast notifications
|
|
│
|
|
└─ WxSDK
|
|
└─ Manages WeChat integration
|
|
|
|
|
|
Utilities
|
|
│
|
|
├─ HttpUtil
|
|
│ └─ Static HTTP methods (GET/POST)
|
|
│
|
|
└─ LevelTypes
|
|
└─ TypeScript interfaces for API data
|
|
```
|
|
|
|
---
|
|
|
|
## Dependency Graph
|
|
|
|
```
|
|
main.ts
|
|
├─ ViewManager
|
|
├─ ToastManager
|
|
└─ [Page Prefabs]
|
|
├─ PageHome
|
|
│ └─ WxSDK
|
|
│
|
|
├─ PageLevel
|
|
│ ├─ StorageManager
|
|
│ ├─ WxSDK
|
|
│ ├─ LevelDataManager
|
|
│ ├─ ToastManager
|
|
│ └─ PassModal
|
|
│ ├─ WxSDK
|
|
│ └─ BaseView
|
|
│
|
|
└─ PageLoading
|
|
├─ ViewManager
|
|
├─ LevelDataManager
|
|
│ ├─ HttpUtil
|
|
│ │ └─ XMLHttpRequest
|
|
│ ├─ LevelTypes
|
|
│ └─ Cocos assetManager
|
|
└─ ToastManager
|
|
|
|
|
|
EXTERNAL APIS
|
|
│
|
|
├─ https://ilookai.cn/api/v1/wechat-game/levels
|
|
│ └─ Called by LevelDataManager._fetchApiData()
|
|
│
|
|
└─ WeChat SDK (wx global)
|
|
├─ wx.shareAppMessage()
|
|
├─ wx.onShareAppMessage()
|
|
├─ wx.showShareMenu()
|
|
├─ wx.vibrateShort()
|
|
└─ wx.vibrateLong()
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Considerations
|
|
|
|
### Memory Management
|
|
- **Image Loading**: Remote images loaded on-demand with local caching
|
|
- **Level Configs**: Loaded incrementally, current + next level only
|
|
- **View Caching**: Pages cached after first load, reused on navigation back
|
|
- **Message Queuing**: No event queue; direct method calls
|
|
|
|
### Network
|
|
- **Single API Call**: Startup only (all levels fetched at once)
|
|
- **Timeout**: 8 seconds for API requests
|
|
- **Retry Logic**: 2 attempts with 1-second delay
|
|
- **Cache Strategy**: In-memory cache, no expiration
|
|
|
|
### Storage
|
|
- **localStorage**: 2 keys (lives + progress)
|
|
- **No Batching**: Each update writes immediately
|
|
- **Synchronous**: No async operations needed
|
|
|
|
### UI/Graphics
|
|
- **Single Image**: Main puzzle image per level
|
|
- **Dynamic Input**: Input field size adjusts based on answer length
|
|
- **Audio**: One-shot playback, no loops
|
|
- **Animations**: Toast fade-out only
|
|
|
|
---
|
|
|
|
## Security Considerations
|
|
|
|
### Current Implementation
|
|
- ⚠️ All data stored locally (no encryption)
|
|
- ⚠️ No user authentication
|
|
- ⚠️ No server-side validation
|
|
- ⚠️ Progress can be manually edited via localStorage
|
|
|
|
### Vulnerabilities
|
|
1. localStorage can be inspected/modified via browser console
|
|
2. No server-side checks on progress/lives
|
|
3. Can modify localStorage to skip levels
|
|
4. Can modify lives directly
|
|
|
|
### Improvements Needed
|
|
- Server-side progress validation
|
|
- User authentication
|
|
- Encrypted storage
|
|
- Server-side truth for lives/progress
|
|
|