You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
AdLike2048/assets/script/GameLaunch.ts

361 lines
12 KiB

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?.();
}
}