Files
mp-xieyingeng/UI_COMPONENT_ANALYSIS.md
richarjiang 69c0986996 feat: 进入关卡时 toast 提示体力消耗,修复 StorageManager 接口位置和 WxSDK 访问级别
- 进入关卡成功后显示 toast 提示消耗体力及剩余体力
- 将 StorageManager 中 UserInfo 接口移至模块顶层,修复嵌套接口语法问题
- WxSDK.getWx() 改为 static 公开方法,便于外部调用
2026-04-10 10:10:19 +08:00

1425 lines
45 KiB
Markdown

# Cocos Creator Project - UI Component Analysis
This document provides a comprehensive analysis of the UI component files in the "写英语" (Write English) Cocos Creator game project.
---
## 1. **main.ts** - Main Entry Script
**Location:** `assets/main.ts`
**Class:** `main extends Component`
### Purpose
- Main initialization script that bootstraps the entire application
- Responsible for initializing the ViewManager singleton
- Registers all page prefabs with their configurations
- Initializes the ToastManager for notifications
### Lifecycle Methods
- **onLoad()** - Executes before `start()` to ensure ViewManager is initialized early
- Calls `_initViewManager()` to set up the view system
### Key Methods
- **_initViewManager()** - Core initialization logic:
- Initializes `ViewManager` with the canvas node as container
- Registers 5 pages with configurations (cache and z-index):
- **PageHome** (cache: true, zIndex: 0) - Landing page
- **PageLevel** (cache: true, zIndex: 1) - Game level page
- **PageWriteLevels** (cache: true, zIndex: 2) - Create challenge levels
- **PagePreviewLevels** (cache: true, zIndex: 3) - Preview selected levels
- **PagePKData** (cache: true, zIndex: 3) - Challenge data page
- Initializes `ToastManager` with toast prefab and canvas reference
### UI Properties (Serialized in Editor)
```
@property pageHomePrefab: Prefab
@property pageLevelPrefab: Prefab
@property pageWriteLevelsPrefab: Prefab
@property pagePreviewLevelsPrefab: Prefab
@property pagePKDataPrefab: Prefab
@property toastPrefab: Prefab
```
### Data Managers Used
- ViewManager (initialization)
- ToastManager (initialization)
### Game Flow
Entry point → Initializes ViewManager → Registers all pages → PageLoading is likely instantiated separately to show splash/loading screen
---
## 2. **PageLoading.ts** - Loading/Splash Screen
**Location:** `assets/PageLoading.ts`
**Class:** `PageLoading extends Component`
### Purpose
- Handles application initialization and resource loading
- Manages user authentication and level data preloading
- Displays loading progress (0-100%)
- Handles WeChat share code detection for shared challenges
- Syncs user progress from server with local storage
### Lifecycle Methods
- **start()** - Called when scene starts
- Immediately calls `_startPreload()`
### Key Methods
**_startPreload() - Main initialization pipeline:**
1. Resets progress bar to 0%
2. Runs login and level loading **in parallel** using `Promise.all()`:
- `AuthManager.instance.initialize()` - User authentication
- `LevelDataManager.instance.initialize()` - Level data loading (provides progress updates)
- Level loading occupies 0-80% of progress bar
3. Logs login success/failure
4. Checks for login success:
- If login successful → calls `_syncProgressFromServer()`
- Maps server `completedLevelIds` to local `currentLevelIndex`
- Takes max of (server progress, local progress) to prevent regression
5. Detects WeChat share code:
- Calls `WxSDK.getShareCodeFromLaunch()` to extract share code from launch parameters
- If share code exists AND login successful → calls `ShareManager.instance.joinShare(shareCode)`
- If join succeeds → directly opens **PageLevel** with `shareMode: true`
- Destroys loading page after PageLevel opens
6. Normal flow (no share code):
- Preloads **PageHome** (80-100% of progress)
- Opens PageHome and destroys itself
**_syncProgressFromServer() - Server progress sync:**
- Gets `completedLevelIds` from server
- Finds max completed index: `LevelDataManager.getMaxCompletedIndex(completedIds)`
- Compares with local max: `StorageManager.getMaxUnlockedLevelIndex()`
- If server > local → calls `StorageManager.onLevelCompleted(maxCompletedIndex)`
- Ensures user never loses progress across devices
**_updateProgress(progress: number)** - Updates progress bar (0-1 range)
**_updateStatusLabel(message: string)** - Updates status text display
### UI Components
```
@property progressBar: ProgressBar
@property statusLabel: Label
```
### Data Managers Used
- **AuthManager** - User authentication (login, completedLevelIds)
- **LevelDataManager** - Level loading with progress callbacks
- **StorageManager** - Local progress storage
- **ShareManager** - Handle shared challenge joins
- **WxSDK** - WeChat integration (launch params, share code detection)
- **ViewManager** - Open pages
### Flow Control
- Login + Level loading (parallel) → Server sync → Share code check → Page navigation → Self-destroy
### Key States
- Progress tracking (0.0 to 1.0)
- Login success/failure handling
- Share code detection and join flow
- Normal vs. share challenge modes
---
## 3. **PageHome.ts** - Home/Landing Page
**Location:** `assets/prefabs/PageHome.ts`
**Class:** `PageHome extends BaseView`
### Purpose
- Main landing page displayed after loading complete
- Provides navigation to:
- Game levels (single-player)
- PK/Challenge creation (multiplayer)
- Handles WeChat SDK initialization and privacy authorization
### Lifecycle Methods
- **onViewLoad()** - First-time initialization:
- Calls `_initButtons()`
- Calls `_initWxShare()`
- Calls `_checkPrivacyAuthorization()`
- **onViewShow()** - Called every time page is shown (after being hidden)
- Logs view show event
- **onViewHide()** - Called when page is covered or closed
- Logs view hide event
- **onViewDestroy()** - Called when page is destroyed:
- Removes button event listeners
### UI Event Handlers
**_initButtons() - Button setup:**
```typescript
startGameBtn - "Start Game" button click _onStartGameClick()
pkBtn - "PK/Challenge" button click _onPkClick()
```
**_onStartGameClick() - Start game handler:**
- Logs button click
- Opens **PageLevel** (game page)
- Navigation: PageHome → PageLevel
**_onPkClick() - Challenge/PK handler:**
- Logs button click
- Opens **PageWriteLevels** (challenge creation page)
- Navigation: PageHome → PageWriteLevels
### UI Properties
```
@property(Node) startGameBtn
@property(Node) pkBtn
```
### Data Managers Used
- ViewManager (page navigation)
- WxSDK (share initialization, privacy checks)
### WeChat Integration
**_initWxShare() - Share configuration:**
```typescript
WxSDK.initShare({
title: '写英语' (title)
imageUrl: '' (empty - uses default)
query: '' (empty - filled per-share)
})
```
**_checkPrivacyAuthorization() - Privacy handling:**
- Checks if running in WeChat environment: `WxSDK.isWechat()`
- Calls `checkPrivacySetting()` to check privacy authorization status
- If not authorized → calls `requirePrivacyAuthorize()` to prompt user
- Catches exceptions and logs warnings
### Flow
PageLoading → PageHome → [startGameBtn] → PageLevel
→ [pkBtn] → PageWriteLevels
---
## 4. **PageLevel.ts** - Game Level/Challenge Page
**Location:** `assets/prefabs/PageLevel.ts`
**Class:** `PageLevel extends BaseView`
### Purpose
- Main game level playing interface
- Handles gameplay mechanics:
- Display level image/clue
- Accept user answer input
- Verify answer correctness
- Manage hint unlocking with point consumption
- Level progression
- Supports both normal and shared challenge modes
- Manages 60-second countdown timer
### Lifecycle Methods
**onViewLoad() - Initialization:**
1. Reads parameters (checks for `shareMode: true`)
2. Sets `currentLevelIndex`:
- Share mode: start at index 0
- Normal mode: restore from `StorageManager.getCurrentLevelIndex()`
3. Updates points display: `updatePointsLabel()`
4. Initializes icon setting button
5. Initializes unlock buttons (for hints 2 & 3)
6. Initializes submit button
7. **Async:** Loads level config → Starts 60-second countdown
**onViewShow() - Shown callback:**
- Updates points display
**onViewHide() - Hidden callback:**
- Logs event
**onViewDestroy() - Cleanup:**
- Clears input nodes
- Stops countdown
- Closes pass modal
- Removes all event listeners
### UI Properties
```
// Layout
@property(Node) inputLayout - Input box container
@property(Node) submitButton - Answer submission
@property(Node) inputTemplate - Input box template
@property(Node) actionNode - Action buttons area
@property(Node) iconSetting - Settings/back button
@property(Node) tipsLayout - Clues layout
@property(Node) mainImage - Main clue image display
// Clues (3 hints)
@property(Node) tipsItem1 - Clue 1 (default unlocked)
@property(Node) tipsItem2 - Clue 2 (unlockable)
@property(Node) tipsItem3 - Clue 3 (unlockable)
// Unlock buttons
@property(Node) unLockItem2 - Unlock button for clue 2
@property(Node) unLockItem3 - Unlock button for clue 3
// Display labels
@property(Label) clockLabel - Countdown timer display
@property(Label) liveLabel - Points display (named "liveLabel" for compatibility)
// Audio clips
@property(AudioClip) clickAudio
@property(AudioClip) successAudio
@property(AudioClip) failAudio
// Pass modal
@property(Prefab) passModalPrefab
```
### Configuration Properties
```
@property currentLevelIndex - Current level (synced from storage)
```
### Internal State
```
_inputNodes: Node[] - Created input box nodes
_countdown: number - Countdown remaining (60 seconds)
_isTimeUp: boolean - Timer expired flag
_currentConfig: RuntimeLevelConfig - Current level data
_isTransitioning: boolean - Prevents double submit during transitions
_isUnlocking: boolean - Prevents double-click on unlock buttons
_passModalNode: Node - Pass modal instance
_isShareMode: boolean - True if in shared challenge mode
```
### Level Loading
**initLevel() - Async level loading:**
- Share mode: loads from `ShareManager.instance.ensureShareLevelReady(levelIndex)`
- Normal mode:
1. Tries cache: `LevelDataManager.getLevelConfig(levelIndex)`
2. If not cached → loads: `LevelDataManager.ensureLevelReady(levelIndex)`
- Calls `_applyLevelConfig(config)` after loading
**_applyLevelConfig(config) - Apply level configuration:**
1. Stores config in `_currentConfig`
2. Resets transition/time-up flags
3. Sets main image from `config.spriteFrame`
4. Sets clue 1 (default visible) from `config.clue1`
5. Hides clues 2 & 3
6. Shows unlock buttons 2 & 3
7. Creates input field based on answer length: `createSingleInput(config.answer.length)`
8. Updates countdown display
9. Preloads next level:
- Share mode: `ShareManager.instance.ensureShareLevelReady(nextIndex)`
- Normal mode: `LevelDataManager.preloadNextLevel(levelIndex)`
### Input Box Management
**createSingleInput(answerLength) - Single input field:**
- Creates one input field sized for full answer
- Sets placeholder: `(X个字)` (X characters)
- Sets max length to answer length
- Dynamically sizes width: `Math.min(600, Math.max(200, answerLength * 60 + 40))`
- Adjusts underline width to match
- Listens to TEXT_CHANGED and EDITING_DID_ENDED events
**getAnswer() - Retrieves user input:**
- Gets text from single input field
- Trims whitespace
- Returns as string
### Clue/Hint System
**Clue Display (3 levels):**
- Clue 1: Always visible, free
- Clue 2: Requires point consumption to unlock
- Clue 3: Requires point consumption to unlock
**Methods:**
- `setClue(index, content)` - Set clue text
- `showClue(index)` - Show clue item
- `hideClue(index)` - Hide clue item
- `showUnlockButton(index)` - Show unlock button
- `hideUnlockButton(index)` - Hide unlock button
**onUnlockClue(index) - Unlock hint handler:**
1. Check if currently unlocking (prevent double-click): `_isUnlocking`
2. Check points availability: `hasPoints()`
3. If no points → show toast "积分不足,无法解锁提示!" (Insufficient points)
4. Call `UserAssetsManager.consumePoint(levelId, index)` (async)
5. If successful:
- Update points display: `updatePointsLabel()`
- Play click sound
- Hide unlock button
- Show clue
- Set clue content from config (clue2 or clue3)
### Points/Score System
**updatePointsLabel() - Display current points:**
- Gets points: `StorageManager.getPoints()`
- Updates label: `x {points}`
**hasPoints() - Check if user has points:**
- Returns: `StorageManager.hasPoints()`
**Data Manager Interactions:**
- `UserAssetsManager.consumePoint(levelId, hintIndex)` - Consume points for hint (async)
- `UserAssetsManager.earnPoint(levelId, timeSpent)` - Earn points on level completion (async)
- `StorageManager.getPoints()` - Get current points
- `StorageManager.hasPoints()` - Check if any points available
### Countdown Timer System
**startCountdown() - Start 60-second timer:**
- Sets `_countdown = 60`
- Sets `_isTimeUp = false`
- Updates display
- Schedules `onCountdownTick()` every 1 second
**onCountdownTick() - Per-second callback:**
- Decrements `_countdown`
- Updates display
- Calls `onTimeUp()` when countdown reaches 0
**updateClockLabel() - Display countdown:**
- Shows: `{_countdown}s`
**stopCountdown() - Stop timer:**
- Unschedules `onCountdownTick()`
**onTimeUp() - Timer expiration:**
- Plays fail sound
- Can add game over logic
### Answer Submission
**onSubmitAnswer() - Submit button handler:**
1. Checks if config loaded and not in transition
2. Gets user answer: `getAnswer()`
3. Compares with correct answer: `_currentConfig.answer`
4. If match → calls `showSuccess()`
5. If mismatch → calls `showError()`
**showSuccess() - Correct answer handler:**
1. Sets `_isTransitioning = true` (prevent double submit)
2. Stops countdown
3. Plays success sound
4. Calculates time spent: `60 - _countdown`
5. Normal mode:
- Calls `UserAssetsManager.earnPoint(levelId, timeSpent)` to earn points
- Updates points display
6. Share mode:
- Calls `ShareManager.reportLevelProgress(levelId, true, timeSpent)` (fire-and-forget)
7. Shows pass modal: `_showPassModal()`
**_showPassModal() - Display completion modal:**
1. Checks if prefab exists
2. Checks if modal already showing (prevent duplicates)
3. Instantiates pass modal
4. Positions at screen center
5. Sets z-index to 999 (highest layer)
6. Adds to Canvas root node (for proper full-screen layout)
7. Gets PassModal component and:
- Calls `setParams({levelIndex: currentLevelIndex + 1})`
- Sets callbacks for next level and share
- Manually calls `onViewLoad()` and `onViewShow()`
**_closePassModal() - Close pass modal:**
- Checks if valid
- Destroys node
- Sets to null
**showError() - Wrong answer handler:**
1. Plays fail sound
2. Triggers device vibration: `WxSDK.vibrateLong()`
3. Shows toast: "答案错误,再试试吧!" (Wrong answer, try again!)
### Level Progression
**nextLevel() - Advance to next level:**
1. Normal mode: saves progress → `StorageManager.onLevelCompleted(currentLevelIndex)`
2. Increments `currentLevelIndex`
3. Checks if more levels exist:
- Share mode: `ShareManager.getShareLevelCount()`
- Normal mode: `LevelDataManager.getLevelCount()`
4. If all levels complete:
- Logs completion message
- Stops countdown
- Share mode: clears share mode → opens PageHome
- Normal mode: goes back (ViewManager.back())
5. If more levels:
- Loads next level: `initLevel()`
- Restarts countdown: `startCountdown()`
### Audio Management
**playSound(clip) - Generic sound player:**
- Gets AudioSource component
- Plays clip one-shot
**playClickSound()** - Play button click sound
**playSuccessSound()** - Play level completion sound
**playFailSound()** - Play wrong answer sound
### Icon Setting Button (Settings/Back)
**initIconSetting() - Bind settings button:**
- Binds TOUCH_END event to `onIconSettingClick()`
**onIconSettingClick() - Back button handler:**
1. Plays click sound
2. Share mode:
- Clears share mode: `ShareManager.clearShareMode()`
- Replaces with PageHome: `ViewManager.replace('PageHome')`
3. Normal mode:
- Goes back to previous page: `ViewManager.back()`
### Data Managers Used
- **LevelDataManager** - Load level configs, preload next level, get level count
- **StorageManager** - Get/set current level, save progress, get points
- **UserAssetsManager** - Consume/earn points for hints and completions
- **ShareManager** - Handle shared challenge mode
- **WxSDK** - Vibration feedback, WeChat API
- **ToastManager** - Show notification toasts
- **ViewManager** - Navigate to other pages
### Game Flow
1. Load level data (async)
2. Display main image, clue 1, unlock buttons
3. Create input field
4. Start 60-second countdown
5. User enters answer
6. User can unlock clues 2 & 3 (points cost)
7. User submits answer
8. If correct: show pass modal, earn points, progress to next level
9. If incorrect: show error toast, continue playing
10. If timeout: show fail sound, can add game over
### Share Challenge Flow
- Join via WeChat link with share code
- Load custom level set from ShareManager
- Play through shared levels
- Report progress back to server
- Return to home when complete
---
## 5. **PagePreviewLevels.ts** - Challenge Level Preview
**Location:** `assets/prefabs/PagePreviewLevels.ts`
**Class:** `PagePreviewLevels extends BaseView`
### Purpose
- Displays preview of 6 selected challenge levels in a vertical scrollable list
- Used after PageWriteLevels (where user selects 6 levels)
- Shows for each level:
- Cover image
- Hint 1
- Hint 2
- Answer
- Allows user to verify selected levels before sharing
### Lifecycle Methods
**onViewLoad() - First-time initialization:**
1. Calls `_initButtons()` - Bind back buttons
2. Calls `_initScrollView()` - Configure scroll view anchor
**onViewShow() - Page shown:**
1. Calls `_buildList()` - Build level preview list from params
**onViewHide() - Page hidden:**
- Logs event
**onViewDestroy() - Cleanup:**
1. Calls `_offButtons()` - Remove button listeners
2. Calls `_clearList()` - Destroy all list items
### UI Properties
```
@property(Node) backBtn - Top-left back button
@property(Node) scrollView - ScrollView component container
@property(Node) listContent - Content node of scroll view (vertical list)
@property(Node) listTemplate - Template node for single level preview item
@property(Node) backButton - Bottom back button
@property(Node) pkTitle - Title label (editable)
```
### Layout Configuration
```typescript
LAYOUT = {
ITEM_HEIGHT: 300, // Height of each level preview item
SPACING_Y: 30, // Vertical spacing between items
PADDING_TOP: 20 // Top padding
}
```
### Button Handling
**_initButtons() - Bind button events:**
- Binds both `backBtn` and `backButton` (redundant back buttons)
- Listens for Button.EventType.CLICK
- Calls `_onBackClick()` when clicked
**_offButtons() - Remove event listeners:**
- Unbinds both back buttons
**_onBackClick() - Back navigation:**
- Calls `ViewManager.instance.back()` to return to PageWriteLevels
### List Building
**_buildList() - Build preview list:**
1. Clears existing list: `_clearList()`
2. Gets params from ViewManager:
- `selectedIndices` - Array of level indices selected by user
- `shareTitle` - User-entered challenge title
3. Updates title label if provided
4. Logs selected level indices
5. Updates content height based on item count
6. For each selected level index:
- Creates item node: `_createItem(displayIndex)`
- Adds to content node
- Stores in `_itemNodes`
- Async loads data: `_loadLevelData(itemNode, levelIndex, displayIndex)`
7. Scrolls to top: `scrollView.scrollToTop(0)`
**_clearList() - Clear existing items:**
- Destroys all nodes in `_itemNodes`
- Empties array
**_updateContentSize(count) - Set list height:**
- Calculates total height:
```
= PADDING_TOP
+ count * ITEM_HEIGHT
+ (count - 1) * SPACING_Y
+ PADDING_TOP
```
- Sets UITransform contentSize
**_createItem(displayIndex) - Create single preview item:**
1. Instantiates template node
2. Sets active = true
3. Calculates y position (vertical list, negative y downward):
```
y = -(PADDING_TOP + displayIndex * (ITEM_HEIGHT + SPACING_Y) + ITEM_HEIGHT / 2)
```
4. Sets position and returns node
**_loadLevelData(item, levelIndex, displayIndex) - Load level data:**
1. Async loads config: `LevelDataManager.ensureLevelReady(levelIndex)`
2. Fills item components:
- **LevelCover** (Sprite) ← spriteFrame
- **Tips1** (Label) ← `线索一:{clue1}`
- **Tips2** (Label) ← `线索二:{clue2}`
- **Answer** (Label) ← `答案:{answer}`
### Data Managers Used
- **LevelDataManager** - Load level configs for preview
- **ViewManager** - Navigate back
### Parameters (from PageWriteLevels)
```typescript
{
selectedIndices: number[], // [0, 5, 10, 15, 20, 25] - 6 level indices
shareTitle: string // User-entered title
}
```
### Flow
PageHome → PageWriteLevels [Select 6 levels] → PagePreviewLevels [Verify] → Share/Back
### Notes
- Uses vertical scrolling list with dynamic layout
- Displays all level data in preview format
- Title is customizable by user
- Soft loading pattern (preview page shows cached/async-loaded data)
---
## 6. **PassModal.ts** - Level Completion Modal
**Location:** `assets/prefabs/PassModal.ts`
**Class:** `PassModal extends BaseView`
### Purpose
- Modal dialog shown after level is completed successfully
- Provides two action buttons:
- "Next Level" - Progress to next level
- "Share to Friends" - Share challenge via WeChat
- Shows level completion confirmation
- Plays success sound
### Interface Definition
```typescript
export interface PassModalCallbacks {
onNextLevel?: () => void; // Callback when next level clicked
onShare?: () => void; // Callback when share clicked
}
```
### Lifecycle Methods
**onViewLoad() - First-time initialization:**
- Calls `_bindButtonEvents()` to attach button listeners
**onViewShow() - Page shown:**
1. Calls `_updateWidget()` to set modal to full screen size
2. Calls `_playSuccessSound()` to play completion audio
**onViewDestroy() - Cleanup:**
- Calls `_unbindButtonEvents()` to remove listeners
### UI Properties
```
@property(Node) nextLevelButton - "Next Level" button
@property(Node) shareButton - "Share" button
@property(Label) tipLabel - Optional tip label (e.g., "+1 life")
@property(AudioClip) successAudio - Completion sound effect
```
### Static Constants
```typescript
static readonly MODAL_Z_INDEX = 999 // Layer index for topmost display
```
### Internal State
```
_callbacks: PassModalCallbacks - Button callbacks
_screenSize: Size | null - Cached screen dimensions
```
### Methods
**setCallbacks(callbacks: PassModalCallbacks) - Set button callbacks:**
- Stores callbacks for next level and share events
**_updateWidget() - Full-screen sizing:**
1. Gets visible screen size: `view.getVisibleSize()`
2. Caches size in `_screenSize` (avoid repeated calculations)
3. Sets UITransform content size to match screen
**_bindButtonEvents() - Attach listeners:**
```
nextLevelButton → TOUCH_END → _onNextLevelClick()
shareButton → TOUCH_END → _onShareClick()
```
**_unbindButtonEvents() - Remove listeners:**
- Checks `isValid` before removing (node may be destroyed)
- Removes both button listeners
**_playSuccessSound() - Play completion audio:**
- Gets AudioSource component
- Plays `successAudio` one-shot
**_onNextLevelClick() - Next level button:**
- Calls callback: `_callbacks.onNextLevel?.()`
- Usually triggers level progression in PageLevel
**_onShareClick() - Share button:**
1. Calls `WxSDK.shareAppMessage()` with parameters:
```
title: '快来一起玩这款游戏吧' (Come play this game!)
query: `level={levelIndex}`
```
2. Calls callback: `_callbacks.onShare?.()`
3. Modal remains open (user can continue or close)
### Data Managers Used
- **WxSDK** - Share app message to WeChat
### Parameters (from PageLevel)
```typescript
{
levelIndex: number // Current level index (1-based for display)
}
```
### Flow
Level Completed → Show PassModal → [Next Level] → Continue game
→ [Share] → Share to WeChat, stay in modal
→ Close → Return to PageLevel
### Notes
- Modal stays open after share (user can click next level without closing)
- Uses full screen size with z-index 999 for modal effect
- Callbacks allow decoupling from page logic
- Screen size cached to avoid repeated view queries
---
## 7. **Toast.ts** - Toast Notification Component
**Location:** `assets/prefabs/Toast.ts`
**Class:** `Toast extends Component`
### Purpose
- Individual toast notification display component
- Shows brief notification message with fade-out animation
- Auto-destroys after display duration
- Supports custom duration
### Lifecycle Methods
**onLoad() - Initialization:**
1. Gets or creates UIOpacity component for fade animation
2. Stores reference in `_uiOpacity`
### UI Properties
```
@property(Label) contentLabel - Text label for message content
```
### Internal State
```
_uiOpacity: UIOpacity | null - Component for opacity animation
```
### Methods
**show(content: string, duration: number = 2000) - Display toast:**
1. Sets label text: `contentLabel.string = content`
2. Resets opacity to full: `_uiOpacity.opacity = 255`
3. Schedules fade-out after duration (converted to seconds):
```
scheduleOnce(_fadeOut, duration / 1000)
```
**_fadeOut() - Fade-out animation:**
1. Tweens opacity from 255 → 0 over 0.3 seconds
2. On completion: destroys node
3. Uses Cocos tween system for smooth animation
### Data Flow
ToastManager.show("message") → Creates Toast instance → Toast.show() → Display + Schedule fade → Destroy
### Features
- Default display time: 2000ms (2 seconds)
- Fade duration: 300ms
- Auto-destroy after fade complete
- UIOpacity component for animation support
---
## 8. **ToastManager.ts** - Toast Management Utility
**Location:** `assets/scripts/utils/ToastManager.ts`
**Class:** `ToastManager` (Singleton)
### Purpose
- Singleton manager for centralized toast notification handling
- Creates and displays Toast instances in a container
- Provides static convenience methods for showing messages
- Initializes toast system once, uses throughout app
### Singleton Pattern
```typescript
static get instance(): ToastManager
// Lazy instantiation on first access
```
### Lifecycle
**init(prefab: Prefab, container?: Node) - Initialize manager:**
- Stores toast prefab reference
- Sets container (defaults to Canvas if not provided)
- Should be called once in main.ts during app initialization
**show(content: string, duration?: number) - Show toast:**
1. Validates prefab and container are initialized
2. Instantiates toast prefab
3. Adds to container as child
4. Gets Toast component from node
5. Calls `Toast.show(content, duration)` to display
### Static Methods
```typescript
static show(content: string, duration?: number = 2000)
// Convenience method: ToastManager.show("message")
```
### Internal State
```
_prefab: Prefab | null - Toast prefab (set during init)
_container: Node | null - Parent node for toast instances
```
### Data Managers Used
- None (utility class)
### Usage Example
```typescript
// In ToastManager initialization (main.ts)
ToastManager.instance.init(toastPrefab, canvasNode);
// Anywhere in app
ToastManager.show("积分不足!", 2000);
```
### Flow
ToastManager.init() [once] → ToastManager.show() [many times]
Each show() → instantiate → add to container → display → fade → destroy
---
## 9. **BaseView.ts** - Base View Class
**Location:** `assets/scripts/core/BaseView.ts`
**Class:** `BaseView extends Component`
### Purpose
- Abstract base class for all pages/views in the game
- Defines page lifecycle interface for consistent behavior
- Manages view state (showing, parameters)
- Handles Cocos lifecycle integration
### Interface Definition
```typescript
export interface ViewConfig {
prefab: Prefab; // Prefab asset reference
cache?: boolean; // Cache instance (true = reuse, false = destroy)
zIndex?: number; // Layer depth (higher = on top)
}
export interface ViewOptions {
params?: any; // Data to pass to page
onComplete?: (view: BaseView) => void; // Success callback
onError?: (err: Error) => void; // Error callback
}
```
### Properties
**Public:**
```
viewId: string - Unique identifier
config: ViewConfig - Configuration from ViewManager
isShowing: boolean - Currently visible flag
_params: any - View parameters
```
### Lifecycle Methods (To Override)
**onViewLoad() - First-time initialization:**
- Called when page is first created
- Initialize UI, bind events, load data
- Default: empty implementation
**onViewShow() - Page becomes visible:**
- Called when page is opened or shown
- Update UI based on current state
- Default: empty implementation
**onViewHide() - Page becomes hidden:**
- Called when page is closed or covered
- Pause animations, release resources
- Default: empty implementation
**onViewDestroy() - Page destruction:**
- Called when page is being destroyed
- Cleanup resources, remove listeners
- Default: empty implementation
### Public Methods
**setParams(params: any) - Store view parameters:**
- Stores data passed from caller
- Used before showing view
**getParams(): any - Retrieve view parameters:**
- Returns stored params
- Used by view to read initialization data
### Internal Methods (Called by ViewManager)
**_doShow() - Execute show logic:**
1. Check if already showing (prevent double-show)
2. Set `isShowing = true`
3. Set `node.active = true`
4. Call `onViewShow()`
**_doHide() - Execute hide logic:**
1. Check if already hidden
2. Set `isShowing = false`
3. Call `onViewHide()`
4. Set `node.active = false`
**_doDestroy() - Execute destroy logic:**
1. Mark as destroyed (prevent double-call)
2. Call `node.destroy()`
### Cocos Lifecycle Integration
**onDestroy() - Cocos lifecycle (called by engine):**
1. Check if not already marked destroyed
2. If currently showing: call `onViewHide()`
3. Call `onViewDestroy()`
### Internal State
```
_destroyed: boolean - Destroyed flag (prevent double-call)
```
### Usage Pattern
```typescript
@ccclass('PageExample')
export class PageExample extends BaseView {
onViewLoad(): void {
// Initialize UI
}
onViewShow(): void {
// Page shown
}
onViewHide(): void {
// Page hidden
}
onViewDestroy(): void {
// Cleanup
}
}
```
### Data Managers Used
- None (base class provides only framework)
### Inheritance Hierarchy
- PageHome extends BaseView
- PageLevel extends BaseView
- PagePreviewLevels extends BaseView
- PassModal extends BaseView
---
## 10. **ViewManager.ts** - View/Page Management System
**Location:** `assets/scripts/core/ViewManager.ts`
**Class:** `ViewManager` (Singleton)
### Purpose
- Centralized page management system
- Handles:
- Page registration and configuration
- Page lifecycle (create, show, hide, destroy)
- Navigation (open, back, replace)
- Page stacking (history navigation)
- Instance caching for reusable pages
- Preloading support
### Singleton Pattern
```typescript
static get instance(): ViewManager
// Lazy-instantiated on first access
```
### Data Structures
**Registered Views:**
```typescript
_registeredViews: Map<string, RegisteredView>
// Maps viewId → configuration
```
**View Stack:**
```typescript
_viewStack: BaseView[]
// LIFO stack for navigation history
```
**View Cache:**
```typescript
_viewCache: Map<string, BaseView>
// Stores instances when cache: true in config
```
### Lifecycle Methods
**init(container: Node) - Initialize manager:**
- Sets container node (usually Canvas)
- Called once in main.ts during startup
- Must be called before opening any pages
### Registration Methods
**register(viewId: string, config: ViewConfig) - Register single page:**
1. Validates not already registered (logs error if duplicate)
2. Applies defaults:
```
cache: true (default)
zIndex: 0 (default)
```
3. Stores in `_registeredViews`
**registerAll(views: Record<string, ViewConfig>) - Batch register:**
- Calls `register()` for each view
### Page Opening
**open(viewId: string, options?: ViewOptions) - Open page:**
1. Validates container initialized
2. Validates page registered
3. Checks cache:
- If cached instance exists and valid → calls `_showView()`
- Otherwise → calls `_instantiateView()`
**_instantiateView(viewId, prefab, options) - Create new instance:**
1. Instantiates prefab: `instantiate(prefab)`
2. Gets BaseView component from node
3. Validates component exists (logs error if missing)
4. Sets view properties:
- `viewId` - page identifier
- `config` - page configuration
- params from options
5. Sets z-index: `setSiblingIndex(config.zIndex)`
6. Adds to container
7. Calls `onViewLoad()` on page
8. Caches if `config.cache === true`
9. Calls `_showView()` to display
**_showView(view, options) - Display page:**
1. Gets current view: `getCurrentView()`
2. If different view showing → calls `_doHide()` on it
3. Updates params if provided
4. Adds to stack if not already there: `_viewStack.push(view)`
5. Calls `view._doShow()` to activate
6. Calls completion callback: `options.onComplete?.(view)`
### Navigation
**back() - Return to previous page:**
- Alias for `close()`
- Pops stack, hides current, shows previous
**close(options?: {destroy?: boolean}) - Close current page:**
1. Pops view from stack
2. Determines if should destroy:
```
shouldDestroy = options?.destroy ?? !currentView.config?.cache
```
(Default: destroy if not cached)
3. Calls `_hideAndDestroyView()`
4. Gets next view from stack
5. If exists → calls `_doShow()` on it
**replace(viewId: string, options?: ViewOptions) - Replace current page:**
1. Gets current view from top of stack
2. Pops and removes from stack
3. Destroys based on cache setting
4. Opens new page: `open(viewId, options)`
### Utility Methods
**_hideAndDestroyView(view, shouldDestroy) - Hide and optionally destroy:**
1. Calls `view._doHide()` to hide
2. If `shouldDestroy`:
- Removes from cache: `_viewCache.delete()`
- Calls `view._doDestroy()` to destroy node
**getCurrentView(): BaseView | null - Get active page:**
- Returns top of stack (or null if empty)
**getViewStack(): BaseView[] - Get copy of stack:**
- Returns [..._viewStack] (copy prevents external modification)
**clearAll() - Clear all pages:**
1. Pops and destroys all stack pages
2. Destroys all cached pages
3. Clears caches
### Preloading
**preload(viewId, onProgress?, onComplete?) - Preload page:**
- Note: Main bundle assets already loaded, so this is mostly for compatibility
- Immediately callbacks with 100% progress and complete
**preloadAll(viewIds, onProgress?, onComplete?) - Batch preload:**
- Preloads all provided view IDs
- Accumulates progress (0-1)
- Calls complete when all done
### Data Flow
**Opening First Page (from PageLoading):**
```
PageLoading.start()
→ ViewManager.open('PageHome')
→ _instantiateView('PageHome', prefab)
→ instantiate prefab
→ getComponent(BaseView)
→ call onViewLoad()
→ cache instance
→ _showView()
→ push to stack
→ call _doShow()
→ node.active = true
→ onViewShow()
```
**Navigating Between Pages:**
```
PageHome.startGameBtn.click()
→ ViewManager.open('PageLevel')
→ _instantiateView or _showView (if cached)
→ _showView()
→ getCurrentView() = PageHome
→ PageHome._doHide()
→ push PageLevel to stack
→ PageLevel._doShow()
```
**Going Back:**
```
PageLevel.onIconSettingClick()
→ ViewManager.back()
→ close()
→ pop from stack = PageLevel
→ PageLevel._doHide()
→ destroy or cache PageLevel
→ getCurrentView() = PageHome
→ PageHome._doShow()
```
### Stack Example
**Session Flow:**
```
Initial: []
After loading PageHome:
[PageHome]
Open PageLevel from PageHome:
[PageHome, PageLevel]
Open PassModal (modal, not in flow typically):
[PageHome, PageLevel, PassModal?] - or modal might use different system
Back from PageLevel:
[PageHome]
Open PageWriteLevels:
[PageHome, PageWriteLevels]
Open PagePreviewLevels:
[PageHome, PageWriteLevels, PagePreviewLevels]
Back from PagePreviewLevels:
[PageHome, PageWriteLevels]
Back from PageWriteLevels:
[PageHome]
```
### Caching Behavior
**Cached Pages (cache: true):**
- Instance reused on re-open
- `onViewLoad()` only called once (first open)
- `onViewShow()` called each time opened
- Memory stays allocated
**Non-Cached Pages (cache: false):**
- New instance created each time
- `onViewLoad()` called each time
- Full cleanup on close
- Memory released
### Error Handling
- Validates container initialized before operations
- Validates page registered before opening
- Validates BaseView component exists on prefab
- Logs errors (using Cocos `error()` function)
- Returns via error callback if provided
### Data Managers Used
- None (infrastructure only)
---
## Summary Table
| File | Class | Purpose | Extends | Key Features |
|------|-------|---------|---------|--------------|
| main.ts | main | App initialization | Component | Initialize ViewManager, register pages |
| PageLoading.ts | PageLoading | Splash/loading screen | Component | Load data, sync progress, handle share code |
| PageHome.ts | PageHome | Landing page | BaseView | Navigation to game/PK, WeChat setup |
| PageLevel.ts | PageLevel | Game level | BaseView | Game mechanics, timer, hints, scoring |
| PagePreviewLevels.ts | PagePreviewLevels | Level preview | BaseView | Scrollable list, level verification |
| PassModal.ts | PassModal | Completion modal | BaseView | Next level/share buttons, success sound |
| Toast.ts | Toast | Notification | Component | Message display, fade animation |
| ToastManager.ts | ToastManager | Toast system | Singleton | Centralized toast management |
| BaseView.ts | BaseView | Page base class | Component | Lifecycle interface, parameter passing |
| ViewManager.ts | ViewManager | Page management | Singleton | Navigation, stacking, caching |
---
## Game Flow Map
```
┌─────────────────────────────────────────────────────────────┐
│ main.ts (Initialize) │
│ Initialize ViewManager + Register All Pages │
└──────────────────────┬──────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ PageLoading.ts (Splash) │
│ • Load AuthManager + LevelDataManager (parallel) │
│ • Sync server progress │
│ • Check for share code (WeChat) │
│ • Preload PageHome │
└──────────────────────┬──────────────────────────────────────┘
┌──────────────┼──────────────┐
│ Normal Mode │ Share Mode │
▼ ▼ │
┌────────────────────────────┐ │
│ PageHome.ts (Landing) │ │
│ [Start Game] [PK/Create] │ │
└──┬──────────────────────┬──┘ │
│ │ │
▼ ▼ │
┌─────────────┐ ┌──────────────────┐
│ PageLevel.ts│ │PageWriteLevels.ts│ (not shown)
│ (Game) │ │ (Create PK) │
│ • Clues │ │ • Select 6 │
│ • Timer │ │ • Input title │
│ • Score │ └──────┬───────────┘
│ • Submit │ │
└──┬──────────┘ ▼
│ ┌──────────────────┐
│ │PagePreviewLevels │
│ │ (Verify/Share) │
│ └──────┬───────────┘
│ │
├─────────────────────┤
▼ ▼
┌──────────────────────────────────┐
│ PassModal.ts (Complete) │
│ [Next Level] [Share to Friends]│
│ • Earn points │
│ • Play success sound │
└──┬───────────────────────────────┘
│ [Next Level]
▼ or [Back]
(Loop to next level)
```
---
## Data Manager Integration
```
┌─────────────────────────────────────────────────────────┐
│ Data Managers (Not in this analysis) │
├─────────────────────────────────────────────────────────┤
│ • AuthManager - User login, completedLevelIds │
│ • LevelDataManager - Level configs, preloading │
│ • StorageManager - Local progress, points │
│ • UserAssetsManager - Point consumption/earning │
│ • ShareManager - Share code, challenge data │
│ • WxSDK - WeChat API, vibration, sharing │
│ • ToastManager - Notifications │
│ • ViewManager - Page navigation │
└─────────────────────────────────────────────────────────┘
Used By Each Component:
┌────────────────────────────────────────────────┐
│ PageLoading │
│ • AuthManager.initialize() │
│ • LevelDataManager.initialize() │
│ • StorageManager.getMaxUnlockedLevelIndex() │
│ • StorageManager.onLevelCompleted() │
│ • ShareManager.joinShare() │
│ • WxSDK.getShareCodeFromLaunch() │
│ • ViewManager.preload/open() │
└────────────────────────────────────────────────┘
┌────────────────────────────────────────────────┐
│ PageLevel │
│ • LevelDataManager (load, preload) │
│ • StorageManager (progress, points) │
│ • UserAssetsManager (consume, earn) │
│ • ShareManager (share mode, report progress) │
│ • WxSDK (vibration, sharing) │
│ • ToastManager (notifications) │
│ • ViewManager (navigation) │
└────────────────────────────────────────────────┘
┌────────────────────────────────────────────────┐
│ PagePreviewLevels │
│ • LevelDataManager.ensureLevelReady() │
│ • ViewManager.back() │
└────────────────────────────────────────────────┘
```
---
## Key Concepts
### 1. **View Lifecycle Pattern**
- **onViewLoad**: First-time initialization
- **onViewShow**: Shown/activated
- **onViewHide**: Hidden/covered
- **onViewDestroy**: Destroyed/cleaned up
### 2. **Caching Strategy**
- Pages can be cached (reused) or destroyed after close
- Cached pages call onViewLoad() once, onViewShow() multiple times
- Default: cache=true for most pages
### 3. **Page Stacking**
- Stack-based navigation (LIFO)
- Each open() adds to stack
- back() pops from stack
- replace() swaps top of stack
### 4. **Points/Scoring System**
- Earn points on level completion: based on time spent
- Consume points to unlock hints
- Points stored locally in StorageManager
- Synced from server on app start
### 5. **Hint System**
- 3 hints per level
- Hint 1: always free
- Hints 2-3: point consumption
- Prevents double-unlock with flag
### 6. **Timer Mechanic**
- 60-second countdown per level
- Displayed in clock label
- Affects points earned (faster = more points)
- Plays sound on timeout
### 7. **Share Challenge Mode**
- Different game flow from normal mode
- Uses ShareManager instead of LevelDataManager
- Reports progress back to server
- Returns to home on completion
### 8. **Toast Notification System**
- Singleton manager with prefab pool
- Supports custom duration
- Fade-out animation
- Auto-destroys after display