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

26 KiB

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