# 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 // Maps viewId → configuration ``` **View Stack:** ```typescript _viewStack: BaseView[] // LIFO stack for navigation history ``` **View Cache:** ```typescript _viewCache: Map // 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) - 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