Feature/editor optimization (#251)

* refactor: 编辑器/运行时架构拆分与构建系统升级

* feat(core): 层级系统重构与UI变换矩阵修复

* refactor: 移除 ecs-components 聚合包并修复跨包组件查找问题

* fix(physics): 修复跨包组件类引用问题

* feat: 统一运行时架构与浏览器运行支持

* feat(asset): 实现浏览器运行时资产加载系统

* fix: 修复文档、CodeQL安全问题和CI类型检查错误

* fix: 修复文档、CodeQL安全问题和CI类型检查错误

* fix: 修复文档、CodeQL安全问题、CI类型检查和测试错误

* test: 补齐核心模块测试用例,修复CI构建配置

* fix: 修复测试用例中的类型错误和断言问题

* fix: 修复 turbo build:npm 任务的依赖顺序问题

* fix: 修复 CI 构建错误并优化构建性能
This commit is contained in:
YHH
2025-12-01 22:28:51 +08:00
committed by GitHub
parent 189714c727
commit b42a7b4e43
468 changed files with 18301 additions and 9075 deletions

View File

@@ -1,123 +1,196 @@
/**
* Browser Runtime Entry Point
* 浏览器运行时入口
*
* 使用统一的 GameRuntime 架构,静态导入所有插件
* Uses the unified GameRuntime architecture with static plugin imports
*/
import { Core, Scene, SceneSerializer } from '@esengine/ecs-framework';
import { EngineBridge } from '@esengine/ecs-engine-bindgen';
import { TransformComponent, SpriteComponent, SpriteAnimatorComponent, CameraComponent } from '@esengine/ecs-components';
import { AssetManager, EngineIntegration } from '@esengine/asset-system';
import { initializeRuntime, createRuntimeSystems, type RuntimeSystems } from './RuntimeSystems';
import { Core } from '@esengine/ecs-framework';
import {
GameRuntime,
createGameRuntime,
BrowserPlatformAdapter,
runtimePluginManager,
BrowserFileSystemService
} from '@esengine/runtime-core';
interface RuntimeConfig {
// 静态导入所有运行时插件(与编辑器保持一致)
// 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 bridge: EngineBridge;
private systems: RuntimeSystems | null = null;
private animationId: number | null = null;
private assetManager: AssetManager;
private engineIntegration: EngineIntegration;
private canvasId: string;
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;
if (!Core.Instance) {
Core.create();
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<void> {
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);
}
if (!Core.scene) {
const runtimeScene = new Scene({ name: 'Runtime Scene' });
Core.setScene(runtimeScene);
}
this.bridge = new EngineBridge({
canvasId: config.canvasId,
width: config.width || window.innerWidth,
height: config.height || window.innerHeight
});
this.assetManager = new AssetManager();
this.engineIntegration = new EngineIntegration(this.assetManager, this.bridge);
}
async initialize(wasmModule: any): Promise<void> {
await this.bridge.initializeWithModule(wasmModule);
// 从配置文件加载插件配置(如果指定了 URL
await this._loadConfigFromUrl();
this.bridge.setPathResolver((path: string) => {
if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('/asset?')) {
return path;
}
return `/asset?path=${encodeURIComponent(path)}`;
// 初始化浏览器文件系统服务(用于资产加载)
// 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
});
this.bridge.setShowGrid(false);
this.bridge.setShowGizmos(false);
// 初始化模块系统
await initializeRuntime(Core);
// 创建运行时系统(传入 canvasId 用于 UI 输入绑定)
this.systems = createRuntimeSystems(Core.scene!, this.bridge, {
canvasId: this.canvasId
// 创建统一运行时
// 插件已经预注册了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<void> {
const response = await fetch(sceneUrl);
const sceneJson = await response.text();
if (!Core.scene) {
throw new Error('Core.scene not initialized');
if (!this._runtime) {
throw new Error('Runtime not initialized');
}
SceneSerializer.deserialize(Core.scene, sceneJson, {
strategy: 'replace',
preserveIds: true
});
await this._runtime.loadSceneFromUrl(sceneUrl);
}
start(): void {
if (this.animationId !== null) return;
let lastTime = performance.now();
const loop = () => {
const currentTime = performance.now();
const deltaTime = (currentTime - lastTime) / 1000;
lastTime = currentTime;
Core.update(deltaTime);
this.animationId = requestAnimationFrame(loop);
};
loop();
if (!this._runtime) return;
this._runtime.start();
}
stop(): void {
if (this.animationId !== null) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
if (!this._runtime) return;
this._runtime.stop();
}
handleResize(width: number, height: number): void {
this.bridge.resize(width, height);
if (!this._runtime) return;
this._runtime.resize(width, height);
}
getAssetManager(): AssetManager {
return this.assetManager;
get assetManager() {
return this._runtime?.assetManager ?? null;
}
getEngineIntegration(): EngineIntegration {
return this.engineIntegration;
get engineIntegration() {
return this._runtime?.engineIntegration ?? null;
}
getSystems(): RuntimeSystems | null {
return this.systems;
get gameRuntime(): GameRuntime | null {
return this._runtime;
}
}
@@ -125,8 +198,7 @@ export default {
create: (config: RuntimeConfig) => new BrowserRuntime(config),
BrowserRuntime,
Core,
TransformComponent,
SpriteComponent,
SpriteAnimatorComponent,
CameraComponent
GameRuntime,
createGameRuntime,
BrowserPlatformAdapter
};