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

45 KiB

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:

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:

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

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)

{
  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

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

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)

{
  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

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

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

// 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

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

@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

static get instance(): ViewManager
// Lazy-instantiated on first access

Data Structures

Registered Views:

_registeredViews: Map<string, RegisteredView>
// Maps viewId → configuration

View Stack:

_viewStack: BaseView[]
// LIFO stack for navigation history

View Cache:

_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