feat: 预制体系统与架构改进 (#303)

* feat(prefab): 实现预制体系统和编辑器 UX 改进

## 预制体系统
- 新增 PrefabSerializer: 预制体序列化/反序列化
- 新增 PrefabInstanceComponent: 追踪预制体实例来源和修改
- 新增 PrefabService: 预制体核心服务
- 新增 PrefabLoader: 预制体资产加载器
- 新增预制体命令: Create/Instantiate/Apply/Revert/BreakLink

## 预制体编辑模式
- 支持双击 .prefab 文件进入编辑模式
- 预制体编辑模式工具栏 (保存/退出)
- 预制体实例指示器和操作菜单

## 编辑器 UX 改进
- SceneHierarchy 快捷键: F2 重命名, Ctrl+D 复制, ↑↓ 导航
- 支持双击实体名称内联编辑
- 删除实体时显示子节点数量警告
- 右键菜单添加重命名/复制选项及快捷键提示
- 布局持久化和重置功能

## Bug 修复
- 修复 editor-runtime 组件类重复导致的 TransformComponent 不识别问题
- 修复 .prefab-name 样式覆盖导致预制体工具栏文字不可见
- 修复 Inspector 资源字段高度不正确问题

* feat(editor): 改进编辑器 UX 交互体验

- ContentBrowser: 加载动画 spinner、搜索高亮、改进空状态设计
- SceneHierarchy: 选中项自动滚动到视图、搜索清除按钮
- PropertyInspector: 输入框本地状态管理、Enter/Escape 键处理
- EntityInspector: 组件折叠状态持久化、属性搜索清除按钮
- Viewport: 变换操作实时数值显示
- 国际化: 添加相关文本 (en/zh)

* fix(build): 修复 Web 构建资产加载和编辑器 UX 改进

构建系统修复:
- 修复 asset-catalog.json 字段名不匹配 (entries vs assets)
- 修复 BrowserFileSystemService 支持两种目录格式
- 修复 bundle 策略检测逻辑 (空对象判断)
- 修复 module.json 中 assetExtensions 声明和类型推断

行为树修复:
- 修复 BehaviorTreeExecutionSystem 使用 loadAsset 替代 loadAssetByPath
- 修复 BehaviorTreeAssetType 常量与 module.json 类型名一致 (behavior-tree)

编辑器 UX 改进:
- 构建完成对话框添加"打开文件夹"按钮
- 构建完成对话框样式优化 (圆形图标背景、按钮布局)
- SceneHierarchy 响应式布局 (窄窗口自动隐藏 Type 列)
- SceneHierarchy 隐藏滚动条

错误追踪:
- 添加全局错误处理器写入日志文件 (%TEMP%/esengine-editor-crash.log)
- 添加 append_to_log Tauri 命令

* feat(render): 修复 UI 渲染和点击特效系统

## UI 渲染修复
- 修复 GUID 验证 bug,使用统一的 isValidGUID() 函数
- 修复 UI 渲染顺序随机问题,Rust 端使用 IndexMap 替代 HashMap
- Web 运行时添加 assetPathResolver 支持 GUID 解析
- UIInteractableComponent.blockEvents 默认值改为 false

## 点击特效系统
- 新增 ClickFxComponent 和 ClickFxSystem
- 支持在点击位置播放粒子效果
- 支持多种触发模式和粒子轮换

## Camera 系统重构
- CameraSystem 从 ecs-engine-bindgen 移至 camera 包
- 新增 CameraManager 统一管理相机

## 编辑器改进
- 改进属性面板 UI 交互
- 粒子编辑器面板优化
- Transform 命令系统

* feat(render): 实现 Sorting Layer 系统和 Overlay 渲染层

- 新增 SortingLayerManager 管理排序层级 (Background, Default, Foreground, UI, Overlay)
- 实现 ISortable 接口,统一 Sprite、UI、Particle 的排序属性
- 修复粒子 Overlay 层被 UI 遮挡问题:添加独立的 Overlay Pass 在 UI 之后渲染
- 更新粒子资产格式:从 sortingOrder 改为 sortingLayer + orderInLayer
- 更新粒子编辑器面板支持新的排序属性
- 优化 UI 渲染系统使用新的排序层级

* feat(ci): 集成 SignPath 代码签名服务

- 添加 SignPath 自动签名工作流(Windows)
- 配置 release-editor.yml 支持代码签名
- 将构建改为草稿模式,等待签名完成后发布
- 添加证书文件到 .gitignore 防止泄露

* fix(asset): 修复 Web 构建资产路径解析和全局单例移除

## 资产路径修复
- 修复 Tauri 本地服务器 `/asset?path=...` 路径解析,正确与 root 目录连接
- BrowserPathResolver 支持两种模式:
  - 'proxy': 使用 /asset?path=... 格式(编辑器 Run in Browser)
  - 'direct': 使用直接路径 /assets/path.png(独立 Web 构建)
- BrowserRuntime 使用 'direct' 模式,无需 Tauri 代理

## 架构改进 - 移除全局单例
- 移除 globalAssetManager 导出,改用 AssetManagerToken 依赖注入
- 移除 globalPathResolver 导出,改用 PathResolutionService
- 移除 globalPathResolutionService 导出
- ParticleUpdateSystem/ClickFxSystem 通过 setAssetManager() 注入依赖
- EngineService 使用 new AssetManager() 替代全局实例

## 新增服务
- PathResolutionService: 统一路径解析接口
- RuntimeModeService: 运行时模式查询服务
- SerializationContext: EntityRef 序列化上下文

## 其他改进
- 完善 ServiceToken 注释说明本地定义的意图
- 导出 BrowserPathResolveMode 类型

* fix(build): 添加 world-streaming composite 设置修复类型检查

* fix(build): 移除 world-streaming 引用避免 composite 冲突

* fix(build): 将 const enum 改为 enum 兼容 isolatedModules

* fix(build): 添加缺失的 IAssetManager 导入
This commit is contained in:
YHH
2025-12-13 19:44:08 +08:00
committed by GitHub
parent a716d8006c
commit beaa1d09de
258 changed files with 17725 additions and 3030 deletions

View File

@@ -7,9 +7,9 @@
*/
import { GizmoRegistry, EntityStoreService, MessageHub, SceneManagerService, ProjectService, PluginManager, IPluginManager, AssetRegistryService, type SystemContext } from '@esengine/editor-core';
import { Core, Scene, Entity, SceneSerializer, ProfilerSDK, createLogger } from '@esengine/ecs-framework';
import { Core, Scene, Entity, SceneSerializer, ProfilerSDK, createLogger, PluginServiceRegistry } from '@esengine/ecs-framework';
import { CameraConfig, EngineBridgeToken, RenderSystemToken, EngineIntegrationToken } from '@esengine/ecs-engine-bindgen';
import { TransformComponent, PluginServiceRegistry, TransformTypeToken } from '@esengine/engine-core';
import { TransformComponent, TransformTypeToken, CanvasElementToken } from '@esengine/engine-core';
import { SpriteComponent, SpriteAnimatorComponent, SpriteAnimatorSystemToken } from '@esengine/sprite';
import { invalidateUIRenderCaches, UIRenderProviderToken, UIInputSystemToken } from '@esengine/ui';
import * as esEngine from '@esengine/engine';
@@ -18,11 +18,10 @@ import {
EngineIntegration,
AssetPathResolver,
AssetPlatform,
globalPathResolver,
SceneResourceManager,
assetManager as globalAssetManager,
AssetType,
AssetManagerToken
AssetManagerToken,
isValidGUID
} from '@esengine/asset-system';
import {
GameRuntime,
@@ -33,6 +32,7 @@ import {
import { BehaviorTreeSystemToken } from '@esengine/behavior-tree';
import { Physics2DSystemToken } from '@esengine/physics-rapier2d';
import { getMaterialManager } from '@esengine/material-system';
import { WebInputSubsystem } from '@esengine/platform-web';
import { resetEngineState } from '../hooks/useEngine';
import { convertFileSrc } from '@tauri-apps/api/core';
import { IdGenerator } from '../utils/idGenerator';
@@ -120,13 +120,17 @@ export class EngineService {
};
// 创建编辑器平台适配器
// Create editor platform adapter
const platform = new EditorPlatformAdapter({
wasmModule: esEngine,
pathTransformer,
gizmoDataProvider: (component, entity, isSelected) =>
GizmoRegistry.getGizmoData(component, entity, isSelected),
hasGizmoProvider: (component) =>
GizmoRegistry.hasProvider(component.constructor as any)
GizmoRegistry.hasProvider(component.constructor as any),
// 提供输入子系统用于 Play 模式下的游戏输入
// Provide input subsystem for game input in Play mode
inputSubsystemFactory: () => new WebInputSubsystem()
});
// 创建统一运行时
@@ -212,6 +216,13 @@ export class EngineService {
services.register(EngineIntegrationToken, this._engineIntegration);
services.register(TransformTypeToken, TransformComponent);
// 注册 Canvas 元素(用于坐标转换等)
// Register canvas element (for coordinate conversion, etc.)
const canvas = this._runtime.platform.getCanvas();
if (canvas) {
services.register(CanvasElementToken, canvas);
}
// 创建系统上下文
const context: SystemContext = {
isEditor: true,
@@ -225,6 +236,10 @@ export class EngineService {
// 插件注册完加载器后,重新同步资产(确保类型正确)
await this._syncAssetRegistryToManager();
// Subscribe to asset changes to sync new assets to runtime
// 订阅资产变化以将新资产同步到运行时
this._subscribeToAssetChanges();
// 同步服务注册表到 GameRuntime用于 start/stop 时启用/禁用系统)
// Sync service registry to GameRuntime (for enabling/disabling systems on start/stop)
const runtimeServices = this._runtime.getServiceRegistry();
@@ -272,6 +287,10 @@ export class EngineService {
* 清理模块系统
*/
clearModuleSystems(): void {
// Unsubscribe from asset change events
// 取消订阅资产变化事件
this._unsubscribeFromAssetChanges();
const pluginManager = Core.services.tryResolve<PluginManager>(IPluginManager);
if (pluginManager) {
pluginManager.clearSceneSystems();
@@ -394,9 +413,9 @@ export class EngineService {
*/
private async _initializeAssetSystem(): Promise<void> {
try {
// Use global assetManager instance so all systems share the same manager
// 使用全局 assetManager 实例,以便所有系统共享同一个管理器
this._assetManager = globalAssetManager;
// Create a new AssetManager instance for this editor session
// 为此编辑器会话创建新的 AssetManager 实例
this._assetManager = new AssetManager();
// Set up asset reader for Tauri environment.
// 为 Tauri 环境设置资产读取器。
@@ -413,8 +432,8 @@ export class EngineService {
}
}
// Sync AssetRegistryService data to global assetManager's database
// 将 AssetRegistryService 的数据同步到全局 assetManager 的数据库
// Sync AssetRegistryService data to assetManager's database
// 将 AssetRegistryService 的数据同步到 assetManager 的数据库
await this._syncAssetRegistryToManager();
const pathTransformerFn = (path: string) => {
@@ -440,11 +459,6 @@ export class EngineService {
pathTransformer: pathTransformerFn
});
globalPathResolver.updateConfig({
platform: AssetPlatform.Editor,
pathTransformer: pathTransformerFn
});
if (this._runtime?.bridge) {
this._engineIntegration = new EngineIntegration(this._assetManager, this._runtime.bridge);
@@ -565,6 +579,103 @@ export class EngineService {
logger.debug('Asset sync complete');
}
/** Unsubscribe function for assets:changed event | assets:changed 事件的取消订阅函数 */
private _assetsChangedUnsubscribe: (() => void) | null = null;
/**
* Subscribe to assets:changed events and sync new assets to runtime AssetManager.
* 订阅 assets:changed 事件,将新资产同步到运行时 AssetManager。
*/
private _subscribeToAssetChanges(): void {
if (this._assetsChangedUnsubscribe) return; // Already subscribed
const messageHub = Core.services.tryResolve(MessageHub);
if (!messageHub || !this._assetManager) return;
const database = this._assetManager.getDatabase();
const assetRegistry = Core.services.tryResolve(AssetRegistryService) as AssetRegistryService | null;
const loaderFactory = this._assetManager.getLoaderFactory();
this._assetsChangedUnsubscribe = messageHub.subscribe(
'assets:changed',
async (data: { type: string; path: string; relativePath: string; guid: string }) => {
if (data.type === 'add' || data.type === 'modify') {
// Get full asset info from registry
// 从注册表获取完整资产信息
const asset = assetRegistry?.getAsset(data.guid);
if (!asset) return;
// Determine asset type
// 确定资产类型
let assetType: string | null = null;
// 1. Try to get type from meta file
const meta = assetRegistry?.metaManager.getMetaByGUID(data.guid);
if (meta?.loaderType) {
assetType = meta.loaderType;
}
// 2. Try to get type from registered loaders
if (!assetType) {
assetType = loaderFactory?.getAssetTypeByPath?.(asset.path) ?? null;
}
// 3. Fallback by extension
if (!assetType) {
const ext = asset.path.substring(asset.path.lastIndexOf('.')).toLowerCase();
if (['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp'].includes(ext)) {
assetType = AssetType.Texture;
} else if (['.mp3', '.wav', '.ogg', '.m4a'].includes(ext)) {
assetType = AssetType.Audio;
} else if (['.json'].includes(ext)) {
assetType = AssetType.Json;
} else if (['.txt', '.md', '.xml', '.yaml'].includes(ext)) {
assetType = AssetType.Text;
} else {
assetType = AssetType.Custom;
}
}
// Add to runtime database
// 添加到运行时数据库
database.addAsset({
guid: asset.guid,
path: asset.path,
type: assetType,
name: asset.name,
size: asset.size,
hash: asset.hash || '',
dependencies: [],
labels: [],
tags: new Map(),
lastModified: asset.lastModified,
version: 1
});
logger.debug(`Asset synced to runtime: ${asset.path} (${data.guid})`);
} else if (data.type === 'remove') {
// Remove from runtime database
// 从运行时数据库移除
database.removeAsset(data.guid);
logger.debug(`Asset removed from runtime: ${data.guid}`);
}
}
);
logger.debug('Subscribed to assets:changed events');
}
/**
* Unsubscribe from assets:changed events.
* 取消订阅 assets:changed 事件。
*/
private _unsubscribeFromAssetChanges(): void {
if (this._assetsChangedUnsubscribe) {
this._assetsChangedUnsubscribe();
this._assetsChangedUnsubscribe = null;
}
}
/**
* Setup asset path resolver for EngineRenderSystem.
* 为 EngineRenderSystem 设置资产路径解析器。
@@ -578,10 +689,6 @@ export class EngineService {
const renderSystem = this._runtime?.renderSystem;
if (!renderSystem) return;
// UUID v4 regex for GUID detection
// UUID v4 正则表达式用于 GUID 检测
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
renderSystem.setAssetPathResolver((guidOrPath: string): string => {
// Skip if already a valid URL
// 如果已经是有效的 URL 则跳过
@@ -589,9 +696,9 @@ export class EngineService {
return guidOrPath;
}
// Check if this is a GUID
// 检查是否为 GUID
if (uuidRegex.test(guidOrPath)) {
// Check if this is a GUID using the unified validation function
// 使用统一的验证函数检查是否为 GUID
if (isValidGUID(guidOrPath)) {
const assetRegistry = Core.services.tryResolve(AssetRegistryService) as AssetRegistryService | null;
if (assetRegistry) {
const relativePath = assetRegistry.getPathByGuid(guidOrPath);
@@ -659,7 +766,8 @@ export class EngineService {
}
/**
* Load texture through asset system
* 通过相对路径加载纹理资产(用户脚本使用)
* Load texture asset by relative path (for user scripts)
*/
async loadTextureAsset(path: string): Promise<number> {
if (!this._assetSystemInitialized || this._initializationError) {
@@ -684,6 +792,29 @@ export class EngineService {
}
}
/**
* 通过 GUID 加载纹理资产(内部引用使用)
* Load texture asset by GUID (for internal references)
*/
async loadTextureAssetByGuid(guid: string): Promise<number> {
if (!this._assetSystemInitialized || this._initializationError) {
console.warn('Asset system not initialized');
return 0;
}
if (!this._engineIntegration) {
console.warn('Engine integration not available');
return 0;
}
try {
return await this._engineIntegration.loadTextureByGuid(guid);
} catch (error) {
console.error('Failed to load texture asset by GUID:', guid, error);
return 0;
}
}
/**
* Get asset manager
*/
@@ -875,7 +1006,15 @@ export class EngineService {
* Save a snapshot of the current scene state.
*/
saveSceneSnapshot(): boolean {
return this._runtime?.saveSceneSnapshot() ?? false;
const success = this._runtime?.saveSceneSnapshot() ?? false;
if (success) {
// 清除 UI 渲染缓存(因为纹理已被清除)
// Clear UI render caches (since textures have been cleared)
invalidateUIRenderCaches();
}
return success;
}
/**

View File

@@ -2,56 +2,26 @@
* Plugin SDK Registry
* 插件 SDK 注册器
*
* 将编辑器核心模块暴露为全局变量,供插件使用。
* 插件构建时将这些模块标记为 external运行时从全局对象获取。
*
* Exposes editor core modules as global variables for plugin use.
* Plugins mark these modules as external during build, and access them from global object at runtime.
* 将统一 SDK 暴露为全局变量,供插件和用户代码使用。
* Exposes unified SDK as global variable for plugins and user code.
*
* 使用方式:
* 1. 编辑器启动时调用 PluginSDKRegistry.initialize()
* 2. 插件构建配置中设置 external: getSDKPackageNames()
* 3. 插件构建配置中设置 globals: getSDKGlobalsMapping()
* 2. 用户代码使用 import { ... } from '@esengine/sdk'
* 3. 运行时通过 window.__ESENGINE_SDK__ 访问
*/
import { Core } from '@esengine/ecs-framework';
import type { ServiceToken } from '@esengine/engine-core';
import type { ServiceToken } from '@esengine/ecs-framework';
import {
EntityStoreService,
MessageHub,
EditorConfig,
getSDKGlobalsMapping,
getSDKPackageNames,
getEnabledSDKModules,
type ISDKModuleConfig
EditorConfig
} from '@esengine/editor-core';
// 动态导入所有 SDK 模块
// Dynamic import all SDK modules
import * as ecsFramework from '@esengine/ecs-framework';
import * as editorRuntime from '@esengine/editor-runtime';
import * as behaviorTree from '@esengine/behavior-tree';
import * as engineCore from '@esengine/engine-core';
import * as sprite from '@esengine/sprite';
import * as camera from '@esengine/camera';
import * as audio from '@esengine/audio';
/**
* 模块实例映射
* Module instance mapping
*
* 由于 ES 模块的静态导入限制,我们需要维护一个包名到模块的映射。
* Due to ES module static import limitations, we need to maintain a mapping from package name to module.
*/
const MODULE_INSTANCES: Record<string, any> = {
'@esengine/ecs-framework': ecsFramework,
'@esengine/editor-runtime': editorRuntime,
'@esengine/behavior-tree': behaviorTree,
'@esengine/engine-core': engineCore,
'@esengine/sprite': sprite,
'@esengine/camera': camera,
'@esengine/audio': audio,
};
// 统一 SDK 导入
// Unified SDK import
import * as sdk from '@esengine/sdk';
// 存储服务实例引用(在初始化时设置)
// Service instance references (set during initialization)
@@ -62,8 +32,8 @@ let messageHubInstance: MessageHub | null = null;
* 插件 API 接口
* Plugin API interface
*
* 为插件提供统一的访问接口,避免模块实例不一致的问题
* Provides unified access interface for plugins, avoiding module instance inconsistency issues.
* 为插件提供统一的访问接口。
* Provides unified access interface for plugins.
*/
export interface IPluginAPI {
/** 获取当前场景 | Get current scene */
@@ -83,32 +53,17 @@ export interface IPluginAPI {
getCore(): typeof Core;
}
/**
* SDK 全局对象类型
* SDK global object type
*/
export interface ISDKGlobal {
/** 动态模块加载 | Dynamic module loading */
require: (moduleName: string) => any;
/** 插件 API | Plugin API */
api: IPluginAPI;
/** 其他动态注册的模块 | Other dynamically registered modules */
[key: string]: any;
}
/**
* 插件 SDK 注册器
* Plugin SDK Registry
*
* 职责:
* 1. 将 SDK 模块暴露到全局对象
* 1. 将统一 SDK 暴露到全局对象 window.__ESENGINE_SDK__
* 2. 提供插件 API
* 3. 支持动态模块加载
*
* Responsibilities:
* 1. Expose SDK modules to global object
* 1. Expose unified SDK to global object window.__ESENGINE_SDK__
* 2. Provide plugin API
* 3. Support dynamic module loading
*/
export class PluginSDKRegistry {
private static initialized = false;
@@ -117,8 +72,8 @@ export class PluginSDKRegistry {
* 初始化 SDK 注册器
* Initialize SDK registry
*
* 将所有配置的 SDK 模块暴露到全局对象。
* Exposes all configured SDK modules to global object.
* 将统一 SDK 暴露到全局对象。
* Exposes unified SDK to global object.
*/
static initialize(): void {
if (this.initialized) {
@@ -137,28 +92,13 @@ export class PluginSDKRegistry {
console.error('[PluginSDKRegistry] MessageHub not registered yet!');
}
// 创建 SDK 全局对象
// Create SDK global object
const sdkGlobal: ISDKGlobal = {
require: this.requireModule.bind(this),
// 创建增强的 SDK 全局对象,包含 API
// Create enhanced SDK global object with API
const sdkGlobal = {
...sdk,
api: this.createPluginAPI(),
};
// 从配置自动注册所有启用的模块
// Auto-register all enabled modules from config
const enabledModules = getEnabledSDKModules();
for (const config of enabledModules) {
const moduleInstance = MODULE_INSTANCES[config.packageName];
if (moduleInstance) {
sdkGlobal[config.globalKey] = moduleInstance;
} else {
console.warn(
`[PluginSDKRegistry] Module "${config.packageName}" configured but not imported. ` +
`Please add import statement for this module.`
);
}
}
// 设置全局对象
// Set global object
const sdkGlobalName = EditorConfig.globals.sdk;
@@ -166,10 +106,7 @@ export class PluginSDKRegistry {
this.initialized = true;
console.log(
`[PluginSDKRegistry] Initialized with ${enabledModules.length} modules:`,
enabledModules.map(m => m.globalKey)
);
console.log(`[PluginSDKRegistry] Initialized SDK at window.${sdkGlobalName}`);
}
/**
@@ -205,24 +142,6 @@ export class PluginSDKRegistry {
};
}
/**
* 动态获取模块(用于 CommonJS 风格的插件)
* Dynamic module loading (for CommonJS style plugins)
*
* @param moduleName 模块包名 | Module package name
*/
private static requireModule(moduleName: string): any {
const module = MODULE_INSTANCES[moduleName];
if (!module) {
const availableModules = Object.keys(MODULE_INSTANCES).join(', ');
throw new Error(
`[PluginSDKRegistry] Unknown module: "${moduleName}". ` +
`Available modules: ${availableModules}`
);
}
return module;
}
/**
* 检查是否已初始化
* Check if initialized
@@ -232,26 +151,18 @@ export class PluginSDKRegistry {
}
/**
* 获取所有可用的 SDK 模块名称
* Get all available SDK module names
*
* @deprecated 使用 getSDKPackageNames() 代替 | Use getSDKPackageNames() instead
* 获取 SDK 包名
* Get SDK package name
*/
static getAvailableModules(): string[] {
return getSDKPackageNames();
static getSDKPackageName(): string {
return '@esengine/sdk';
}
/**
* 获取全局变量映射(用于生成插件构建配置)
* Get globals config (for generating plugin build config)
*
* @deprecated 使用 getSDKGlobalsMapping() 代替 | Use getSDKGlobalsMapping() instead
* 获取 SDK 全局变量名
* Get SDK global variable name
*/
static getGlobalsConfig(): Record<string, string> {
return getSDKGlobalsMapping();
static getSDKGlobalName(): string {
return EditorConfig.globals.sdk;
}
}
// 重新导出辅助函数,方便插件构建工具使用
// Re-export helper functions for plugin build tools
export { getSDKGlobalsMapping, getSDKPackageNames, getEnabledSDKModules };

View File

@@ -40,6 +40,8 @@ export interface ModuleManifest {
wasmPaths?: string[];
runtimeWasmPath?: string;
externalDependencies?: string[];
/** Global key for window.__ESENGINE__ (optional, defaults to camelCase of id) */
globalKey?: string;
}
export class RuntimeResolver {
@@ -263,6 +265,7 @@ export class RuntimeResolver {
const copiedModules: string[] = [];
// Copy each module's dist files
const missingModules: string[] = [];
for (const module of modules) {
const moduleDistDir = `${this.engineModulesPath}\\${module.id}\\dist`;
const moduleSrcFile = `${moduleDistDir}\\index.mjs`;
@@ -294,9 +297,18 @@ export class RuntimeResolver {
}
copiedModules.push(module.id);
console.log(`[RuntimeResolver] Copied module: ${module.id} (${module.name})`);
} else {
missingModules.push(module.id);
console.warn(`[RuntimeResolver] MISSING dist for module: ${module.id} (looked in ${moduleDistDir})`);
}
}
if (missingModules.length > 0) {
console.error(`[RuntimeResolver] ${missingModules.length} modules have missing dist files:`, missingModules);
console.error('[RuntimeResolver] Please run: npm run build in the workspace to build all modules');
}
// Copy external dependencies (e.g., rapier2d)
await this.copyExternalDependencies(modules, libsDir, importMap);

View File

@@ -7,7 +7,7 @@
*/
import { Core } from '@esengine/ecs-framework';
import type { ServiceToken } from '@esengine/engine-core';
import type { ServiceToken } from '@esengine/ecs-framework';
import { ProfilerServiceToken, type IProfilerService } from './tokens';
/**

View File

@@ -9,7 +9,7 @@
* These services are defined in editor-app, so Tokens are also defined here.
*/
import { createServiceToken } from '@esengine/engine-core';
import { createServiceToken } from '@esengine/ecs-framework';
// ============================================================================
// Profiler Data Types (定义在这里以避免循环依赖)