import 'db://assets/mx/tools/MiniGameTextureBoot'; import 'db://assets/mx/tools/CacheManagerPatch'; import 'db://assets/mx/tools/SilenceConsoleInRelease'; import 'db://assets/script/battle/debug/DesignDebug'; import { _decorator, assetManager, Camera, clamp01, Component, director, Label, Node, Sprite, sys } from 'cc'; import { GBundle, UIConfigs, GAudioPath } from './game/ConfigRes'; import { GameController } from './game/GameController'; import { GM } from './game/GM'; import { DEBUG, WECHAT } from 'cc/env'; import { sdkConfig } from './sdk/sdkConfig'; import { GEvent } from '../mx/module/event/GEvent'; import { BattleUiPauseGate } from './battle/BattleUiPauseGate'; import { BootPipeline } from '../mx/module/boot/BootPipeline'; import { RuntimeLoadingGate } from '../mx/module/ui/RuntimeLoadingGate'; import { ClientErrorReporter } from '../mx/tools/ClientErrorReporter'; const { ccclass, property } = _decorator; /** * 游戏入口组件:冷启动 Boot、双门闩进游戏、Boot/运行时 Loading 与网络等待 UI。 */ @ccclass('GameLaunch') export class GameLaunch extends Component { /** 当前场景挂载的唯一实例 */ static instantiate: GameLaunch = null; @property(Node) Loading: Node; @property(Label) title: Label; @property(Node) netUI: Node; @property(Node) loadingUI: Node; private _progress = 0; /** 首包 Boot 资源就绪 */ private _firstResReady = false; /** 配表与 gg.data.init 完成 */ private _sdkAndDataReady = false; /** 已进入游戏,停止 Boot 进度刷新 */ private _enteredGame = false; private _deferredStarted = false; private _loadingProgressSprite: Sprite | null = null; private _loadingPercentLabel: Label | null = null; private _loadingBarSprite: Sprite | null = null; private _bootPipeline = new BootPipeline(); private _runtimeLoadingGate: RuntimeLoadingGate | null = null; protected onLoad(): void { gg.sdk.capturePfShow(); if (WECHAT) { // 编译期仅微信包保留;initSdk30r 内再 require 30r,抖音/快手包不含 @dn-sdk gg.sdk.initSdk30r(); } GameLaunch.instantiate = this; if (assetManager.cacheManager) { assetManager.cacheManager.autoClear = true; } this.bindGlobalEvents(); this.resolveLoadingRefs(); this._runtimeLoadingGate = new RuntimeLoadingGate( this.getLoadingUI.bind(this), this.getBootLoading.bind(this), this.isRuntimePhase.bind(this), this.onLoadingWatchdog.bind(this), ); } protected update(dt: number): void { if (this._enteredGame) { return; } if (this._loadingProgressSprite) { this._loadingProgressSprite.fillRange = clamp01(this._progress); } if (!gg.game) { return; } const progress = gg.game.CurentLoadingProgress; if (this._loadingPercentLabel) { this._loadingPercentLabel.string = `${Math.floor(progress * 100)}%`; } if (this._loadingBarSprite) { this._loadingBarSprite.fillRange = progress; } } start(): void { if (DEBUG) { window['gg'] = gg; window['gm'] = GM; } if (!this.initCameraAndShell()) { return; } gg.game = this.addComponent(GameController); Promise.all([ gg.lang.init(sdkConfig.defaultLang), this._bootPipeline.start({ onTitle: this.onBootTitle.bind(this), onProgress: this.onBootProgress.bind(this), onBootAssetsReady: this.onBootAssetsReady.bind(this), onSdkAndDataReady: this.onSdkAndDataReady.bind(this), }), ]).catch(this.onBootFailed.bind(this)); } /** * 双门闩:首包资源与 SDK/存档均就绪后进游戏。 * 门闩字段为 _firstResReady / _sdkAndDataReady;不用 Loading.active 作就绪条件。 */ private tryEnterGame(): void { if (this._enteredGame) { return; } if (!this._firstResReady || !this._sdkAndDataReady) { return; } if (this._progress < 1) { this._progress = 1; } this._enteredGame = true; if (!this._deferredStarted) { this._deferredStarted = true; this._bootPipeline.startDeferredPhase(); } gg.game.init(); if (this.Loading && this.Loading.active) { this.Loading.active = false; } if (this._loadingProgressSprite) { this._loadingProgressSprite.fillRange = 1; } this.enabled = false; } /** 显示网络等待 UI */ showNetUI(): void { if (this.netUI) { this.netUI.active = true; } } /** 关闭网络等待 UI */ closeNetUI(): void { if (this.netUI) { this.netUI.active = false; } } /** 请求显示运行时 Loading */ showLoading(index: number = 2, immediate = false): void { this._runtimeLoadingGate?.requestShow(index, immediate); } /** 请求隐藏运行时 Loading */ hideLoading(): void { this._runtimeLoadingGate?.requestHide(); } /** 强制关闭运行时 Loading */ forceHideLoading(): void { this._runtimeLoadingGate?.forceHide(); } protected onDestroy(): void { this.unbindGlobalEvents(); this._runtimeLoadingGate?.dispose(); this._runtimeLoadingGate = null; if (GameLaunch.instantiate === this) { GameLaunch.instantiate = null; } } private bindGlobalEvents(): void { GEvent.Ins.on(GEvent.SHOW_NET_UI, this.showNetUI, this); GEvent.Ins.on(GEvent.HIDE_NET_UI, this.closeNetUI, this); GEvent.Ins.on(GEvent.SHOW_LOADING, this.showLoading, this); GEvent.Ins.on(GEvent.HIDE_LOADING, this.hideLoading, this); GEvent.Ins.on(GEvent.FORCE_HIDE_LOADING, this.forceHideLoading, this); this._bindJsErrorSink(); this._bindBrowserErrorSink(); } private unbindGlobalEvents(): void { GEvent.Ins.off(GEvent.SHOW_NET_UI, this.showNetUI, this); GEvent.Ins.off(GEvent.HIDE_NET_UI, this.closeNetUI, this); GEvent.Ins.off(GEvent.SHOW_LOADING, this.showLoading, this); GEvent.Ins.off(GEvent.HIDE_LOADING, this.hideLoading, this); GEvent.Ins.off(GEvent.FORCE_HIDE_LOADING, this.forceHideLoading, this); if (sys.isBrowser && typeof window !== 'undefined') { window.removeEventListener('error', this._onBrowserError); window.removeEventListener('unhandledrejection', this._onBrowserRejection); } } /** 小游戏未捕获异常:打日志并上报平台 */ private _bindJsErrorSink(): void { const names = ['wx', 'tt', 'ks', 'qg']; for (let i = 0; i < names.length; i++) { const api = window[names[i]] as { onError?: (cb: (msg: unknown) => void) => void; onUnhandledRejection?: (cb: (res: unknown) => void) => void; } | undefined; if (!api) continue; try { api.onError?.(this._onMiniGameJsError); api.onUnhandledRejection?.(this._onMiniGameRejection); } catch (e) { ClientErrorReporter.report('GameLaunch_bindJsErrorSink', e); } } } /** 浏览器预览未捕获异常 */ private _bindBrowserErrorSink(): void { if (!sys.isBrowser || typeof window === 'undefined') return; window.addEventListener('error', this._onBrowserError); window.addEventListener('unhandledrejection', this._onBrowserRejection); } private _onMiniGameJsError = (msg: unknown): void => { ClientErrorReporter.report('MiniGame_onError', msg); }; private _onMiniGameRejection = (res: unknown): void => { const reason = res && typeof res === 'object' && 'reason' in res ? (res as { reason?: unknown }).reason : res; ClientErrorReporter.report('MiniGame_unhandledRejection', reason); }; private _onBrowserError = (ev: ErrorEvent): void => { const msg = ev.message || String(ev.error ?? 'error'); ClientErrorReporter.report('Browser_onError', msg); }; private _onBrowserRejection = (ev: PromiseRejectionEvent): void => { ClientErrorReporter.report('Browser_unhandledRejection', ev.reason); }; private resolveLoadingRefs(): void { if (!this.Loading) { console.log('[GameLaunch] Loading 节点未绑定'); } else { const progressNode = this.Loading.getChildByName('progress'); if (!progressNode) { console.log('[GameLaunch] Loading/progress 子节点缺失'); } else { this._loadingProgressSprite = progressNode.getComponent(Sprite); if (!this._loadingProgressSprite) { console.log('[GameLaunch] progress 节点缺少 Sprite 组件'); } } } if (!this.loadingUI) { console.log('[GameLaunch] loadingUI 节点未绑定'); return; } const labelNode = this.loadingUI.find('1/Label'); if (!labelNode) { console.log('[GameLaunch] loadingUI/1/Label 缺失'); } else { this._loadingPercentLabel = labelNode.getComponent(Label); if (!this._loadingPercentLabel) { console.log('[GameLaunch] loadingUI Label 缺少 Label 组件'); } } const barNode = this.loadingUI.find('1/progress'); if (!barNode) { console.log('[GameLaunch] loadingUI/1/progress 缺失'); } else { this._loadingBarSprite = barNode.getComponent(Sprite); if (!this._loadingBarSprite) { console.log('[GameLaunch] loadingUI progress 缺少 Sprite 组件'); } } } /**绑定场景相机并初始化 UI、音频 */ private initCameraAndShell(): boolean { const scene = director.getScene(); if (!scene) { console.error('[GameLaunch] director.getScene() 为空,无法初始化 UI'); return false; } const uiCameraNode = scene.find('UICamera'); if (!uiCameraNode) { console.error('[GameLaunch] 场景缺少 UICamera 节点'); return false; } const uiCamera = uiCameraNode.getComponent(Camera); if (!uiCamera) { console.error('[GameLaunch] UICamera 缺少 Camera 组件'); return false; } gg.ui.init(UIConfigs, uiCamera, false); gg.ui.setBattlePauseHook(this.onBattleUiPause.bind(this)); gg.audio.init({ bundle: GBundle.Audio, path: GAudioPath.SfxUi, clickEffname: '点击按钮' }); return true; } private onBootTitle(text: string): void { if (!this.title) { console.log('[GameLaunch] title Label 未绑定,无法更新 Boot 标题'); return; } this.title.string = text; } private onBootProgress(delta: number): void { this._progress += delta; } private onBootAssetsReady(): void { this.onBootTitle('资源加载完成'); this._firstResReady = true; this.tryEnterGame(); } private onSdkAndDataReady(): void { this._sdkAndDataReady = true; this.tryEnterGame(); } private onBootFailed(err: unknown): void { console.error('[GameLaunch] boot', String(err)); } private onBattleUiPause(uiId: number, open: boolean): void { const token = `ui:${uiId}`; if (open) { BattleUiPauseGate.acquire(token); } else { BattleUiPauseGate.release(token); } } private getLoadingUI(): Node | null { return this.loadingUI; } private getBootLoading(): Node | null { return this.Loading; } private isRuntimePhase(): boolean { return this._enteredGame; } private onLoadingWatchdog(): void { gg.game?.onLoadingWatchdogTimeout?.(); } }