import { _decorator, BlockInputEvents, Button, Camera, Canvas, Component, director, Director, Label, Node, ProgressBar, } from 'cc'; import { AppKernel } from '../../app/AppKernel'; import { LaunchProgress, LaunchStateMachine } from './LaunchStateMachine'; import { LaunchDialog } from './states/LaunchState'; const { ccclass, property } = _decorator; @ccclass('LaunchController') export class LaunchController extends Component { @property(Canvas) canvas: Canvas | null = null; @property(Label) progressLabel: Label | null = null; @property(ProgressBar) progressBar: ProgressBar | null = null; @property(Node) dialogNode: Node | null = null; @property(Label) dialogLabel: Label | null = null; @property(Button) dialogConfirmButton: Button | null = null; @property(Button) dialogCancelButton: Button | null = null; private launchStateMachine: LaunchStateMachine | null = null; private dialogResolve: ((confirmed: boolean) => void) | null = null; protected onLoad(): void { director.addPersistRootNode(this.node); AppKernel.initialize(); this.initEvents(); this.launchStateMachine = new LaunchStateMachine({ kernel: AppKernel.Instance, error: null, progress: (value) => this.updateProgress(value), dialog: (message) => this.showDialog(message), }); } private initEvents(): void { director.on(Director.EVENT_AFTER_SCENE_LAUNCH, this.onChangeScene, this,); AppKernel.events.once('launch:completed', this.onLaunchCompleted, this); } private readonly onLaunchCompleted = (): void => { if (!this.node.isValid) return; this.node.active = false; if (director.isPersistRootNode(this.node)) { director.removePersistRootNode(this.node); } this.node.destroy(); }; protected async start(): Promise { await this.launchStateMachine?.run(); } protected onDestroy(): void { this.finishDialog(false); AppKernel.events.offTarget(this); director.off(Director.EVENT_AFTER_SCENE_LAUNCH, this.onChangeScene, this); } private readonly onChangeScene = (): void => { if (!this.node.isValid) return; const scene = director.getScene(); const lobbyCanvas = scene?.getComponentsInChildren(Canvas)?.find(item => item != this.canvas); this.canvas.enabled = false; if (lobbyCanvas) { this.node.setParent(lobbyCanvas.node, true); this.node.setSiblingIndex(lobbyCanvas.node.children.length - 1); } }; private updateProgress(value: LaunchProgress): void { if (value.title !== undefined && this.progressLabel) { this.progressLabel.string = value.title; } if (value.progress !== undefined && this.progressBar) { this.progressBar.progress = value.progress; } } private showDialog({ message }: LaunchDialog): Promise { if ( !this.dialogNode || !this.dialogLabel || !this.dialogConfirmButton || !this.dialogCancelButton ) { if (typeof globalThis.confirm === 'function') { return Promise.resolve(globalThis.confirm(message)); } console.warn('[LaunchController] dialog nodes are not configured'); return Promise.resolve(false); } this.finishDialog(false); this.dialogLabel.string = message; this.dialogNode.active = true; return new Promise((resolve) => { this.dialogResolve = resolve; this.dialogConfirmButton?.node.once( Button.EventType.CLICK, this.onDialogConfirm, this, ); this.dialogCancelButton?.node.once( Button.EventType.CLICK, this.onDialogCancel, this, ); }); } private readonly onDialogConfirm = (): void => { this.finishDialog(true); }; private readonly onDialogCancel = (): void => { this.finishDialog(false); }; private finishDialog(confirmed: boolean): void { this.dialogConfirmButton?.node.off( Button.EventType.CLICK, this.onDialogConfirm, this, ); this.dialogCancelButton?.node.off( Button.EventType.CLICK, this.onDialogCancel, this, ); if (this.dialogNode) this.dialogNode.active = false; const resolve = this.dialogResolve; this.dialogResolve = null; resolve?.(confirmed); } }