From 0c590d7c1268c65a77405fa657f3646641ea81cc Mon Sep 17 00:00:00 2001 From: yhh <359807859@qq.com> Date: Wed, 3 Dec 2025 16:20:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(platform-web):=20=E6=B7=BB=E5=8A=A0Browser?= =?UTF-8?q?Runtime=E5=92=8C=E8=B5=84=E4=BA=A7=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/platform-web/module.json | 19 ++ packages/platform-web/package.json | 29 +-- packages/platform-web/rollup.config.js | 36 ++- .../platform-web/rollup.runtime.config.js | 51 ---- .../platform-web/src/BrowserAssetReader.ts | 129 ++++++++++ packages/platform-web/src/BrowserRuntime.ts | 239 ++++++++++++++++++ packages/platform-web/src/index.ts | 48 +++- packages/platform-web/src/runtime.ts | 204 --------------- 8 files changed, 470 insertions(+), 285 deletions(-) create mode 100644 packages/platform-web/module.json delete mode 100644 packages/platform-web/rollup.runtime.config.js create mode 100644 packages/platform-web/src/BrowserAssetReader.ts create mode 100644 packages/platform-web/src/BrowserRuntime.ts delete mode 100644 packages/platform-web/src/runtime.ts diff --git a/packages/platform-web/module.json b/packages/platform-web/module.json new file mode 100644 index 00000000..23ee87ea --- /dev/null +++ b/packages/platform-web/module.json @@ -0,0 +1,19 @@ +{ + "id": "platform-web", + "name": "@esengine/platform-web", + "displayName": "Platform Web", + "description": "Web platform runtime | Web 平台运行时", + "version": "1.0.0", + "category": "Platform", + "icon": "Globe", + "tags": ["platform", "web", "browser", "runtime"], + "isCore": true, + "defaultEnabled": true, + "isEngineModule": true, + "canContainContent": false, + "platforms": ["web"], + "dependencies": ["core", "runtime-core"], + "exports": {}, + "outputPath": "dist/index.mjs", + "requiresWasm": false +} diff --git a/packages/platform-web/package.json b/packages/platform-web/package.json index f799ecd7..be7bf2e6 100644 --- a/packages/platform-web/package.json +++ b/packages/platform-web/package.json @@ -1,7 +1,8 @@ { "name": "@esengine/platform-web", "version": "1.0.0", - "description": "Web/H5 平台适配器", + "description": "Web/H5 Platform Adapter - Browser runtime and asset loading", + "type": "module", "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", @@ -10,19 +11,13 @@ "import": "./dist/index.mjs", "require": "./dist/index.js", "types": "./dist/index.d.ts" - }, - "./runtime": { - "default": "./dist/runtime.browser.js" } }, - "unpkg": "dist/runtime.browser.js", - "jsdelivr": "dist/runtime.browser.js", "files": [ "dist" ], "scripts": { - "build": "rollup -c && rollup -c rollup.runtime.config.js", - "build:runtime": "rollup -c rollup.runtime.config.js", + "build": "rollup -c", "build:npm": "npm run build", "clean": "rimraf dist", "type-check": "npx tsc --noEmit", @@ -33,28 +28,20 @@ "web", "h5", "platform", - "adapter" + "runtime", + "browser" ], "author": "yhh", "license": "MIT", + "dependencies": { + "@esengine/runtime-core": "workspace:*" + }, "devDependencies": { "@esengine/ecs-framework": "workspace:*", "@esengine/asset-system": "workspace:*", "@esengine/platform-common": "workspace:*", - "@esengine/audio": "workspace:*", - "@esengine/behavior-tree": "workspace:*", - "@esengine/camera": "workspace:*", - "@esengine/ecs-engine-bindgen": "workspace:*", - "@esengine/engine-core": "workspace:*", - "@esengine/physics-rapier2d": "workspace:*", - "@esengine/runtime-core": "workspace:*", - "@esengine/sprite": "workspace:*", - "@esengine/tilemap": "workspace:*", - "@esengine/ui": "workspace:*", - "@rollup/plugin-alias": "^6.0.0", "@rollup/plugin-commonjs": "^28.0.3", "@rollup/plugin-node-resolve": "^16.0.1", - "@rollup/plugin-replace": "^6.0.3", "@rollup/plugin-typescript": "^11.1.6", "rimraf": "^5.0.0", "rollup": "^4.42.0", diff --git a/packages/platform-web/rollup.config.js b/packages/platform-web/rollup.config.js index 9e6b400c..f2e934e2 100644 --- a/packages/platform-web/rollup.config.js +++ b/packages/platform-web/rollup.config.js @@ -3,19 +3,41 @@ import commonjs from '@rollup/plugin-commonjs'; import typescript from '@rollup/plugin-typescript'; import dts from 'rollup-plugin-dts'; -// 所有 @esengine/* 包设为 external,避免多实例问题 +/** + * Platform-web Rollup Configuration + * + * Builds: + * 1. ESM + CJS bundles for editor usage + * 2. TypeScript declarations + * + * All @esengine/* packages are external to avoid bundling. + * Game builds use import maps to resolve modules at runtime. + */ + const external = [ - '@esengine/platform-common', '@esengine/ecs-framework', + '@esengine/runtime-core', + '@esengine/platform-common', + '@esengine/asset-system', '@esengine/ecs-components', + '@esengine/ecs-engine-bindgen', '@esengine/tilemap', '@esengine/ui', '@esengine/behavior-tree', - '@esengine/ecs-engine-bindgen', - '@esengine/asset-system', + // Editor packages (should never be in runtime) + '@esengine/editor-core', + '@esengine/ui-editor', + '@esengine/tilemap-editor', + '@esengine/behavior-tree-editor', + '@esengine/blueprint-editor', + '@esengine/physics-rapier2d-editor', + // React (editor only) + 'react', + 'react-dom', ]; export default [ + // Main bundle (ESM + CJS) { input: 'src/index.ts', output: [ @@ -32,7 +54,10 @@ export default [ ], external, plugins: [ - resolve(), + resolve({ + browser: true, + preferBuiltins: false + }), commonjs(), typescript({ tsconfig: './tsconfig.json', @@ -40,6 +65,7 @@ export default [ }) ] }, + // TypeScript declarations { input: 'src/index.ts', output: { diff --git a/packages/platform-web/rollup.runtime.config.js b/packages/platform-web/rollup.runtime.config.js deleted file mode 100644 index 0188dfdd..00000000 --- a/packages/platform-web/rollup.runtime.config.js +++ /dev/null @@ -1,51 +0,0 @@ -import resolve from '@rollup/plugin-node-resolve'; -import commonjs from '@rollup/plugin-commonjs'; -import typescript from '@rollup/plugin-typescript'; -import replace from '@rollup/plugin-replace'; - -export default { - input: 'src/runtime.ts', - output: { - file: 'dist/runtime.browser.js', - format: 'iife', - name: 'ECSRuntime', - sourcemap: true, - globals: {}, - exports: 'default' - }, - // Exclude editor-only packages that contain React - external: [ - 'react', - 'react-dom', - '@esengine/editor-core', - // Editor packages should never be imported in runtime - '@esengine/ui-editor', - '@esengine/tilemap-editor', - '@esengine/behavior-tree-editor', - '@esengine/blueprint-editor', - '@esengine/physics-rapier2d-editor' - ], - plugins: [ - // Replace process.env.NODE_ENV for browser - replace({ - preventAssignment: true, - 'process.env.NODE_ENV': JSON.stringify('production') - }), - resolve({ - browser: true, - preferBuiltins: false, - exportConditions: ['import', 'default'] - }), - commonjs(), - typescript({ - tsconfig: './tsconfig.json', - declaration: false, - sourceMap: true - }) - ], - onwarn(warning, warn) { - // Suppress "Unresolved dependencies" warnings for external packages - if (warning.code === 'UNRESOLVED_IMPORT') return; - warn(warning); - } -}; diff --git a/packages/platform-web/src/BrowserAssetReader.ts b/packages/platform-web/src/BrowserAssetReader.ts new file mode 100644 index 00000000..ca2e229d --- /dev/null +++ b/packages/platform-web/src/BrowserAssetReader.ts @@ -0,0 +1,129 @@ +/** + * Browser Asset Reader + * 浏览器资产读取器 + * + * Implements IAssetReader interface for browser environment. + * Uses fetch API to load assets from web server. + */ + +/** + * Browser Asset Reader for loading assets via fetch API + * 通过 fetch API 加载资产的浏览器读取器 + */ +export class BrowserAssetReader { + private _baseUrl: string; + private _audioContext: AudioContext | null = null; + + constructor(baseUrl: string = '/assets') { + this._baseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; + } + + /** + * Resolve asset path to URL + * 将资产路径解析为 URL + */ + private _resolveUrl(absolutePath: string): string { + // Handle absolute Windows paths (e.g., F:\TowerECS\assets\...) + if (/^[A-Za-z]:[\\/]/.test(absolutePath)) { + const normalized = absolutePath.replace(/\\/g, '/'); + const assetsIndex = normalized.toLowerCase().indexOf('/assets/'); + if (assetsIndex >= 0) { + return `${this._baseUrl}${normalized.substring(assetsIndex + 7)}`; + } + const filename = normalized.split('/').pop(); + return `${this._baseUrl}/${filename}`; + } + + // Handle relative paths + if (absolutePath.startsWith('./') || absolutePath.startsWith('../')) { + return absolutePath; + } + if (absolutePath.startsWith('/assets/')) { + return absolutePath; + } + if (absolutePath.startsWith('assets/')) { + return `/${absolutePath}`; + } + return `${this._baseUrl}/${absolutePath}`; + } + + /** + * Read text file + * 读取文本文件 + */ + async readText(absolutePath: string): Promise { + const url = this._resolveUrl(absolutePath); + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to load text: ${url} (${response.status})`); + } + return response.text(); + } + + /** + * Read binary file + * 读取二进制文件 + */ + async readBinary(absolutePath: string): Promise { + const url = this._resolveUrl(absolutePath); + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to load binary: ${url} (${response.status})`); + } + return response.arrayBuffer(); + } + + /** + * Load image + * 加载图片 + */ + async loadImage(absolutePath: string): Promise { + const url = this._resolveUrl(absolutePath); + return new Promise((resolve, reject) => { + const img = new Image(); + img.crossOrigin = 'anonymous'; + img.onload = () => resolve(img); + img.onerror = () => reject(new Error(`Failed to load image: ${url}`)); + img.src = url; + }); + } + + /** + * Load audio + * 加载音频 + */ + async loadAudio(absolutePath: string): Promise { + if (!this._audioContext) { + this._audioContext = new AudioContext(); + } + const url = this._resolveUrl(absolutePath); + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to load audio: ${url} (${response.status})`); + } + const arrayBuffer = await response.arrayBuffer(); + return this._audioContext.decodeAudioData(arrayBuffer); + } + + /** + * Check if file exists + * 检查文件是否存在 + */ + async exists(absolutePath: string): Promise { + const url = this._resolveUrl(absolutePath); + try { + const response = await fetch(url, { method: 'HEAD' }); + return response.ok; + } catch { + return false; + } + } + + /** + * Get base URL + * 获取基础 URL + */ + get baseUrl(): string { + return this._baseUrl; + } +} diff --git a/packages/platform-web/src/BrowserRuntime.ts b/packages/platform-web/src/BrowserRuntime.ts new file mode 100644 index 00000000..9771f421 --- /dev/null +++ b/packages/platform-web/src/BrowserRuntime.ts @@ -0,0 +1,239 @@ +/** + * Browser Runtime + * 浏览器运行时 + * + * Lightweight runtime for web game builds. + * Uses dynamic plugin loading via import maps. + * + * 轻量级 Web 游戏运行时。 + * 通过 import maps 动态加载插件。 + */ + +import { Core } from '@esengine/ecs-framework'; +import { + GameRuntime, + createGameRuntime, + BrowserPlatformAdapter, + runtimePluginManager, + BrowserFileSystemService, + type IPlugin +} from '@esengine/runtime-core'; +import type { IAssetManager } from '@esengine/asset-system'; +import { BrowserAssetReader } from './BrowserAssetReader'; + +/** + * Runtime configuration + * 运行时配置 + */ +export interface RuntimeConfig { + /** Canvas element ID */ + canvasId: string; + /** Canvas width (defaults to window.innerWidth) */ + width?: number; + /** Canvas height (defaults to window.innerHeight) */ + height?: number; + /** Asset catalog file URL (defaults to '/asset-catalog.json') */ + assetCatalogUrl?: string; + /** Asset base URL (defaults to '/assets') */ + assetBaseUrl?: string; +} + +/** + * Browser Runtime + * 浏览器运行时 + * + * Main entry point for running games in browser. + * Supports dynamic plugin registration. + */ +export class BrowserRuntime { + private _runtime: GameRuntime | null = null; + private _canvasId: string; + private _width: number; + private _height: number; + private _assetCatalogUrl: string; + private _assetBaseUrl: string; + private _fileSystem: BrowserFileSystemService | null = null; + private _assetReader: BrowserAssetReader | null = null; + private _initialized = false; + + constructor(config: RuntimeConfig) { + this._canvasId = config.canvasId; + this._width = config.width ?? window.innerWidth; + this._height = config.height ?? window.innerHeight; + this._assetCatalogUrl = config.assetCatalogUrl ?? '/asset-catalog.json'; + this._assetBaseUrl = config.assetBaseUrl ?? '/assets'; + } + + /** + * Register a plugin dynamically + * 动态注册插件 + * + * Call this before initialize() to register plugins. + */ + registerPlugin(plugin: IPlugin): void { + if (plugin) { + runtimePluginManager.register(plugin); + runtimePluginManager.enable(plugin.manifest.id); + console.log(`[Runtime] Registered plugin: ${plugin.manifest.id}`); + } + } + + /** + * Register multiple plugins + * 注册多个插件 + */ + registerPlugins(plugins: IPlugin[]): void { + for (const plugin of plugins) { + this.registerPlugin(plugin); + } + } + + /** + * Initialize the runtime + * 初始化运行时 + * + * @param wasmModule - Optional WASM module (from es_engine.js) + */ + async initialize(wasmModule?: unknown): Promise { + if (this._initialized) { + console.warn('[Runtime] Already initialized'); + return; + } + + // Initialize browser file system service + this._fileSystem = new BrowserFileSystemService({ + baseUrl: this._assetBaseUrl, + catalogUrl: this._assetCatalogUrl, + enableCache: true + }); + await this._fileSystem.initialize(); + + // Initialize asset reader + this._assetReader = new BrowserAssetReader(this._assetBaseUrl); + + // Create browser platform adapter + const platform = new BrowserPlatformAdapter({ + wasmModule: wasmModule ?? undefined + }); + + // Create game runtime + this._runtime = createGameRuntime({ + platform, + canvasId: this._canvasId, + width: this._width, + height: this._height, + autoStartRenderLoop: false + }); + + await this._runtime.initialize(); + + // Register file system service + const IFileSystemServiceKey = Symbol.for('IFileSystemService'); + if (!Core.services.isRegistered(IFileSystemServiceKey)) { + Core.services.registerInstance(IFileSystemServiceKey, this._fileSystem); + } + + // Set asset reader for AssetManager + if (this._runtime.assetManager && this._assetReader) { + this._runtime.assetManager.setReader(this._assetReader); + } + + // Browser-specific settings (no editor UI) + this._runtime.setShowGrid(false); + this._runtime.setShowGizmos(false); + + this._initialized = true; + console.log('[Runtime] Initialized'); + } + + /** + * Load a scene from URL + * 从 URL 加载场景 + */ + async loadScene(sceneUrl: string): Promise { + if (!this._runtime) { + throw new Error('Runtime not initialized. Call initialize() first.'); + } + await this._runtime.loadSceneFromUrl(sceneUrl); + } + + /** + * Start the game loop + * 启动游戏循环 + */ + start(): void { + this._runtime?.start(); + } + + /** + * Stop the game loop + * 停止游戏循环 + */ + stop(): void { + this._runtime?.stop(); + } + + /** + * Handle window resize + * 处理窗口大小变化 + */ + handleResize(width: number, height: number): void { + this._runtime?.resize(width, height); + } + + /** + * Get the underlying GameRuntime + * 获取底层 GameRuntime + */ + get gameRuntime(): GameRuntime | null { + return this._runtime; + } + + /** + * Get the asset manager + * 获取资产管理器 + * + * @returns The asset manager instance, or null if not initialized + */ + get assetManager(): IAssetManager | null { + return this._runtime?.assetManager ?? null; + } + + /** + * Check if runtime is initialized + * 检查运行时是否已初始化 + */ + get isInitialized(): boolean { + return this._initialized; + } +} + +/** + * Create a browser runtime instance + * 创建浏览器运行时实例 + */ +export function create(config: RuntimeConfig): BrowserRuntime { + return new BrowserRuntime(config); +} + +/** + * Default export for convenient usage + * 默认导出,便于使用 + * + * Usage in game HTML: + * ```js + * const ECSRuntime = (await import('@esengine/platform-web')).default; + * const runtime = ECSRuntime.create({ canvasId: 'game-canvas' }); + * ``` + */ +export default { + create, + BrowserRuntime, + BrowserAssetReader, + // Re-export useful types from dependencies + Core, + GameRuntime, + createGameRuntime, + BrowserPlatformAdapter, + runtimePluginManager +}; diff --git a/packages/platform-web/src/index.ts b/packages/platform-web/src/index.ts index 83f8739f..cc200553 100644 --- a/packages/platform-web/src/index.ts +++ b/packages/platform-web/src/index.ts @@ -1,29 +1,69 @@ /** * @esengine/platform-web * - * Web/H5 平台适配器 - 仅包含平台差异代码 - * 通用运行时逻辑在 @esengine/runtime-core + * Web/H5 Platform Adapter + * Web/H5 平台适配器 + * + * Provides browser-specific implementations: + * - BrowserRuntime: Main entry point for game builds + * - BrowserAssetReader: Asset loading via fetch API + * - Web subsystems: Canvas, Input, Storage, WASM * * @packageDocumentation */ -// Web 平台子系统 +// ============================================ +// Runtime (main entry for game builds) +// ============================================ +export { + BrowserRuntime, + create, + type RuntimeConfig +} from './BrowserRuntime'; + +// Default export for convenient usage in game builds +export { default } from './BrowserRuntime'; + +// Asset reader +export { BrowserAssetReader } from './BrowserAssetReader'; + +// ============================================ +// Web Platform Subsystems +// ============================================ export { WebCanvasSubsystem } from './subsystems/WebCanvasSubsystem'; export { WebInputSubsystem } from './subsystems/WebInputSubsystem'; export { WebStorageSubsystem } from './subsystems/WebStorageSubsystem'; export { WebWASMSubsystem } from './subsystems/WebWASMSubsystem'; -// Web 特定系统 +// ============================================ +// Web-specific Systems +// ============================================ export { Canvas2DRenderSystem } from './systems/Canvas2DRenderSystem'; +// ============================================ +// Platform Detection Utilities +// ============================================ + +/** + * Check if running in web browser + * 检查是否在浏览器中运行 + */ export function isWebPlatform(): boolean { return typeof window !== 'undefined' && typeof document !== 'undefined'; } +/** + * Get canvas element by ID + * 根据 ID 获取 canvas 元素 + */ export function getWebCanvas(canvasId: string): HTMLCanvasElement | null { return document.getElementById(canvasId) as HTMLCanvasElement | null; } +/** + * Create a new canvas element + * 创建新的 canvas 元素 + */ export function createWebCanvas(width: number, height: number): HTMLCanvasElement { const canvas = document.createElement('canvas'); canvas.width = width; diff --git a/packages/platform-web/src/runtime.ts b/packages/platform-web/src/runtime.ts deleted file mode 100644 index b3d162bc..00000000 --- a/packages/platform-web/src/runtime.ts +++ /dev/null @@ -1,204 +0,0 @@ -/** - * Browser Runtime Entry Point - * 浏览器运行时入口 - * - * 使用统一的 GameRuntime 架构,静态导入所有插件 - * Uses the unified GameRuntime architecture with static plugin imports - */ - -import { Core } from '@esengine/ecs-framework'; -import { - GameRuntime, - createGameRuntime, - BrowserPlatformAdapter, - runtimePluginManager, - BrowserFileSystemService -} from '@esengine/runtime-core'; - -// 静态导入所有运行时插件(与编辑器保持一致) -// Static import all runtime plugins (consistent with editor) -import { EnginePlugin } from '@esengine/engine-core'; -import { CameraPlugin } from '@esengine/camera'; -import { SpritePlugin } from '@esengine/sprite'; -import { AudioPlugin } from '@esengine/audio'; -import { UIPlugin } from '@esengine/ui'; -import { TilemapPlugin } from '@esengine/tilemap'; -import { BehaviorTreePlugin } from '@esengine/behavior-tree'; -// 使用 runtime 子路径导入,包含 WASM 依赖 -import { PhysicsPlugin } from '@esengine/physics-rapier2d/runtime'; - -// 预注册所有插件(在 GameRuntime 初始化前) -// Pre-register all plugins (before GameRuntime initialization) -const ALL_PLUGINS = [ - EnginePlugin, - CameraPlugin, - SpritePlugin, - AudioPlugin, - UIPlugin, - TilemapPlugin, - BehaviorTreePlugin, - PhysicsPlugin, -]; - -// 注册并启用所有插件(浏览器运行时默认启用所有功能) -for (const plugin of ALL_PLUGINS) { - if (plugin) { - runtimePluginManager.register(plugin); - // 确保所有插件都启用(覆盖 enabledByDefault: false) - runtimePluginManager.enable(plugin.descriptor.id); - } -} - -export interface RuntimeConfig { - canvasId: string; - width?: number; - height?: number; - /** 项目配置文件 URL / Project config file URL */ - projectConfigUrl?: string; - /** 资产目录文件 URL / Asset catalog file URL */ - assetCatalogUrl?: string; - /** 资产基础 URL / Asset base URL */ - assetBaseUrl?: string; -} - -/** - * 编辑器项目配置文件格式 - * Editor project config file format (ecs-editor.config.json) - */ -interface EditorProjectConfig { - projectType?: string; - plugins?: { - enabledPlugins: string[]; - }; - [key: string]: any; -} - -/** - * Browser Runtime Wrapper - * 浏览器运行时包装器 - */ -class BrowserRuntime { - private _runtime: GameRuntime | null = null; - private _canvasId: string; - private _configUrl?: string; - private _assetCatalogUrl?: string; - private _assetBaseUrl?: string; - private _fileSystem: BrowserFileSystemService | null = null; - - constructor(config: RuntimeConfig) { - this._canvasId = config.canvasId; - this._configUrl = config.projectConfigUrl; - this._assetCatalogUrl = config.assetCatalogUrl ?? '/asset-catalog.json'; - this._assetBaseUrl = config.assetBaseUrl ?? '/assets'; - } - - /** - * 从配置文件 URL 加载插件配置 - */ - private async _loadConfigFromUrl(): Promise { - if (!this._configUrl) return; - - try { - const response = await fetch(this._configUrl); - if (!response.ok) { - console.warn(`[BrowserRuntime] Failed to load config from ${this._configUrl}: ${response.status}`); - return; - } - - const editorConfig: EditorProjectConfig = await response.json(); - - // 如果有插件配置,应用到 runtimePluginManager - if (editorConfig.plugins?.enabledPlugins) { - runtimePluginManager.loadConfig({ enabledPlugins: editorConfig.plugins.enabledPlugins }); - console.log('[BrowserRuntime] Loaded plugin config:', editorConfig.plugins.enabledPlugins); - } - } catch (error) { - console.warn('[BrowserRuntime] Error loading config file:', error); - } - } - - async initialize(wasmModule: any): Promise { - // 从配置文件加载插件配置(如果指定了 URL) - await this._loadConfigFromUrl(); - - // 初始化浏览器文件系统服务(用于资产加载) - // Initialize browser file system service (for asset loading) - this._fileSystem = new BrowserFileSystemService({ - baseUrl: this._assetBaseUrl, - catalogUrl: this._assetCatalogUrl, - enableCache: true - }); - await this._fileSystem.initialize(); - - // 创建浏览器平台适配器 - const platform = new BrowserPlatformAdapter({ - wasmModule: wasmModule - }); - - // 创建统一运行时 - // 插件已经预注册了,GameRuntime 会检测到并跳过动态加载 - this._runtime = createGameRuntime({ - platform, - canvasId: this._canvasId, - width: window.innerWidth, - height: window.innerHeight, - autoStartRenderLoop: false - }); - - await this._runtime.initialize(); - - // 注册文件系统服务到 Core.services(必须在 GameRuntime.initialize 之后,因为 Core 在那时才创建) - // Register file system service to Core.services (must be after GameRuntime.initialize as Core is created there) - const IFileSystemServiceKey = Symbol.for('IFileSystemService'); - if (!Core.services.isRegistered(IFileSystemServiceKey)) { - Core.services.registerInstance(IFileSystemServiceKey, this._fileSystem); - } - - // 设置浏览器特定配置 - this._runtime.setShowGrid(false); - this._runtime.setShowGizmos(false); - } - - async loadScene(sceneUrl: string): Promise { - if (!this._runtime) { - throw new Error('Runtime not initialized'); - } - await this._runtime.loadSceneFromUrl(sceneUrl); - } - - start(): void { - if (!this._runtime) return; - this._runtime.start(); - } - - stop(): void { - if (!this._runtime) return; - this._runtime.stop(); - } - - handleResize(width: number, height: number): void { - if (!this._runtime) return; - this._runtime.resize(width, height); - } - - get assetManager() { - return this._runtime?.assetManager ?? null; - } - - get engineIntegration() { - return this._runtime?.engineIntegration ?? null; - } - - get gameRuntime(): GameRuntime | null { - return this._runtime; - } -} - -export default { - create: (config: RuntimeConfig) => new BrowserRuntime(config), - BrowserRuntime, - Core, - GameRuntime, - createGameRuntime, - BrowserPlatformAdapter -};