diff --git a/packages/editor/editor-app/README.md b/packages/editor/editor-app/README.md index 039f3e1a..4973858e 100644 --- a/packages/editor/editor-app/README.md +++ b/packages/editor/editor-app/README.md @@ -24,15 +24,7 @@ cd esengine pnpm install ``` -### 2. Clone Physics Dependencies (Optional) - -If you need physics support, clone the rapier.js dependency: - -```bash -git clone https://github.com/esengine/rapier.js.git packages/thirdparty/rapier.js -``` - -### 3. Build Dependencies +### 2. Build Dependencies From the project root: @@ -40,7 +32,7 @@ From the project root: pnpm build:editor ``` -### 4. Run Editor +### 3. Run Editor ```bash cd packages/editor/editor-app diff --git a/packages/editor/editor-app/README_CN.md b/packages/editor/editor-app/README_CN.md index e362863d..5d99ef01 100644 --- a/packages/editor/editor-app/README_CN.md +++ b/packages/editor/editor-app/README_CN.md @@ -24,15 +24,7 @@ cd esengine pnpm install ``` -### 2. 克隆物理依赖(可选) - -如果需要物理引擎支持,需要克隆 rapier.js 依赖: - -```bash -git clone https://github.com/esengine/rapier.js.git packages/thirdparty/rapier.js -``` - -### 3. 构建依赖 +### 2. 构建依赖 在项目根目录执行: @@ -40,7 +32,7 @@ git clone https://github.com/esengine/rapier.js.git packages/thirdparty/rapier.j pnpm build:editor ``` -### 4. 启动编辑器 +### 3. 启动编辑器 ```bash cd packages/editor/editor-app diff --git a/packages/editor/plugins/behavior-tree-editor/package.json b/packages/editor/plugins/behavior-tree-editor/package.json index 3335bf56..c278359d 100644 --- a/packages/editor/plugins/behavior-tree-editor/package.json +++ b/packages/editor/plugins/behavior-tree-editor/package.json @@ -30,6 +30,7 @@ "devDependencies": { "@esengine/ecs-framework": "workspace:*", "@esengine/engine-core": "workspace:*", + "@esengine/asset-system": "workspace:*", "@esengine/editor-core": "workspace:*", "@esengine/editor-runtime": "workspace:*", "@esengine/node-editor": "workspace:*", diff --git a/packages/editor/plugins/behavior-tree-editor/src/BehaviorTreeRuntimeModule.ts b/packages/editor/plugins/behavior-tree-editor/src/BehaviorTreeRuntimeModule.ts new file mode 100644 index 00000000..ba35d092 --- /dev/null +++ b/packages/editor/plugins/behavior-tree-editor/src/BehaviorTreeRuntimeModule.ts @@ -0,0 +1,45 @@ +/** + * @zh ESEngine 行为树运行时模块 + * @en ESEngine Behavior Tree Runtime Module + * + * @zh 纯运行时模块,不依赖 asset-system。资产加载由编辑器在 install 时注册。 + * @en Pure runtime module, no asset-system dependency. Asset loading is registered by editor during install. + */ + +import type { IScene, ServiceContainer, IComponentRegistry } from '@esengine/ecs-framework'; +import type { IRuntimeModule, SystemContext } from '@esengine/engine-core'; + +import { + BehaviorTreeRuntimeComponent, + BehaviorTreeExecutionSystem, + BehaviorTreeAssetManager, + GlobalBlackboardService, + BehaviorTreeSystemToken +} from '@esengine/behavior-tree'; + +export class BehaviorTreeRuntimeModule implements IRuntimeModule { + registerComponents(registry: IComponentRegistry): void { + registry.register(BehaviorTreeRuntimeComponent); + } + + registerServices(services: ServiceContainer): void { + if (!services.isRegistered(GlobalBlackboardService)) { + services.registerSingleton(GlobalBlackboardService); + } + if (!services.isRegistered(BehaviorTreeAssetManager)) { + services.registerSingleton(BehaviorTreeAssetManager); + } + } + + createSystems(scene: IScene, context: SystemContext): void { + const ecsServices = (context as { ecsServices?: ServiceContainer }).ecsServices; + const behaviorTreeSystem = new BehaviorTreeExecutionSystem(ecsServices); + + if (context.isEditor) { + behaviorTreeSystem.enabled = false; + } + + scene.addSystem(behaviorTreeSystem); + context.services.register(BehaviorTreeSystemToken, behaviorTreeSystem); + } +} diff --git a/packages/editor/plugins/behavior-tree-editor/src/index.ts b/packages/editor/plugins/behavior-tree-editor/src/index.ts index 03cda4e9..575d41d8 100644 --- a/packages/editor/plugins/behavior-tree-editor/src/index.ts +++ b/packages/editor/plugins/behavior-tree-editor/src/index.ts @@ -30,8 +30,11 @@ import { LocaleService, } from '@esengine/editor-runtime'; -// Runtime imports from @esengine/behavior-tree package -import { BehaviorTreeRuntimeComponent, BehaviorTreeRuntimeModule } from '@esengine/behavior-tree'; +// Runtime imports +import { BehaviorTreeRuntimeComponent, BehaviorTreeAssetType } from '@esengine/behavior-tree'; +import { AssetManagerToken } from '@esengine/asset-system'; +import { BehaviorTreeRuntimeModule } from './BehaviorTreeRuntimeModule'; +import { BehaviorTreeLoader } from './runtime/BehaviorTreeLoader'; // Editor components and services import { BehaviorTreeService } from './services/BehaviorTreeService'; @@ -71,6 +74,10 @@ export class BehaviorTreeEditorModule implements IEditorModuleLoader { // 设置插件上下文 PluginContext.setServices(services); + // 注册行为树资产加载器到 AssetManager + // Register behavior tree asset loader to AssetManager + this.registerAssetLoader(); + // 注册服务 this.registerServices(services); @@ -92,6 +99,22 @@ export class BehaviorTreeEditorModule implements IEditorModuleLoader { logger.info('BehaviorTree editor module installed'); } + /** + * 注册行为树资产加载器 + * Register behavior tree asset loader + */ + private registerAssetLoader(): void { + try { + const assetManager = PluginAPI.resolve(AssetManagerToken); + if (assetManager) { + assetManager.registerLoader(BehaviorTreeAssetType, new BehaviorTreeLoader()); + logger.info('BehaviorTree asset loader registered'); + } + } catch (error) { + logger.warn('Failed to register asset loader:', error); + } + } + private registerAssetCreationMappings(services: ServiceContainer): void { try { const fileActionRegistry = services.resolve(IFileActionRegistry); @@ -376,7 +399,7 @@ export const BehaviorTreePlugin: IEditorPlugin = { editorModule: new BehaviorTreeEditorModule(), }; -export { BehaviorTreeRuntimeModule }; +// BehaviorTreeRuntimeModule is internal, not re-exported // Re-exports for editor functionality export { PluginContext } from './PluginContext'; diff --git a/packages/editor/plugins/behavior-tree-editor/src/runtime/BehaviorTreeLoader.ts b/packages/editor/plugins/behavior-tree-editor/src/runtime/BehaviorTreeLoader.ts new file mode 100644 index 00000000..de48d3d2 --- /dev/null +++ b/packages/editor/plugins/behavior-tree-editor/src/runtime/BehaviorTreeLoader.ts @@ -0,0 +1,61 @@ +/** + * @zh ESEngine 资产加载器 + * @en ESEngine asset loader + * @internal + */ + +import { Core } from '@esengine/ecs-framework'; +import { + BehaviorTreeAssetManager, + EditorToBehaviorTreeDataConverter, + BehaviorTreeAssetType, + type BehaviorTreeData +} from '@esengine/behavior-tree'; + +/** + * @zh 行为树资产接口 + * @en Behavior tree asset interface + * @internal + */ +export interface IBehaviorTreeAsset { + data: BehaviorTreeData; + path: string; +} + +/** + * @zh 行为树加载器 + * @en Behavior tree loader implementing IAssetLoader interface + * @internal + */ +export class BehaviorTreeLoader { + readonly supportedType = BehaviorTreeAssetType; + readonly supportedExtensions = ['.btree']; + readonly contentType = 'text' as const; + + async parse(content: { text?: string }, context: { metadata: { path: string } }): Promise { + if (!content.text) { + throw new Error('Behavior tree content is empty'); + } + + const treeData = EditorToBehaviorTreeDataConverter.fromEditorJSON(content.text); + const assetPath = context.metadata.path; + treeData.id = assetPath; + + const btAssetManager = Core.services.tryResolve(BehaviorTreeAssetManager); + if (btAssetManager) { + btAssetManager.loadAsset(treeData); + } + + return { + data: treeData, + path: assetPath + }; + } + + dispose(asset: IBehaviorTreeAsset): void { + const btAssetManager = Core.services.tryResolve(BehaviorTreeAssetManager); + if (btAssetManager && asset.data) { + btAssetManager.unloadAsset(asset.data.id); + } + } +} diff --git a/packages/editor/plugins/behavior-tree-editor/tsconfig.build.json b/packages/editor/plugins/behavior-tree-editor/tsconfig.build.json index ba0684d9..b9a7da06 100644 --- a/packages/editor/plugins/behavior-tree-editor/tsconfig.build.json +++ b/packages/editor/plugins/behavior-tree-editor/tsconfig.build.json @@ -1,23 +1,15 @@ { + "extends": "../../../../tsconfig.base.json", "compilerOptions": { - "target": "ES2020", - "module": "ES2020", - "moduleResolution": "bundler", - "lib": ["ES2020", "DOM"], - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, + "composite": false, "declaration": true, "declarationMap": true, + "outDir": "./dist", + "rootDir": "./src", "jsx": "react-jsx", - "resolveJsonModule": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true + "skipLibCheck": true, + "moduleResolution": "bundler" }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] + "exclude": ["node_modules", "dist", "**/*.test.ts"] } diff --git a/packages/editor/plugins/behavior-tree-editor/tsup.config.ts b/packages/editor/plugins/behavior-tree-editor/tsup.config.ts index 213cdda2..18b90c4a 100644 --- a/packages/editor/plugins/behavior-tree-editor/tsup.config.ts +++ b/packages/editor/plugins/behavior-tree-editor/tsup.config.ts @@ -2,6 +2,8 @@ import { defineConfig } from 'tsup'; import { editorOnlyPreset } from '../../../tools/build-config/src/presets/plugin-tsup'; export default defineConfig({ - ...editorOnlyPreset(), + ...editorOnlyPreset({ + external: ['@esengine/asset-system'] + }), tsconfig: 'tsconfig.build.json' }); diff --git a/packages/editor/plugins/fairygui-editor/tsconfig.build.json b/packages/editor/plugins/fairygui-editor/tsconfig.build.json new file mode 100644 index 00000000..fc4607b3 --- /dev/null +++ b/packages/editor/plugins/fairygui-editor/tsconfig.build.json @@ -0,0 +1,13 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "composite": false, + "declaration": true, + "declarationMap": true, + "outDir": "./dist", + "rootDir": "./src", + "jsx": "react-jsx" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +} diff --git a/packages/editor/plugins/fairygui-editor/tsconfig.json b/packages/editor/plugins/fairygui-editor/tsconfig.json index a7094023..f3428052 100644 --- a/packages/editor/plugins/fairygui-editor/tsconfig.json +++ b/packages/editor/plugins/fairygui-editor/tsconfig.json @@ -1,6 +1,7 @@ { - "extends": "../build-config/tsconfig.json", + "extends": "../../../../tsconfig.base.json", "compilerOptions": { + "composite": true, "outDir": "./dist", "rootDir": "./src", "jsx": "react-jsx", diff --git a/packages/editor/plugins/fairygui-editor/tsup.config.ts b/packages/editor/plugins/fairygui-editor/tsup.config.ts index 84c79146..a1161c3c 100644 --- a/packages/editor/plugins/fairygui-editor/tsup.config.ts +++ b/packages/editor/plugins/fairygui-editor/tsup.config.ts @@ -5,6 +5,7 @@ export default defineConfig({ format: ['esm'], dts: true, clean: true, + tsconfig: 'tsconfig.build.json', external: [ 'react', 'react-dom', diff --git a/packages/physics/physics-rapier2d/src/loaders/WeChatRapier2DLoader.ts b/packages/physics/physics-rapier2d/src/loaders/WeChatRapier2DLoader.ts index d66f701d..01e2f318 100644 --- a/packages/physics/physics-rapier2d/src/loaders/WeChatRapier2DLoader.ts +++ b/packages/physics/physics-rapier2d/src/loaders/WeChatRapier2DLoader.ts @@ -121,9 +121,9 @@ export class WeChatRapier2DLoader implements IWasmLibraryLoader { // 导入 Rapier2D 标准版 const RAPIER = await import('@esengine/rapier2d'); - // 初始化 WASM - 标准版需要提供 WASM 路径 - const wasmPath = this._config.minigame?.wasmPath || 'wasm/rapier_wasm2d_bg.wasm'; - await RAPIER.init(wasmPath); + // 初始化 WASM - WASM 已经作为 base64 嵌入到包中 + // Initialize WASM - WASM is embedded as base64 in the package + await RAPIER.init(); return RAPIER; } finally { diff --git a/packages/physics/physics-rapier2d/src/loaders/WebRapier2DLoader.ts b/packages/physics/physics-rapier2d/src/loaders/WebRapier2DLoader.ts index 693859e6..5bce582e 100644 --- a/packages/physics/physics-rapier2d/src/loaders/WebRapier2DLoader.ts +++ b/packages/physics/physics-rapier2d/src/loaders/WebRapier2DLoader.ts @@ -53,10 +53,9 @@ export class WebRapier2DLoader implements IWasmLibraryLoader { // 动态导入标准版 const RAPIER = await import('@esengine/rapier2d'); - // 初始化 WASM - 标准版需要提供 WASM 路径 - // 构建时 WASM 文件会被复制到 wasm/ 目录 - const wasmPath = this._config.web?.wasmPath || 'wasm/rapier_wasm2d_bg.wasm'; - await RAPIER.init(wasmPath); + // 初始化 WASM - WASM 已经作为 base64 嵌入到包中 + // Initialize WASM - WASM is embedded as base64 in the package + await RAPIER.init(); console.log(`[${this._config.name}] 加载完成`); return RAPIER; diff --git a/packages/physics/rapier2d/package.json b/packages/physics/rapier2d/package.json index 59346949..ad346a90 100644 --- a/packages/physics/rapier2d/package.json +++ b/packages/physics/rapier2d/package.json @@ -19,11 +19,13 @@ ], "scripts": { "gen:src": "node scripts/gen-src.mjs", - "build": "pnpm gen:src && tsup", - "clean": "rimraf dist src" + "build": "tsup", + "build:regen": "pnpm gen:src && tsup", + "clean": "rimraf dist" }, "license": "Apache-2.0", "devDependencies": { + "base64-js": "^1.5.1", "rimraf": "^5.0.0", "tsup": "^8.0.0", "typescript": "^5.0.0" diff --git a/packages/physics/rapier2d/src/control/character_controller.ts b/packages/physics/rapier2d/src/control/character_controller.ts index e27c32b8..8e536392 100644 --- a/packages/physics/rapier2d/src/control/character_controller.ts +++ b/packages/physics/rapier2d/src/control/character_controller.ts @@ -91,9 +91,8 @@ export class KinematicCharacterController { */ public setUp(vector: Vector) { let rawVect = VectorOps.intoRaw(vector); - const result = this.raw.setUp(rawVect); + return this.raw.setUp(rawVect); rawVect.free(); - return result; } public applyImpulsesToDynamicBodies(): boolean { diff --git a/packages/physics/rapier2d/src/control/ray_cast_vehicle_controller.ts b/packages/physics/rapier2d/src/control/ray_cast_vehicle_controller.ts index 3268d1de..fa65b3e3 100644 --- a/packages/physics/rapier2d/src/control/ray_cast_vehicle_controller.ts +++ b/packages/physics/rapier2d/src/control/ray_cast_vehicle_controller.ts @@ -28,9 +28,6 @@ export class DynamicRayCastVehicleController { bodies: RigidBodySet, colliders: ColliderSet, ) { - if (typeof RawDynamicRayCastVehicleController === 'undefined') { - throw new Error('DynamicRayCastVehicleController is not available in 2D mode'); - } this.raw = new RawDynamicRayCastVehicleController(chassis.handle); this.broadPhase = broadPhase; this.narrowPhase = narrowPhase; diff --git a/packages/physics/rapier2d/src/init.ts b/packages/physics/rapier2d/src/init.ts index c1f2b3f2..668bfe13 100644 --- a/packages/physics/rapier2d/src/init.ts +++ b/packages/physics/rapier2d/src/init.ts @@ -1,60 +1,12 @@ -/** - * RAPIER initialization module with dynamic WASM loading support. - * RAPIER 初始化模块,支持动态 WASM 加载。 - */ - +// @ts-ignore +import wasmBase64 from "../pkg/rapier_wasm2d_bg.wasm"; import wasmInit from "../pkg/rapier_wasm2d"; - -/** - * Input types for WASM initialization. - * WASM 初始化的输入类型。 - */ -export type InitInput = - | RequestInfo // URL string or Request object - | URL // URL object - | Response // Fetch Response object - | BufferSource // ArrayBuffer or TypedArray - | WebAssembly.Module; // Pre-compiled module - -let initialized = false; +import base64 from "base64-js"; /** * Initializes RAPIER. * Has to be called and awaited before using any library methods. - * - * 初始化 RAPIER。 - * 必须在使用任何库方法之前调用并等待。 - * - * @param input - WASM source (required). Can be URL, Response, ArrayBuffer, etc. - * WASM 源(必需)。可以是 URL、Response、ArrayBuffer 等。 - * - * @example - * // Load from URL | 从 URL 加载 - * await RAPIER.init('wasm/rapier_wasm2d_bg.wasm'); - * - * @example - * // Load from fetch response | 从 fetch 响应加载 - * const response = await fetch('wasm/rapier_wasm2d_bg.wasm'); - * await RAPIER.init(response); - * - * @example - * // Load from ArrayBuffer | 从 ArrayBuffer 加载 - * const buffer = await fetch('wasm/rapier_wasm2d_bg.wasm').then(r => r.arrayBuffer()); - * await RAPIER.init(buffer); */ -export async function init(input?: InitInput): Promise { - if (initialized) { - return; - } - - await wasmInit(input); - initialized = true; -} - -/** - * Check if RAPIER is already initialized. - * 检查 RAPIER 是否已初始化。 - */ -export function isInitialized(): boolean { - return initialized; +export async function init() { + await wasmInit(base64.toByteArray(wasmBase64 as unknown as string).buffer); } diff --git a/packages/physics/rapier2d/src/math.ts b/packages/physics/rapier2d/src/math.ts index 04e543cd..6f3e473d 100644 --- a/packages/physics/rapier2d/src/math.ts +++ b/packages/physics/rapier2d/src/math.ts @@ -28,7 +28,7 @@ export class VectorOps { } // FIXME: type ram: RawVector? - public static fromRaw(raw: RawVector): Vector | null { + public static fromRaw(raw: RawVector): Vector { if (!raw) return null; let res = VectorOps.new(raw.x, raw.y); @@ -56,7 +56,7 @@ export class RotationOps { return 0.0; } - public static fromRaw(raw: RawRotation): Rotation | null { + public static fromRaw(raw: RawRotation): Rotation { if (!raw) return null; let res = raw.angle; diff --git a/packages/physics/rapier2d/tsup.config.ts b/packages/physics/rapier2d/tsup.config.ts index 933ffe82..0f0bc799 100644 --- a/packages/physics/rapier2d/tsup.config.ts +++ b/packages/physics/rapier2d/tsup.config.ts @@ -7,4 +7,7 @@ export default defineConfig({ sourcemap: true, clean: true, external: ["../pkg/rapier_wasm2d.js"], + loader: { + ".wasm": "base64", + }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4dbc8164..b3182879 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -569,6 +569,9 @@ importers: specifier: workspace:* version: link:../../../framework/behavior-tree devDependencies: + '@esengine/asset-system': + specifier: workspace:* + version: link:../../../engine/asset-system '@esengine/build-config': specifier: workspace:* version: link:../../../tools/build-config @@ -1861,6 +1864,9 @@ importers: packages/physics/rapier2d: devDependencies: + base64-js: + specifier: ^1.5.1 + version: 1.5.1 rimraf: specifier: ^5.0.0 version: 5.0.10