# 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() │ │ • isWechat() │ │ • init() │ │ │ │ • post() │ │ • 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 │ ├─ _imageCache: Map │ └─ _loadingLevels: Set (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