- 进入关卡成功后显示 toast 提示消耗体力及剩余体力 - 将 StorageManager 中 UserInfo 接口移至模块顶层,修复嵌套接口语法问题 - WxSDK.getWx() 改为 static 公开方法,便于外部调用
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
- Calls
Key Methods
- _initViewManager() - Core initialization logic:
- Initializes
ViewManagerwith 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
ToastManagerwith toast prefab and canvas reference
- Initializes
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()
- Immediately calls
Key Methods
_startPreload() - Main initialization pipeline:
- Resets progress bar to 0%
- Runs login and level loading in parallel using
Promise.all():AuthManager.instance.initialize()- User authenticationLevelDataManager.instance.initialize()- Level data loading (provides progress updates)- Level loading occupies 0-80% of progress bar
- Logs login success/failure
- Checks for login success:
- If login successful → calls
_syncProgressFromServer() - Maps server
completedLevelIdsto localcurrentLevelIndex - Takes max of (server progress, local progress) to prevent regression
- If login successful → calls
- 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
- Calls
- Normal flow (no share code):
- Preloads PageHome (80-100% of progress)
- Opens PageHome and destroys itself
_syncProgressFromServer() - Server progress sync:
- Gets
completedLevelIdsfrom 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()
- Calls
-
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:
- Reads parameters (checks for
shareMode: true) - Sets
currentLevelIndex:- Share mode: start at index 0
- Normal mode: restore from
StorageManager.getCurrentLevelIndex()
- Updates points display:
updatePointsLabel() - Initializes icon setting button
- Initializes unlock buttons (for hints 2 & 3)
- Initializes submit button
- 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:
- Tries cache:
LevelDataManager.getLevelConfig(levelIndex) - If not cached → loads:
LevelDataManager.ensureLevelReady(levelIndex)
- Tries cache:
- Calls
_applyLevelConfig(config)after loading
_applyLevelConfig(config) - Apply level configuration:
- Stores config in
_currentConfig - Resets transition/time-up flags
- Sets main image from
config.spriteFrame - Sets clue 1 (default visible) from
config.clue1 - Hides clues 2 & 3
- Shows unlock buttons 2 & 3
- Creates input field based on answer length:
createSingleInput(config.answer.length) - Updates countdown display
- Preloads next level:
- Share mode:
ShareManager.instance.ensureShareLevelReady(nextIndex) - Normal mode:
LevelDataManager.preloadNextLevel(levelIndex)
- Share mode:
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 textshowClue(index)- Show clue itemhideClue(index)- Hide clue itemshowUnlockButton(index)- Show unlock buttonhideUnlockButton(index)- Hide unlock button
onUnlockClue(index) - Unlock hint handler:
- Check if currently unlocking (prevent double-click):
_isUnlocking - Check points availability:
hasPoints() - If no points → show toast "积分不足,无法解锁提示!" (Insufficient points)
- Call
UserAssetsManager.consumePoint(levelId, index)(async) - If successful:
- Update points display:
updatePointsLabel() - Play click sound
- Hide unlock button
- Show clue
- Set clue content from config (clue2 or clue3)
- Update points display:
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 pointsStorageManager.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:
- Checks if config loaded and not in transition
- Gets user answer:
getAnswer() - Compares with correct answer:
_currentConfig.answer - If match → calls
showSuccess() - If mismatch → calls
showError()
showSuccess() - Correct answer handler:
- Sets
_isTransitioning = true(prevent double submit) - Stops countdown
- Plays success sound
- Calculates time spent:
60 - _countdown - Normal mode:
- Calls
UserAssetsManager.earnPoint(levelId, timeSpent)to earn points - Updates points display
- Calls
- Share mode:
- Calls
ShareManager.reportLevelProgress(levelId, true, timeSpent)(fire-and-forget)
- Calls
- Shows pass modal:
_showPassModal()
_showPassModal() - Display completion modal:
- Checks if prefab exists
- Checks if modal already showing (prevent duplicates)
- Instantiates pass modal
- Positions at screen center
- Sets z-index to 999 (highest layer)
- Adds to Canvas root node (for proper full-screen layout)
- Gets PassModal component and:
- Calls
setParams({levelIndex: currentLevelIndex + 1}) - Sets callbacks for next level and share
- Manually calls
onViewLoad()andonViewShow()
- Calls
_closePassModal() - Close pass modal:
- Checks if valid
- Destroys node
- Sets to null
showError() - Wrong answer handler:
- Plays fail sound
- Triggers device vibration:
WxSDK.vibrateLong() - Shows toast: "答案错误,再试试吧!" (Wrong answer, try again!)
Level Progression
nextLevel() - Advance to next level:
- Normal mode: saves progress →
StorageManager.onLevelCompleted(currentLevelIndex) - Increments
currentLevelIndex - Checks if more levels exist:
- Share mode:
ShareManager.getShareLevelCount() - Normal mode:
LevelDataManager.getLevelCount()
- Share mode:
- If all levels complete:
- Logs completion message
- Stops countdown
- Share mode: clears share mode → opens PageHome
- Normal mode: goes back (ViewManager.back())
- If more levels:
- Loads next level:
initLevel() - Restarts countdown:
startCountdown()
- Loads next level:
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:
- Plays click sound
- Share mode:
- Clears share mode:
ShareManager.clearShareMode() - Replaces with PageHome:
ViewManager.replace('PageHome')
- Clears share mode:
- Normal mode:
- Goes back to previous page:
ViewManager.back()
- Goes back to previous page:
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
- Load level data (async)
- Display main image, clue 1, unlock buttons
- Create input field
- Start 60-second countdown
- User enters answer
- User can unlock clues 2 & 3 (points cost)
- User submits answer
- If correct: show pass modal, earn points, progress to next level
- If incorrect: show error toast, continue playing
- 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:
- Calls
_initButtons()- Bind back buttons - Calls
_initScrollView()- Configure scroll view anchor
onViewShow() - Page shown:
- Calls
_buildList()- Build level preview list from params
onViewHide() - Page hidden:
- Logs event
onViewDestroy() - Cleanup:
- Calls
_offButtons()- Remove button listeners - 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
backBtnandbackButton(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:
- Clears existing list:
_clearList() - Gets params from ViewManager:
selectedIndices- Array of level indices selected by usershareTitle- User-entered challenge title
- Updates title label if provided
- Logs selected level indices
- Updates content height based on item count
- For each selected level index:
- Creates item node:
_createItem(displayIndex) - Adds to content node
- Stores in
_itemNodes - Async loads data:
_loadLevelData(itemNode, levelIndex, displayIndex)
- Creates item node:
- 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:
- Instantiates template node
- Sets active = true
- Calculates y position (vertical list, negative y downward):
y = -(PADDING_TOP + displayIndex * (ITEM_HEIGHT + SPACING_Y) + ITEM_HEIGHT / 2) - Sets position and returns node
_loadLevelData(item, levelIndex, displayIndex) - Load level data:
- Async loads config:
LevelDataManager.ensureLevelReady(levelIndex) - 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:
- Calls
_updateWidget()to set modal to full screen size - 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:
- Gets visible screen size:
view.getVisibleSize() - Caches size in
_screenSize(avoid repeated calculations) - Sets UITransform content size to match screen
_bindButtonEvents() - Attach listeners:
nextLevelButton → TOUCH_END → _onNextLevelClick()
shareButton → TOUCH_END → _onShareClick()
_unbindButtonEvents() - Remove listeners:
- Checks
isValidbefore removing (node may be destroyed) - Removes both button listeners
_playSuccessSound() - Play completion audio:
- Gets AudioSource component
- Plays
successAudioone-shot
_onNextLevelClick() - Next level button:
- Calls callback:
_callbacks.onNextLevel?.() - Usually triggers level progression in PageLevel
_onShareClick() - Share button:
- Calls
WxSDK.shareAppMessage()with parameters:title: '快来一起玩这款游戏吧' (Come play this game!) query: `level={levelIndex}` - Calls callback:
_callbacks.onShare?.() - 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:
- Gets or creates UIOpacity component for fade animation
- 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:
- Sets label text:
contentLabel.string = content - Resets opacity to full:
_uiOpacity.opacity = 255 - Schedules fade-out after duration (converted to seconds):
scheduleOnce(_fadeOut, duration / 1000)
_fadeOut() - Fade-out animation:
- Tweens opacity from 255 → 0 over 0.3 seconds
- On completion: destroys node
- 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:
- Validates prefab and container are initialized
- Instantiates toast prefab
- Adds to container as child
- Gets Toast component from node
- 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:
- Check if already showing (prevent double-show)
- Set
isShowing = true - Set
node.active = true - Call
onViewShow()
_doHide() - Execute hide logic:
- Check if already hidden
- Set
isShowing = false - Call
onViewHide() - Set
node.active = false
_doDestroy() - Execute destroy logic:
- Mark as destroyed (prevent double-call)
- Call
node.destroy()
Cocos Lifecycle Integration
onDestroy() - Cocos lifecycle (called by engine):
- Check if not already marked destroyed
- If currently showing: call
onViewHide() - 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:
- Validates not already registered (logs error if duplicate)
- Applies defaults:
cache: true (default) zIndex: 0 (default) - Stores in
_registeredViews
registerAll(views: Record<string, ViewConfig>) - Batch register:
- Calls
register()for each view
Page Opening
open(viewId: string, options?: ViewOptions) - Open page:
- Validates container initialized
- Validates page registered
- Checks cache:
- If cached instance exists and valid → calls
_showView() - Otherwise → calls
_instantiateView()
- If cached instance exists and valid → calls
_instantiateView(viewId, prefab, options) - Create new instance:
- Instantiates prefab:
instantiate(prefab) - Gets BaseView component from node
- Validates component exists (logs error if missing)
- Sets view properties:
viewId- page identifierconfig- page configuration- params from options
- Sets z-index:
setSiblingIndex(config.zIndex) - Adds to container
- Calls
onViewLoad()on page - Caches if
config.cache === true - Calls
_showView()to display
_showView(view, options) - Display page:
- Gets current view:
getCurrentView() - If different view showing → calls
_doHide()on it - Updates params if provided
- Adds to stack if not already there:
_viewStack.push(view) - Calls
view._doShow()to activate - 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:
- Pops view from stack
- Determines if should destroy:
(Default: destroy if not cached)
shouldDestroy = options?.destroy ?? !currentView.config?.cache - Calls
_hideAndDestroyView() - Gets next view from stack
- If exists → calls
_doShow()on it
replace(viewId: string, options?: ViewOptions) - Replace current page:
- Gets current view from top of stack
- Pops and removes from stack
- Destroys based on cache setting
- Opens new page:
open(viewId, options)
Utility Methods
_hideAndDestroyView(view, shouldDestroy) - Hide and optionally destroy:
- Calls
view._doHide()to hide - If
shouldDestroy:- Removes from cache:
_viewCache.delete() - Calls
view._doDestroy()to destroy node
- Removes from cache:
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:
- Pops and destroys all stack pages
- Destroys all cached pages
- 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