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:
@@ -1,5 +1,15 @@
|
||||
import type { IService } from '@esengine/ecs-framework';
|
||||
import { Injectable, Core, createLogger, SceneSerializer, Scene } from '@esengine/ecs-framework';
|
||||
import type { IService, Entity, PrefabData } from '@esengine/ecs-framework';
|
||||
import {
|
||||
Injectable,
|
||||
Core,
|
||||
createLogger,
|
||||
SceneSerializer,
|
||||
Scene,
|
||||
PrefabSerializer,
|
||||
HierarchySystem,
|
||||
ComponentRegistry
|
||||
} from '@esengine/ecs-framework';
|
||||
import type { ComponentType } from '@esengine/ecs-framework';
|
||||
import type { SceneResourceManager } from '@esengine/asset-system';
|
||||
import type { MessageHub } from './MessageHub';
|
||||
import type { IFileAPI } from '../Types/IFileAPI';
|
||||
@@ -16,6 +26,29 @@ export interface SceneState {
|
||||
isSaved: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预制体编辑模式状态
|
||||
* Prefab edit mode state
|
||||
*/
|
||||
export interface PrefabEditModeState {
|
||||
/** 是否处于预制体编辑模式 | Whether in prefab edit mode */
|
||||
isActive: boolean;
|
||||
/** 预制体文件路径 | Prefab file path */
|
||||
prefabPath: string;
|
||||
/** 预制体名称 | Prefab name */
|
||||
prefabName: string;
|
||||
/** 预制体 GUID | Prefab GUID */
|
||||
prefabGuid?: string;
|
||||
/** 原始预制体数据(用于比较修改) | Original prefab data (for modification comparison) */
|
||||
originalPrefabData: PrefabData;
|
||||
/** 原场景路径 | Original scene path */
|
||||
originalScenePath: string | null;
|
||||
/** 原场景名称 | Original scene name */
|
||||
originalSceneName: string;
|
||||
/** 原场景是否已修改 | Whether original scene was modified */
|
||||
originalSceneModified: boolean;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SceneManagerService implements IService {
|
||||
private sceneState: SceneState = {
|
||||
@@ -25,6 +58,12 @@ export class SceneManagerService implements IService {
|
||||
isSaved: false
|
||||
};
|
||||
|
||||
/** 预制体编辑模式状态 | Prefab edit mode state */
|
||||
private prefabEditModeState: PrefabEditModeState | null = null;
|
||||
|
||||
/** 预制体编辑时场景中的根实体 | Root entity in scene during prefab editing */
|
||||
private prefabRootEntity: Entity | null = null;
|
||||
|
||||
private unsubscribeHandlers: Array<() => void> = [];
|
||||
private sceneResourceManager: SceneResourceManager | null = null;
|
||||
|
||||
@@ -196,17 +235,15 @@ export class SceneManagerService implements IService {
|
||||
public async saveSceneAs(filePath?: string): Promise<void> {
|
||||
let path: string | null | undefined = filePath;
|
||||
if (!path) {
|
||||
let defaultName = this.sceneState.sceneName || 'Untitled';
|
||||
const defaultName = this.sceneState.sceneName || 'Untitled';
|
||||
let scenesDir: string | undefined;
|
||||
|
||||
// 获取场景目录,限制保存位置 | Get scenes directory to restrict save location
|
||||
if (this.projectService?.isProjectOpen()) {
|
||||
const scenesPath = this.projectService.getScenesPath();
|
||||
if (scenesPath) {
|
||||
const sep = scenesPath.includes('\\') ? '\\' : '/';
|
||||
defaultName = `${scenesPath}${sep}${defaultName}`;
|
||||
}
|
||||
scenesDir = this.projectService.getScenesPath() ?? undefined;
|
||||
}
|
||||
|
||||
path = await this.fileAPI.saveSceneDialog(defaultName);
|
||||
path = await this.fileAPI.saveSceneDialog(defaultName, scenesDir);
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
@@ -250,17 +287,15 @@ export class SceneManagerService implements IService {
|
||||
public async exportScene(filePath?: string): Promise<void> {
|
||||
let path: string | null | undefined = filePath;
|
||||
if (!path) {
|
||||
let defaultName = (this.sceneState.sceneName || 'Untitled') + '.ecs.bin';
|
||||
const defaultName = (this.sceneState.sceneName || 'Untitled') + '.ecs.bin';
|
||||
let scenesDir: string | undefined;
|
||||
|
||||
// 获取场景目录,限制保存位置 | Get scenes directory to restrict save location
|
||||
if (this.projectService?.isProjectOpen()) {
|
||||
const scenesPath = this.projectService.getScenesPath();
|
||||
if (scenesPath) {
|
||||
const sep = scenesPath.includes('\\') ? '\\' : '/';
|
||||
defaultName = `${scenesPath}${sep}${defaultName}`;
|
||||
}
|
||||
scenesDir = this.projectService.getScenesPath() ?? undefined;
|
||||
}
|
||||
|
||||
path = await this.fileAPI.saveSceneDialog(defaultName);
|
||||
path = await this.fileAPI.saveSceneDialog(defaultName, scenesDir);
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
@@ -309,20 +344,352 @@ export class SceneManagerService implements IService {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== 预制体编辑模式 API | Prefab Edit Mode API =====
|
||||
|
||||
/**
|
||||
* 进入预制体编辑模式
|
||||
* Enter prefab edit mode
|
||||
*
|
||||
* @param prefabPath - 预制体文件路径 | Prefab file path
|
||||
*/
|
||||
public async enterPrefabEditMode(prefabPath: string): Promise<void> {
|
||||
// 如果已在预制体编辑模式,先退出
|
||||
// If already in prefab edit mode, exit first
|
||||
if (this.prefabEditModeState?.isActive) {
|
||||
await this.exitPrefabEditMode(false);
|
||||
}
|
||||
|
||||
const scene = Core.scene as Scene | null;
|
||||
if (!scene) {
|
||||
throw new Error('No active scene');
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 读取预制体文件 | Read prefab file
|
||||
const prefabJson = await this.fileAPI.readFileContent(prefabPath);
|
||||
const prefabData = PrefabSerializer.deserialize(prefabJson);
|
||||
|
||||
// 2. 验证预制体数据 | Validate prefab data
|
||||
const validation = PrefabSerializer.validate(prefabData);
|
||||
if (!validation.valid) {
|
||||
throw new Error(`Invalid prefab: ${validation.errors?.join(', ')}`);
|
||||
}
|
||||
|
||||
// 3. 保存当前场景状态 | Save current scene state
|
||||
const savedScenePath = this.sceneState.currentScenePath;
|
||||
const savedSceneName = this.sceneState.sceneName;
|
||||
const savedSceneModified = this.sceneState.isModified;
|
||||
|
||||
// 4. 请求保存场景快照(通过 MessageHub,由 EngineService 处理)
|
||||
// Request to save scene snapshot (via MessageHub, handled by EngineService)
|
||||
const snapshotSaved = await this.messageHub.request<void, boolean>(
|
||||
'engine:saveSceneSnapshot',
|
||||
undefined,
|
||||
5000
|
||||
).catch(() => false);
|
||||
|
||||
if (!snapshotSaved) {
|
||||
logger.warn('Failed to save scene snapshot, proceeding without snapshot');
|
||||
}
|
||||
|
||||
// 5. 清空场景 | Clear scene
|
||||
scene.entities.removeAllEntities();
|
||||
|
||||
// 5.1 清理查询系统和系统缓存 | Clear query system and system caches
|
||||
scene.querySystem.setEntities([]);
|
||||
scene.clearSystemEntityCaches();
|
||||
|
||||
// 5.2 重置所有系统的实体跟踪状态 | Reset entity tracking for all systems
|
||||
for (const system of scene.systems) {
|
||||
system.resetEntityTracking();
|
||||
}
|
||||
|
||||
// 6. 获取组件注册表 | Get component registry
|
||||
// ComponentRegistry.getAllComponentNames() 返回 Map<string, Function>
|
||||
// 需要转换为 Map<string, ComponentType>
|
||||
const nameToType = ComponentRegistry.getAllComponentNames();
|
||||
const componentRegistry = new Map<string, ComponentType>();
|
||||
nameToType.forEach((type, name) => {
|
||||
componentRegistry.set(name, type as ComponentType);
|
||||
});
|
||||
|
||||
// 7. 实例化预制体到场景 | Instantiate prefab to scene
|
||||
logger.info(`Instantiating prefab with ${componentRegistry.size} registered component types`);
|
||||
logger.debug('Available component types:', Array.from(componentRegistry.keys()));
|
||||
|
||||
this.prefabRootEntity = PrefabSerializer.instantiate(
|
||||
prefabData,
|
||||
scene,
|
||||
componentRegistry,
|
||||
{
|
||||
trackInstance: false, // 编辑模式不追踪实例 | Don't track instance in edit mode
|
||||
preserveIds: false
|
||||
}
|
||||
);
|
||||
|
||||
logger.info(`Prefab instantiated, root entity: ${this.prefabRootEntity?.name} (id: ${this.prefabRootEntity?.id})`);
|
||||
logger.info(`Scene entity count: ${scene.entities.count}`);
|
||||
|
||||
// 7.1 强制重建查询系统 | Force rebuild query system
|
||||
// 使用 setEntities 完全重置,确保所有索引正确重建
|
||||
// Using setEntities to fully reset, ensuring all indexes are correctly rebuilt
|
||||
const allEntities = Array.from(scene.entities.buffer);
|
||||
scene.querySystem.setEntities(allEntities);
|
||||
|
||||
// 7.2 重置所有系统的实体跟踪状态,强制它们重新扫描
|
||||
// Reset all system entity tracking, forcing them to rescan
|
||||
for (const system of scene.systems) {
|
||||
system.resetEntityTracking();
|
||||
}
|
||||
|
||||
// 7.3 清理系统缓存 | Clear system caches
|
||||
scene.clearSystemEntityCaches();
|
||||
|
||||
// 8. 加载场景资源(纹理、音频等)| Load scene resources (textures, audio, etc.)
|
||||
if (this.sceneResourceManager) {
|
||||
await this.sceneResourceManager.loadSceneResources(scene);
|
||||
logger.info('Scene resources loaded for prefab');
|
||||
} else {
|
||||
logger.warn('SceneResourceManager not available, skipping resource loading');
|
||||
}
|
||||
|
||||
// 9. 设置预制体编辑模式状态 | Set prefab edit mode state
|
||||
const prefabName = prefabData.metadata.name || prefabPath.split(/[/\\]/).pop()?.replace('.prefab', '') || 'Prefab';
|
||||
this.prefabEditModeState = {
|
||||
isActive: true,
|
||||
prefabPath,
|
||||
prefabName,
|
||||
prefabGuid: prefabData.metadata.guid,
|
||||
originalPrefabData: prefabData,
|
||||
originalScenePath: savedScenePath,
|
||||
originalSceneName: savedSceneName,
|
||||
originalSceneModified: savedSceneModified
|
||||
};
|
||||
|
||||
// 10. 更新场景状态 | Update scene state
|
||||
this.sceneState = {
|
||||
currentScenePath: null,
|
||||
sceneName: `Prefab: ${prefabName}`,
|
||||
isModified: false,
|
||||
isSaved: true
|
||||
};
|
||||
|
||||
// 11. 同步到 EntityStore | Sync to EntityStore
|
||||
this.entityStore?.syncFromScene();
|
||||
|
||||
// 12. 发布事件 | Publish events
|
||||
await this.messageHub.publish('prefab:editMode:enter', {
|
||||
prefabPath,
|
||||
prefabName,
|
||||
prefabGuid: prefabData.metadata.guid
|
||||
});
|
||||
await this.messageHub.publish('prefab:editMode:changed', {
|
||||
isActive: true,
|
||||
prefabPath,
|
||||
prefabName
|
||||
});
|
||||
|
||||
logger.info(`Entered prefab edit mode: ${prefabPath}`);
|
||||
} catch (error) {
|
||||
logger.error('Failed to enter prefab edit mode:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出预制体编辑模式
|
||||
* Exit prefab edit mode
|
||||
*
|
||||
* @param save - 是否保存修改 | Whether to save changes
|
||||
*/
|
||||
public async exitPrefabEditMode(save: boolean = false): Promise<void> {
|
||||
if (!this.prefabEditModeState?.isActive) {
|
||||
logger.warn('Not in prefab edit mode');
|
||||
return;
|
||||
}
|
||||
|
||||
const scene = Core.scene as Scene | null;
|
||||
if (!scene) {
|
||||
throw new Error('No active scene');
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 如果需要保存,先保存预制体 | If save requested, save prefab first
|
||||
if (save && this.sceneState.isModified) {
|
||||
await this.savePrefab();
|
||||
}
|
||||
|
||||
// 2. 清空当前场景 | Clear current scene
|
||||
scene.entities.removeAllEntities();
|
||||
this.prefabRootEntity = null;
|
||||
|
||||
// 3. 请求恢复场景快照(通过 MessageHub,由 EngineService 处理)
|
||||
// Request to restore scene snapshot (via MessageHub, handled by EngineService)
|
||||
const snapshotRestored = await this.messageHub.request<void, boolean>(
|
||||
'engine:restoreSceneSnapshot',
|
||||
undefined,
|
||||
5000
|
||||
).catch(() => false);
|
||||
|
||||
// 4. 恢复场景状态 | Restore scene state
|
||||
const originalState = this.prefabEditModeState;
|
||||
this.sceneState = {
|
||||
currentScenePath: originalState.originalScenePath,
|
||||
sceneName: originalState.originalSceneName,
|
||||
isModified: originalState.originalSceneModified,
|
||||
isSaved: !originalState.originalSceneModified
|
||||
};
|
||||
|
||||
// 5. 清除预制体编辑模式状态 | Clear prefab edit mode state
|
||||
this.prefabEditModeState = null;
|
||||
|
||||
// 6. 同步到 EntityStore | Sync to EntityStore
|
||||
this.entityStore?.syncFromScene();
|
||||
|
||||
// 7. 发布事件 | Publish events
|
||||
await this.messageHub.publish('prefab:editMode:exit', { saved: save });
|
||||
await this.messageHub.publish('prefab:editMode:changed', {
|
||||
isActive: false
|
||||
});
|
||||
|
||||
if (snapshotRestored) {
|
||||
await this.messageHub.publish('scene:restored', {});
|
||||
}
|
||||
|
||||
logger.info(`Exited prefab edit mode, saved: ${save}`);
|
||||
} catch (error) {
|
||||
logger.error('Failed to exit prefab edit mode:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存预制体
|
||||
* Save prefab
|
||||
*/
|
||||
public async savePrefab(): Promise<void> {
|
||||
if (!this.prefabEditModeState?.isActive) {
|
||||
throw new Error('Not in prefab edit mode');
|
||||
}
|
||||
|
||||
const scene = Core.scene as Scene | null;
|
||||
if (!scene) {
|
||||
throw new Error('No active scene');
|
||||
}
|
||||
|
||||
if (!this.prefabRootEntity) {
|
||||
throw new Error('No prefab root entity');
|
||||
}
|
||||
|
||||
try {
|
||||
const hierarchySystem = scene.getSystem(HierarchySystem) ?? undefined;
|
||||
|
||||
// 1. 从根实体创建预制体数据 | Create prefab data from root entity
|
||||
const newPrefabData = PrefabSerializer.createPrefab(
|
||||
this.prefabRootEntity,
|
||||
{
|
||||
name: this.prefabEditModeState.prefabName,
|
||||
description: this.prefabEditModeState.originalPrefabData.metadata.description,
|
||||
tags: this.prefabEditModeState.originalPrefabData.metadata.tags,
|
||||
includeChildren: true
|
||||
},
|
||||
hierarchySystem
|
||||
);
|
||||
|
||||
// 2. 保持原有 GUID | Preserve original GUID
|
||||
if (this.prefabEditModeState.prefabGuid) {
|
||||
newPrefabData.metadata.guid = this.prefabEditModeState.prefabGuid;
|
||||
}
|
||||
|
||||
// 3. 保持原有创建时间,更新修改时间 | Preserve creation time, update modification time
|
||||
newPrefabData.metadata.createdAt = this.prefabEditModeState.originalPrefabData.metadata.createdAt;
|
||||
newPrefabData.metadata.modifiedAt = Date.now();
|
||||
|
||||
// 4. 序列化并保存 | Serialize and save
|
||||
const prefabJson = PrefabSerializer.serialize(newPrefabData, true);
|
||||
await this.fileAPI.saveProject(this.prefabEditModeState.prefabPath, prefabJson);
|
||||
|
||||
// 5. 更新原始数据(用于后续修改检测)| Update original data (for subsequent modification detection)
|
||||
this.prefabEditModeState.originalPrefabData = newPrefabData;
|
||||
|
||||
// 6. 标记为已保存 | Mark as saved
|
||||
this.sceneState.isModified = false;
|
||||
this.sceneState.isSaved = true;
|
||||
|
||||
// 7. 发布事件 | Publish event
|
||||
await this.messageHub.publish('prefab:saved', {
|
||||
prefabPath: this.prefabEditModeState.prefabPath,
|
||||
prefabName: this.prefabEditModeState.prefabName
|
||||
});
|
||||
|
||||
logger.info(`Prefab saved: ${this.prefabEditModeState.prefabPath}`);
|
||||
} catch (error) {
|
||||
logger.error('Failed to save prefab:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否处于预制体编辑模式
|
||||
* Check if in prefab edit mode
|
||||
*/
|
||||
public isPrefabEditMode(): boolean {
|
||||
return this.prefabEditModeState?.isActive ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预制体编辑模式状态
|
||||
* Get prefab edit mode state
|
||||
*/
|
||||
public getPrefabEditModeState(): PrefabEditModeState | null {
|
||||
return this.prefabEditModeState ? { ...this.prefabEditModeState } : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查预制体是否已修改
|
||||
* Check if prefab has been modified
|
||||
*/
|
||||
public isPrefabModified(): boolean {
|
||||
return (this.prefabEditModeState?.isActive ?? false) && this.sceneState.isModified;
|
||||
}
|
||||
|
||||
private setupAutoModificationTracking(): void {
|
||||
// 实体级别事件 | Entity-level events
|
||||
const unsubscribeEntityAdded = this.messageHub.subscribe('entity:added', () => {
|
||||
this.markAsModified();
|
||||
});
|
||||
|
||||
const unsubscribeEntityRemoved = this.messageHub.subscribe('entity:removed', () => {
|
||||
this.markAsModified();
|
||||
});
|
||||
|
||||
const unsubscribeEntityReordered = this.messageHub.subscribe('entity:reordered', () => {
|
||||
this.markAsModified();
|
||||
});
|
||||
|
||||
this.unsubscribeHandlers.push(unsubscribeEntityAdded, unsubscribeEntityRemoved, unsubscribeEntityReordered);
|
||||
// 组件级别事件 | Component-level events
|
||||
const unsubscribeComponentAdded = this.messageHub.subscribe('component:added', () => {
|
||||
this.markAsModified();
|
||||
});
|
||||
const unsubscribeComponentRemoved = this.messageHub.subscribe('component:removed', () => {
|
||||
this.markAsModified();
|
||||
});
|
||||
const unsubscribeComponentPropertyChanged = this.messageHub.subscribe('component:property:changed', () => {
|
||||
this.markAsModified();
|
||||
});
|
||||
|
||||
// 通用场景修改事件 | Generic scene modification event
|
||||
const unsubscribeSceneModified = this.messageHub.subscribe('scene:modified', () => {
|
||||
this.markAsModified();
|
||||
});
|
||||
|
||||
this.unsubscribeHandlers.push(
|
||||
unsubscribeEntityAdded,
|
||||
unsubscribeEntityRemoved,
|
||||
unsubscribeEntityReordered,
|
||||
unsubscribeComponentAdded,
|
||||
unsubscribeComponentRemoved,
|
||||
unsubscribeComponentPropertyChanged,
|
||||
unsubscribeSceneModified
|
||||
);
|
||||
|
||||
logger.debug('Auto modification tracking setup complete');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user