feat(asset): 增强资产管理系统和编辑器 UI (#291)

* fix(editor): 修复粒子实体创建和优化检视器

- 添加 effects 分类到右键菜单,修复粒子实体无法创建的问题
- 添加粒子效果的本地化标签
- 简化粒子组件检视器,优先显示资产文件选择
- 高级属性只在未选择资产时显示,且默认折叠
- 添加可折叠的属性分组提升用户体验

* fix(particle): 修复粒子系统在浏览器预览中的资产加载和渲染

- 添加粒子 Gizmo 支持,显示发射形状并响应 Transform 缩放/旋转
- 修复资产热重载:添加 reloadAsset() 方法和 assets:refresh 事件监听
- 修复 VectorFieldEditors 数值输入精度(step 改为 0.01)
- 修复浏览器预览中粒子资产加载失败的问题:
  - 将相对路径转换为绝对路径以正确复制资产文件
  - 使用原始 GUID 而非生成的 GUID 构建 asset catalog
  - 初始化全局 assetManager 单例的 catalog 和 loader
  - 在 GameRuntime 的 systemContext 中添加 engineIntegration
- 公开 AssetManager.initializeFromCatalog 方法供运行时使用

* feat(asset): 增强资产管理系统和编辑器 UI

主要改动:
- 添加 loaderType 字段支持显式指定加载器类型覆盖
- 添加 .particle 扩展名和类型映射
- 新增 MANAGED_ASSET_DIRECTORIES 常量和相关工具方法
- EngineService 使用全局 assetManager 并同步 AssetRegistry 数据
- 修复插件启用逻辑,defaultEnabled=true 的新插件不被旧配置禁用
- ContentBrowser 添加 GUID 管理目录指示和非托管目录警告
- AssetPickerDialog 和 AssetFileInspector UI 增强
This commit is contained in:
YHH
2025-12-07 20:26:03 +08:00
committed by GitHub
parent 568b327425
commit d92c2a7b66
9 changed files with 1013 additions and 86 deletions

View File

@@ -19,7 +19,9 @@ import {
AssetPathResolver,
AssetPlatform,
globalPathResolver,
SceneResourceManager
SceneResourceManager,
assetManager as globalAssetManager,
AssetType
} from '@esengine/asset-system';
import {
GameRuntime,
@@ -202,12 +204,18 @@ export class EngineService {
engineBridge: this._runtime.bridge,
renderSystem: this._runtime.renderSystem,
assetManager: this._assetManager,
isEditor: true
engineIntegration: this._engineIntegration,
isEditor: true,
transformType: TransformComponent
};
// 让插件为场景创建系统
pluginManager.createSystemsForScene(this._runtime.scene!, context);
// Re-sync assets after plugins registered their loaders
// 插件注册完加载器后,重新同步资产(确保类型正确)
await this._syncAssetRegistryToManager();
// 同步系统引用到 GameRuntime 的 systemContext用于 start/stop 时启用/禁用系统)
this._runtime.updateSystemContext({
animatorSystem: context.animatorSystem,
@@ -357,7 +365,9 @@ export class EngineService {
*/
private async _initializeAssetSystem(): Promise<void> {
try {
this._assetManager = new AssetManager();
// Use global assetManager instance so all systems share the same manager
// 使用全局 assetManager 实例,以便所有系统共享同一个管理器
this._assetManager = globalAssetManager;
// Set up asset reader for Tauri environment.
// 为 Tauri 环境设置资产读取器。
@@ -374,6 +384,10 @@ export class EngineService {
}
}
// Sync AssetRegistryService data to global assetManager's database
// 将 AssetRegistryService 的数据同步到全局 assetManager 的数据库
await this._syncAssetRegistryToManager();
const pathTransformerFn = (path: string) => {
if (!path.startsWith('http://') && !path.startsWith('https://') &&
!path.startsWith('data:') && !path.startsWith('asset://')) {
@@ -431,6 +445,97 @@ export class EngineService {
}
}
/**
* Sync AssetRegistryService data to AssetManager's database.
* 将 AssetRegistryService 的数据同步到 AssetManager 的数据库。
*
* This enables GUID-based asset loading through the global assetManager.
* Components like ParticleSystemComponent use the global assetManager to load assets by GUID.
*
* Asset type resolution order:
* 1. loaderType from .meta file (explicit user override)
* 2. loaderFactory.getAssetTypeByPath (plugin-registered loaders)
* 3. Extension-based fallback (built-in types)
*
* 资产类型解析顺序:
* 1. .meta 文件中的 loaderType用户显式覆盖
* 2. loaderFactory.getAssetTypeByPath插件注册的加载器
* 3. 基于扩展名的回退(内置类型)
*/
private async _syncAssetRegistryToManager(): Promise<void> {
if (!this._assetManager) return;
const assetRegistry = Core.services.tryResolve(AssetRegistryService) as AssetRegistryService | null;
if (!assetRegistry || !assetRegistry.isReady) {
console.warn('[EngineService] AssetRegistryService not ready, skipping sync');
return;
}
const database = this._assetManager.getDatabase();
const allAssets = assetRegistry.getAllAssets();
const metaManager = assetRegistry.metaManager;
console.log(`[EngineService] Syncing ${allAssets.length} assets from AssetRegistry to AssetManager`);
// Use loaderFactory to determine asset type from path
// This allows plugins to register their own loaders and types
// 使用 loaderFactory 根据路径确定资产类型
// 这允许插件注册自己的加载器和类型
const loaderFactory = this._assetManager.getLoaderFactory();
for (const asset of allAssets) {
let assetType: string | null = null;
// 1. Check for explicit loaderType in .meta file (user override)
// 1. 检查 .meta 文件中的显式 loaderType用户覆盖
const meta = metaManager.getMetaByGUID(asset.guid);
if (meta?.loaderType) {
assetType = meta.loaderType;
}
// 2. Try to get type from registered loaders
// 2. 尝试从已注册的加载器获取类型
if (!assetType) {
assetType = loaderFactory?.getAssetTypeByPath?.(asset.path) ?? null;
}
// 3. Fallback: determine type from extension for basic types
// 3. 回退:根据扩展名确定基本类型
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 {
// Use Custom type - the plugin's loader should handle it
// 使用 Custom 类型 - 插件的加载器应该处理它
assetType = AssetType.Custom;
}
}
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
});
}
console.log(`[EngineService] Asset sync complete`);
}
/**
* Setup asset path resolver for EngineRenderSystem.
* 为 EngineRenderSystem 设置资产路径解析器。
@@ -873,8 +978,12 @@ export class EngineService {
dispose(): void {
this.stop();
// Don't dispose the global assetManager, just clear the reference
// 不要 dispose 全局 assetManager只是清除引用
if (this._assetManager) {
this._assetManager.dispose();
// Clear the database to free memory when switching projects
// 切换项目时清空数据库以释放内存
this._assetManager.getDatabase().clear();
this._assetManager = null;
}