feat(engine): 添加编辑器模式标志控制编辑器UI显示 (#274)
* feat(engine): 添加编辑器模式标志控制编辑器UI显示 - 在 Rust 引擎中添加 isEditor 标志,控制网格、gizmos、坐标轴指示器的显示 - 运行时模式下自动隐藏所有编辑器专用 UI - 编辑器预览和浏览器运行时通过 setEditorMode(false) 禁用编辑器 UI - 添加 Scene.isEditorMode 延迟组件生命周期回调,直到 begin() 调用 - 修复用户组件注册到 Core ComponentRegistry 以支持序列化 - 修复 Run in Browser 时用户组件加载问题 * fix: 复制引擎模块的类型定义文件到 dist/engine * fix: 修复用户项目 tsconfig paths 类型定义路径 - 从 module.json 读取实际包名而不是使用目录名 - 修复 .d.ts 文件复制逻辑,支持 .mjs 扩展名
This commit is contained in:
@@ -375,7 +375,15 @@ export class Entity {
|
|||||||
if (this.scene.referenceTracker) {
|
if (this.scene.referenceTracker) {
|
||||||
this.scene.referenceTracker.registerEntityScene(this.id, this.scene);
|
this.scene.referenceTracker.registerEntityScene(this.id, this.scene);
|
||||||
}
|
}
|
||||||
component.onAddedToEntity();
|
|
||||||
|
// 编辑器模式下延迟执行 onAddedToEntity | Defer onAddedToEntity in editor mode
|
||||||
|
if (this.scene.isEditorMode) {
|
||||||
|
this.scene.queueDeferredComponentCallback(() => {
|
||||||
|
component.onAddedToEntity();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
component.onAddedToEntity();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.scene && this.scene.eventSystem) {
|
if (this.scene && this.scene.eventSystem) {
|
||||||
this.scene.eventSystem.emitSync('component:added', {
|
this.scene.eventSystem.emitSync('component:added', {
|
||||||
|
|||||||
@@ -78,6 +78,18 @@ export interface IScene {
|
|||||||
*/
|
*/
|
||||||
readonly services: ServiceContainer;
|
readonly services: ServiceContainer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑器模式标志
|
||||||
|
*
|
||||||
|
* 当为 true 时,组件的生命周期回调(如 onAddedToEntity)会被延迟,
|
||||||
|
* 直到调用 begin() 开始运行场景时才会触发。
|
||||||
|
*
|
||||||
|
* Editor mode flag.
|
||||||
|
* When true, component lifecycle callbacks (like onAddedToEntity) are deferred
|
||||||
|
* until begin() is called to start running the scene.
|
||||||
|
*/
|
||||||
|
isEditorMode: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取系统列表
|
* 获取系统列表
|
||||||
*/
|
*/
|
||||||
@@ -98,6 +110,15 @@ export interface IScene {
|
|||||||
*/
|
*/
|
||||||
unload(): void;
|
unload(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加延迟的组件生命周期回调
|
||||||
|
*
|
||||||
|
* Queue a deferred component lifecycle callback.
|
||||||
|
*
|
||||||
|
* @param callback 要延迟执行的回调 | The callback to defer
|
||||||
|
*/
|
||||||
|
queueDeferredComponentCallback(callback: () => void): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始场景
|
* 开始场景
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -117,6 +117,30 @@ export class Scene implements IScene {
|
|||||||
*/
|
*/
|
||||||
private _didSceneBegin: boolean = false;
|
private _didSceneBegin: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑器模式标志
|
||||||
|
*
|
||||||
|
* 当为 true 时,组件的生命周期回调(如 onAddedToEntity)会被延迟,
|
||||||
|
* 直到调用 begin() 开始运行场景时才会触发。
|
||||||
|
*
|
||||||
|
* Editor mode flag.
|
||||||
|
* When true, component lifecycle callbacks (like onAddedToEntity) are deferred
|
||||||
|
* until begin() is called to start running the scene.
|
||||||
|
*/
|
||||||
|
public isEditorMode: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 延迟的组件生命周期回调队列
|
||||||
|
*
|
||||||
|
* 在编辑器模式下,组件的 onAddedToEntity 回调会被加入此队列,
|
||||||
|
* 等到 begin() 调用时统一执行。
|
||||||
|
*
|
||||||
|
* Deferred component lifecycle callback queue.
|
||||||
|
* In editor mode, component's onAddedToEntity callbacks are queued here,
|
||||||
|
* and will be executed when begin() is called.
|
||||||
|
*/
|
||||||
|
private _deferredComponentCallbacks: Array<() => void> = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统列表缓存
|
* 系统列表缓存
|
||||||
*/
|
*/
|
||||||
@@ -319,14 +343,47 @@ export class Scene implements IScene {
|
|||||||
*/
|
*/
|
||||||
public unload(): void {}
|
public unload(): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加延迟的组件生命周期回调
|
||||||
|
*
|
||||||
|
* 在编辑器模式下,组件的 onAddedToEntity 回调会通过此方法加入队列。
|
||||||
|
*
|
||||||
|
* Queue a deferred component lifecycle callback.
|
||||||
|
* In editor mode, component's onAddedToEntity callbacks are queued via this method.
|
||||||
|
*
|
||||||
|
* @param callback 要延迟执行的回调 | The callback to defer
|
||||||
|
*/
|
||||||
|
public queueDeferredComponentCallback(callback: () => void): void {
|
||||||
|
this._deferredComponentCallbacks.push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始场景,启动实体处理器等
|
* 开始场景,启动实体处理器等
|
||||||
*
|
*
|
||||||
* 这个方法会启动场景。它将启动实体处理器等,并调用onStart方法。
|
* 这个方法会启动场景。它将启动实体处理器等,并调用onStart方法。
|
||||||
|
* 在编辑器模式下,此方法还会执行所有延迟的组件生命周期回调。
|
||||||
|
*
|
||||||
|
* This method starts the scene. It will start entity processors and call onStart.
|
||||||
|
* In editor mode, this method also executes all deferred component lifecycle callbacks.
|
||||||
*/
|
*/
|
||||||
public begin() {
|
public begin() {
|
||||||
// 标记场景已开始运行并调用onStart方法
|
// 标记场景已开始运行
|
||||||
this._didSceneBegin = true;
|
this._didSceneBegin = true;
|
||||||
|
|
||||||
|
// 执行所有延迟的组件生命周期回调 | Execute all deferred component lifecycle callbacks
|
||||||
|
if (this._deferredComponentCallbacks.length > 0) {
|
||||||
|
for (const callback of this._deferredComponentCallbacks) {
|
||||||
|
try {
|
||||||
|
callback();
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Error executing deferred component callback:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 清空队列 | Clear the queue
|
||||||
|
this._deferredComponentCallbacks = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用onStart方法
|
||||||
this.onStart();
|
this.onStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -598,6 +598,28 @@ export class EngineBridge implements IEngineBridge {
|
|||||||
this.getEngine().setShowGizmos(show);
|
this.getEngine().setShowGizmos(show);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set editor mode.
|
||||||
|
* 设置编辑器模式。
|
||||||
|
*
|
||||||
|
* When false (runtime mode), editor-only UI like grid, gizmos,
|
||||||
|
* and axis indicator are automatically hidden.
|
||||||
|
* 当为 false(运行时模式)时,编辑器专用 UI 会自动隐藏。
|
||||||
|
*/
|
||||||
|
setEditorMode(isEditor: boolean): void {
|
||||||
|
if (!this.initialized) return;
|
||||||
|
this.getEngine().setEditorMode(isEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get editor mode.
|
||||||
|
* 获取编辑器模式。
|
||||||
|
*/
|
||||||
|
isEditorMode(): boolean {
|
||||||
|
if (!this.initialized) return true;
|
||||||
|
return this.getEngine().isEditorMode();
|
||||||
|
}
|
||||||
|
|
||||||
// ===== Multi-viewport API =====
|
// ===== Multi-viewport API =====
|
||||||
// ===== 多视口 API =====
|
// ===== 多视口 API =====
|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,11 @@ export class GameEngine {
|
|||||||
* The shader ID for referencing this shader | 用于引用此着色器的ID
|
* The shader ID for referencing this shader | 用于引用此着色器的ID
|
||||||
*/
|
*/
|
||||||
compileShader(vertex_source: string, fragment_source: string): number;
|
compileShader(vertex_source: string, fragment_source: string): number;
|
||||||
|
/**
|
||||||
|
* Get editor mode.
|
||||||
|
* 获取编辑器模式。
|
||||||
|
*/
|
||||||
|
isEditorMode(): boolean;
|
||||||
/**
|
/**
|
||||||
* Render sprites as overlay (without clearing screen).
|
* Render sprites as overlay (without clearing screen).
|
||||||
* 渲染精灵作为叠加层(不清除屏幕)。
|
* 渲染精灵作为叠加层(不清除屏幕)。
|
||||||
@@ -156,6 +161,15 @@ export class GameEngine {
|
|||||||
* * `r`, `g`, `b`, `a` - Color components (0.0-1.0) | 颜色分量 (0.0-1.0)
|
* * `r`, `g`, `b`, `a` - Color components (0.0-1.0) | 颜色分量 (0.0-1.0)
|
||||||
*/
|
*/
|
||||||
setClearColor(r: number, g: number, b: number, a: number): void;
|
setClearColor(r: number, g: number, b: number, a: number): void;
|
||||||
|
/**
|
||||||
|
* Set editor mode.
|
||||||
|
* 设置编辑器模式。
|
||||||
|
*
|
||||||
|
* When false (runtime mode), editor-only UI like grid, gizmos,
|
||||||
|
* and axis indicator are automatically hidden.
|
||||||
|
* 当为 false(运行时模式)时,编辑器专用 UI(如网格、gizmos、坐标轴指示器)会自动隐藏。
|
||||||
|
*/
|
||||||
|
setEditorMode(is_editor: boolean): void;
|
||||||
/**
|
/**
|
||||||
* Set gizmo visibility.
|
* Set gizmo visibility.
|
||||||
* 设置辅助工具可见性。
|
* 设置辅助工具可见性。
|
||||||
@@ -374,6 +388,7 @@ export interface InitOutput {
|
|||||||
readonly gameengine_hasMaterial: (a: number, b: number) => number;
|
readonly gameengine_hasMaterial: (a: number, b: number) => number;
|
||||||
readonly gameengine_hasShader: (a: number, b: number) => number;
|
readonly gameengine_hasShader: (a: number, b: number) => number;
|
||||||
readonly gameengine_height: (a: number) => number;
|
readonly gameengine_height: (a: number) => number;
|
||||||
|
readonly gameengine_isEditorMode: (a: number) => number;
|
||||||
readonly gameengine_isKeyDown: (a: number, b: number, c: number) => number;
|
readonly gameengine_isKeyDown: (a: number, b: number, c: number) => number;
|
||||||
readonly gameengine_loadTexture: (a: number, b: number, c: number, d: number) => [number, number];
|
readonly gameengine_loadTexture: (a: number, b: number, c: number, d: number) => [number, number];
|
||||||
readonly gameengine_loadTextureByPath: (a: number, b: number, c: number) => [number, number, number];
|
readonly gameengine_loadTextureByPath: (a: number, b: number, c: number) => [number, number, number];
|
||||||
@@ -389,6 +404,7 @@ export interface InitOutput {
|
|||||||
readonly gameengine_setActiveViewport: (a: number, b: number, c: number) => number;
|
readonly gameengine_setActiveViewport: (a: number, b: number, c: number) => number;
|
||||||
readonly gameengine_setCamera: (a: number, b: number, c: number, d: number, e: number) => void;
|
readonly gameengine_setCamera: (a: number, b: number, c: number, d: number, e: number) => void;
|
||||||
readonly gameengine_setClearColor: (a: number, b: number, c: number, d: number, e: number) => void;
|
readonly gameengine_setClearColor: (a: number, b: number, c: number, d: number, e: number) => void;
|
||||||
|
readonly gameengine_setEditorMode: (a: number, b: number) => void;
|
||||||
readonly gameengine_setMaterialBlendMode: (a: number, b: number, c: number) => number;
|
readonly gameengine_setMaterialBlendMode: (a: number, b: number, c: number) => number;
|
||||||
readonly gameengine_setMaterialColor: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => number;
|
readonly gameengine_setMaterialColor: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => number;
|
||||||
readonly gameengine_setMaterialFloat: (a: number, b: number, c: number, d: number, e: number) => number;
|
readonly gameengine_setMaterialFloat: (a: number, b: number, c: number, d: number, e: number) => number;
|
||||||
|
|||||||
@@ -475,12 +475,26 @@ fn update_tsconfig_file(
|
|||||||
// Check for index.d.ts
|
// Check for index.d.ts
|
||||||
// 检查是否存在 index.d.ts
|
// 检查是否存在 index.d.ts
|
||||||
let dts_path = module_path.join("index.d.ts");
|
let dts_path = module_path.join("index.d.ts");
|
||||||
if dts_path.exists() {
|
if !dts_path.exists() {
|
||||||
let module_name = format!("@esengine/{}", module_id);
|
continue;
|
||||||
let dts_path_str = format!("{}/{}/index.d.ts", engine_path_normalized, module_id);
|
|
||||||
paths.insert(module_name, serde_json::json!([dts_path_str]));
|
|
||||||
module_count += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read module.json to get the actual package name
|
||||||
|
// 读取 module.json 获取实际的包名
|
||||||
|
let module_json_path = module_path.join("module.json");
|
||||||
|
let module_name = if module_json_path.exists() {
|
||||||
|
fs::read_to_string(&module_json_path)
|
||||||
|
.ok()
|
||||||
|
.and_then(|content| serde_json::from_str::<serde_json::Value>(&content).ok())
|
||||||
|
.and_then(|json| json.get("name").and_then(|n| n.as_str()).map(|s| s.to_string()))
|
||||||
|
.unwrap_or_else(|| format!("@esengine/{}", module_id))
|
||||||
|
} else {
|
||||||
|
format!("@esengine/{}", module_id)
|
||||||
|
};
|
||||||
|
|
||||||
|
let dts_path_str = format!("{}/{}/index.d.ts", engine_path_normalized, module_id);
|
||||||
|
paths.insert(module_name, serde_json::json!([dts_path_str]));
|
||||||
|
module_count += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -248,14 +248,20 @@ export class ${className} extends Component {
|
|||||||
@Property({ type: 'number', label: 'Example Property' })
|
@Property({ type: 'number', label: 'Example Property' })
|
||||||
public exampleProperty: number = 0;
|
public exampleProperty: number = 0;
|
||||||
|
|
||||||
onInitialize(): void {
|
/**
|
||||||
// 组件初始化时调用
|
* 组件添加到实体时调用
|
||||||
// Called when component is initialized
|
* Called when component is added to entity
|
||||||
|
*/
|
||||||
|
onAddedToEntity(): void {
|
||||||
|
console.log('${className} added to entity');
|
||||||
}
|
}
|
||||||
|
|
||||||
onDestroy(): void {
|
/**
|
||||||
// 组件销毁时调用
|
* 组件从实体移除时调用
|
||||||
// Called when component is destroyed
|
* Called when component is removed from entity
|
||||||
|
*/
|
||||||
|
onRemovedFromEntity(): void {
|
||||||
|
console.log('${className} removed from entity');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -25,8 +25,12 @@ import type { ModuleManifest } from '../services/RuntimeResolver';
|
|||||||
*
|
*
|
||||||
* This matches the structure of published builds for consistency
|
* This matches the structure of published builds for consistency
|
||||||
* 这与发布构建的结构一致
|
* 这与发布构建的结构一致
|
||||||
|
*
|
||||||
|
* @param importMap - Import map for module resolution
|
||||||
|
* @param modules - Module manifests for plugin loading
|
||||||
|
* @param hasUserRuntime - Whether user-runtime.js exists and should be loaded
|
||||||
*/
|
*/
|
||||||
function generateRuntimeHtml(importMap: Record<string, string>, modules: ModuleManifest[]): string {
|
function generateRuntimeHtml(importMap: Record<string, string>, modules: ModuleManifest[], hasUserRuntime: boolean = false): string {
|
||||||
const importMapScript = `<script type="importmap">
|
const importMapScript = `<script type="importmap">
|
||||||
${JSON.stringify({ imports: importMap }, null, 2).split('\n').join('\n ')}
|
${JSON.stringify({ imports: importMap }, null, 2).split('\n').join('\n ')}
|
||||||
</script>`;
|
</script>`;
|
||||||
@@ -45,6 +49,44 @@ function generateRuntimeHtml(importMap: Record<string, string>, modules: ModuleM
|
|||||||
}`
|
}`
|
||||||
).join('\n');
|
).join('\n');
|
||||||
|
|
||||||
|
// Generate user runtime loading code
|
||||||
|
// 生成用户运行时加载代码
|
||||||
|
const userRuntimeCode = hasUserRuntime ? `
|
||||||
|
updateLoading('Loading user scripts...');
|
||||||
|
try {
|
||||||
|
// Import ECS framework and set up global for user-runtime.js shim
|
||||||
|
// 导入 ECS 框架并为 user-runtime.js 设置全局变量
|
||||||
|
const ecsFramework = await import('@esengine/ecs-framework');
|
||||||
|
window.__ESENGINE__ = window.__ESENGINE__ || {};
|
||||||
|
window.__ESENGINE__.ecsFramework = ecsFramework;
|
||||||
|
|
||||||
|
// Load user-runtime.js which contains compiled user components
|
||||||
|
// 加载 user-runtime.js,其中包含编译的用户组件
|
||||||
|
const userRuntimeScript = document.createElement('script');
|
||||||
|
userRuntimeScript.src = './user-runtime.js?_=' + Date.now();
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
userRuntimeScript.onload = resolve;
|
||||||
|
userRuntimeScript.onerror = reject;
|
||||||
|
document.head.appendChild(userRuntimeScript);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register user components to ComponentRegistry
|
||||||
|
// 将用户组件注册到 ComponentRegistry
|
||||||
|
if (window.__USER_RUNTIME_EXPORTS__) {
|
||||||
|
const { ComponentRegistry, Component } = ecsFramework;
|
||||||
|
const exports = window.__USER_RUNTIME_EXPORTS__;
|
||||||
|
for (const [name, exported] of Object.entries(exports)) {
|
||||||
|
if (typeof exported === 'function' && exported.prototype instanceof Component) {
|
||||||
|
ComponentRegistry.register(exported);
|
||||||
|
console.log('[Preview] Registered user component:', name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[Preview] Failed to load user scripts:', e.message);
|
||||||
|
}
|
||||||
|
` : '';
|
||||||
|
|
||||||
return `<!DOCTYPE html>
|
return `<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@@ -136,7 +178,7 @@ ${importMapScript}
|
|||||||
${pluginImportCode}
|
${pluginImportCode}
|
||||||
|
|
||||||
await runtime.initialize(wasmModule);
|
await runtime.initialize(wasmModule);
|
||||||
|
${userRuntimeCode}
|
||||||
updateLoading('Loading scene...');
|
updateLoading('Loading scene...');
|
||||||
await runtime.loadScene('./scene.json?_=' + Date.now());
|
await runtime.loadScene('./scene.json?_=' + Date.now());
|
||||||
|
|
||||||
@@ -681,9 +723,9 @@ export function Viewport({ locale = 'en', messageHub }: ViewportProps) {
|
|||||||
// Save editor camera state
|
// Save editor camera state
|
||||||
editorCameraRef.current = { x: camera2DOffset.x, y: camera2DOffset.y, zoom: camera2DZoom };
|
editorCameraRef.current = { x: camera2DOffset.x, y: camera2DOffset.y, zoom: camera2DZoom };
|
||||||
setPlayState('playing');
|
setPlayState('playing');
|
||||||
// Hide grid and gizmos in play mode
|
// Disable editor mode (hides grid, gizmos, axis indicator)
|
||||||
EngineService.getInstance().setShowGrid(false);
|
// 禁用编辑器模式(隐藏网格、gizmos、坐标轴指示器)
|
||||||
EngineService.getInstance().setShowGizmos(false);
|
EngineService.getInstance().setEditorMode(false);
|
||||||
// Switch to player camera
|
// Switch to player camera
|
||||||
syncPlayerCamera();
|
syncPlayerCamera();
|
||||||
engine.start();
|
engine.start();
|
||||||
@@ -708,9 +750,9 @@ export function Viewport({ locale = 'en', messageHub }: ViewportProps) {
|
|||||||
// Restore editor camera state
|
// Restore editor camera state
|
||||||
setCamera2DOffset({ x: editorCameraRef.current.x, y: editorCameraRef.current.y });
|
setCamera2DOffset({ x: editorCameraRef.current.x, y: editorCameraRef.current.y });
|
||||||
setCamera2DZoom(editorCameraRef.current.zoom);
|
setCamera2DZoom(editorCameraRef.current.zoom);
|
||||||
// Restore grid and gizmos
|
// Restore editor mode (restores grid, gizmos, axis indicator based on settings)
|
||||||
EngineService.getInstance().setShowGrid(showGrid);
|
// 恢复编辑器模式(根据设置恢复网格、gizmos、坐标轴指示器)
|
||||||
EngineService.getInstance().setShowGizmos(showGizmos);
|
EngineService.getInstance().setEditorMode(true);
|
||||||
// Restore editor default background color
|
// Restore editor default background color
|
||||||
EngineService.getInstance().setClearColor(0.1, 0.1, 0.12, 1.0);
|
EngineService.getInstance().setClearColor(0.1, 0.1, 0.12, 1.0);
|
||||||
};
|
};
|
||||||
@@ -888,8 +930,21 @@ export function Viewport({ locale = 'en', messageHub }: ViewportProps) {
|
|||||||
await TauriAPI.writeFileContent(`${runtimeDir}/asset-catalog.json`, JSON.stringify(assetCatalog, null, 2));
|
await TauriAPI.writeFileContent(`${runtimeDir}/asset-catalog.json`, JSON.stringify(assetCatalog, null, 2));
|
||||||
console.log(`[Viewport] Asset catalog created with ${Object.keys(catalogEntries).length} entries`);
|
console.log(`[Viewport] Asset catalog created with ${Object.keys(catalogEntries).length} entries`);
|
||||||
|
|
||||||
|
// Copy user-runtime.js if it exists
|
||||||
|
// 如果存在用户运行时,复制 user-runtime.js
|
||||||
|
let hasUserRuntime = false;
|
||||||
|
if (projectPath) {
|
||||||
|
const userRuntimePath = `${projectPath}\\.esengine\\compiled\\user-runtime.js`;
|
||||||
|
const userRuntimeExists = await TauriAPI.pathExists(userRuntimePath);
|
||||||
|
if (userRuntimeExists) {
|
||||||
|
await TauriAPI.copyFile(userRuntimePath, `${runtimeDir}\\user-runtime.js`);
|
||||||
|
console.log('[Viewport] Copied user-runtime.js');
|
||||||
|
hasUserRuntime = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Generate HTML with import maps (matching published build structure)
|
// Generate HTML with import maps (matching published build structure)
|
||||||
const runtimeHtml = generateRuntimeHtml(importMap, modules);
|
const runtimeHtml = generateRuntimeHtml(importMap, modules, hasUserRuntime);
|
||||||
await TauriAPI.writeFileContent(`${runtimeDir}/index.html`, runtimeHtml);
|
await TauriAPI.writeFileContent(`${runtimeDir}/index.html`, runtimeHtml);
|
||||||
|
|
||||||
// Start local server and open browser
|
// Start local server and open browser
|
||||||
@@ -954,10 +1009,26 @@ export function Viewport({ locale = 'en', messageHub }: ViewportProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write scene data and HTML with import maps
|
// Write scene data
|
||||||
const sceneDataStr = typeof sceneData === 'string' ? sceneData : new TextDecoder().decode(sceneData);
|
const sceneDataStr = typeof sceneData === 'string' ? sceneData : new TextDecoder().decode(sceneData);
|
||||||
await TauriAPI.writeFileContent(`${runtimeDir}/scene.json`, sceneDataStr);
|
await TauriAPI.writeFileContent(`${runtimeDir}/scene.json`, sceneDataStr);
|
||||||
await TauriAPI.writeFileContent(`${runtimeDir}/index.html`, generateRuntimeHtml(importMap, modules));
|
|
||||||
|
// Copy user-runtime.js if it exists
|
||||||
|
// 如果存在用户运行时,复制 user-runtime.js
|
||||||
|
let hasUserRuntime = false;
|
||||||
|
const currentProject = projectService?.getCurrentProject();
|
||||||
|
if (currentProject?.path) {
|
||||||
|
const userRuntimePath = `${currentProject.path}\\.esengine\\compiled\\user-runtime.js`;
|
||||||
|
const userRuntimeExists = await TauriAPI.pathExists(userRuntimePath);
|
||||||
|
if (userRuntimeExists) {
|
||||||
|
await TauriAPI.copyFile(userRuntimePath, `${runtimeDir}\\user-runtime.js`);
|
||||||
|
console.log('[Viewport] Copied user-runtime.js for device preview');
|
||||||
|
hasUserRuntime = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write HTML with import maps
|
||||||
|
await TauriAPI.writeFileContent(`${runtimeDir}/index.html`, generateRuntimeHtml(importMap, modules, hasUserRuntime));
|
||||||
|
|
||||||
// Copy textures referenced in scene
|
// Copy textures referenced in scene
|
||||||
const assetsDir = `${runtimeDir}\\assets`;
|
const assetsDir = `${runtimeDir}\\assets`;
|
||||||
|
|||||||
@@ -612,6 +612,26 @@ export class EngineService {
|
|||||||
return this._runtime?.renderSystem?.getShowGizmos() ?? true;
|
return this._runtime?.renderSystem?.getShowGizmos() ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set editor mode.
|
||||||
|
* 设置编辑器模式。
|
||||||
|
*
|
||||||
|
* When false (runtime mode), editor-only UI like grid, gizmos,
|
||||||
|
* and axis indicator are automatically hidden.
|
||||||
|
* 当为 false(运行时模式)时,编辑器专用 UI 会自动隐藏。
|
||||||
|
*/
|
||||||
|
setEditorMode(isEditor: boolean): void {
|
||||||
|
this._runtime?.setEditorMode(isEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get editor mode.
|
||||||
|
* 获取编辑器模式。
|
||||||
|
*/
|
||||||
|
isEditorMode(): boolean {
|
||||||
|
return this._runtime?.isEditorMode() ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set UI canvas size for boundary display.
|
* Set UI canvas size for boundary display.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -213,6 +213,15 @@ function copyEngineModulesPlugin(): Plugin {
|
|||||||
if (fs.existsSync(sourceMapPath)) {
|
if (fs.existsSync(sourceMapPath)) {
|
||||||
fs.copyFileSync(sourceMapPath, path.join(moduleOutputDir, 'index.js.map'));
|
fs.copyFileSync(sourceMapPath, path.join(moduleOutputDir, 'index.js.map'));
|
||||||
}
|
}
|
||||||
|
// Copy type definitions if exists
|
||||||
|
// 复制类型定义文件(如果存在)
|
||||||
|
// Handle both .js and .mjs extensions
|
||||||
|
// 处理 .js 和 .mjs 两种扩展名
|
||||||
|
const distDir = path.dirname(module.distPath);
|
||||||
|
const dtsPath = path.join(distDir, 'index.d.ts');
|
||||||
|
if (fs.existsSync(dtsPath)) {
|
||||||
|
fs.copyFileSync(dtsPath, path.join(moduleOutputDir, 'index.d.ts'));
|
||||||
|
}
|
||||||
hasRuntime = true;
|
hasRuntime = true;
|
||||||
|
|
||||||
// Copy additional included files (e.g., chunks)
|
// Copy additional included files (e.g., chunks)
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ export class SceneManagerService implements IService {
|
|||||||
throw new Error('No active scene');
|
throw new Error('No active scene');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 确保编辑器模式下设置 isEditorMode,延迟组件生命周期回调
|
||||||
|
// Ensure isEditorMode is set in editor to defer component lifecycle callbacks
|
||||||
|
scene.isEditorMode = true;
|
||||||
|
|
||||||
// 只移除实体,保留系统(系统由模块管理)
|
// 只移除实体,保留系统(系统由模块管理)
|
||||||
// Only remove entities, preserve systems (systems managed by modules)
|
// Only remove entities, preserve systems (systems managed by modules)
|
||||||
scene.entities.removeAllEntities();
|
scene.entities.removeAllEntities();
|
||||||
@@ -117,6 +121,11 @@ export class SceneManagerService implements IService {
|
|||||||
if (!scene) {
|
if (!scene) {
|
||||||
throw new Error('No active scene');
|
throw new Error('No active scene');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 确保编辑器模式下设置 isEditorMode,延迟组件生命周期回调
|
||||||
|
// Ensure isEditorMode is set in editor to defer component lifecycle callbacks
|
||||||
|
scene.isEditorMode = true;
|
||||||
|
|
||||||
scene.deserialize(jsonData, {
|
scene.deserialize(jsonData, {
|
||||||
strategy: 'replace'
|
strategy: 'replace'
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { IService } from '@esengine/ecs-framework';
|
import type { IService } from '@esengine/ecs-framework';
|
||||||
import { Injectable, createLogger, PlatformDetector } from '@esengine/ecs-framework';
|
import { Injectable, createLogger, PlatformDetector, ComponentRegistry as CoreComponentRegistry } from '@esengine/ecs-framework';
|
||||||
import type {
|
import type {
|
||||||
IUserCodeService,
|
IUserCodeService,
|
||||||
UserScriptInfo,
|
UserScriptInfo,
|
||||||
@@ -333,9 +333,23 @@ export class UserCodeService implements IService, IUserCodeService {
|
|||||||
if (this._isComponentClass(exported)) {
|
if (this._isComponentClass(exported)) {
|
||||||
logger.debug(`Found component: ${name} | 发现组件: ${name}`);
|
logger.debug(`Found component: ${name} | 发现组件: ${name}`);
|
||||||
|
|
||||||
// Register with ComponentRegistry if provided | 如果提供了 ComponentRegistry 则注册
|
// Register with Core ComponentRegistry for serialization/deserialization
|
||||||
// ComponentRegistry expects ComponentTypeInfo object, not the class directly
|
// 注册到核心 ComponentRegistry 用于序列化/反序列化
|
||||||
// ComponentRegistry 期望 ComponentTypeInfo 对象,而不是直接传入类
|
try {
|
||||||
|
CoreComponentRegistry.register(exported);
|
||||||
|
// Debug: verify registration
|
||||||
|
const registeredType = CoreComponentRegistry.getComponentType(name);
|
||||||
|
if (registeredType) {
|
||||||
|
logger.info(`Component ${name} registered to core registry successfully`);
|
||||||
|
} else {
|
||||||
|
logger.warn(`Component ${name} registered but not found by name lookup`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn(`Failed to register component ${name} to core registry | 注册组件 ${name} 到核心注册表失败:`, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register with Editor ComponentRegistry for UI display
|
||||||
|
// 注册到编辑器 ComponentRegistry 用于 UI 显示
|
||||||
if (componentRegistry && typeof componentRegistry.register === 'function') {
|
if (componentRegistry && typeof componentRegistry.register === 'function') {
|
||||||
try {
|
try {
|
||||||
componentRegistry.register({
|
componentRegistry.register({
|
||||||
@@ -345,7 +359,7 @@ export class UserCodeService implements IService, IUserCodeService {
|
|||||||
description: `User component: ${name}`
|
description: `User component: ${name}`
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn(`Failed to register component ${name} | 注册组件 ${name} 失败:`, err);
|
logger.warn(`Failed to register component ${name} to editor registry | 注册组件 ${name} 到编辑器注册表失败:`, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,14 @@ pub struct Engine {
|
|||||||
/// Whether to show gizmos.
|
/// Whether to show gizmos.
|
||||||
/// 是否显示辅助工具。
|
/// 是否显示辅助工具。
|
||||||
show_gizmos: bool,
|
show_gizmos: bool,
|
||||||
|
|
||||||
|
/// Whether the engine is running in editor mode.
|
||||||
|
/// 引擎是否在编辑器模式下运行。
|
||||||
|
///
|
||||||
|
/// When false (runtime mode), editor-only UI like grid, gizmos,
|
||||||
|
/// and axis indicator are automatically hidden.
|
||||||
|
/// 当为 false(运行时模式)时,编辑器专用 UI(如网格、gizmos、坐标轴指示器)会自动隐藏。
|
||||||
|
is_editor: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
@@ -116,6 +124,7 @@ impl Engine {
|
|||||||
show_grid: true,
|
show_grid: true,
|
||||||
viewport_manager: ViewportManager::new(),
|
viewport_manager: ViewportManager::new(),
|
||||||
show_gizmos: true,
|
show_gizmos: true,
|
||||||
|
is_editor: true, // 默认为编辑器模式 | Default to editor mode
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +163,7 @@ impl Engine {
|
|||||||
show_grid: true,
|
show_grid: true,
|
||||||
viewport_manager: ViewportManager::new(),
|
viewport_manager: ViewportManager::new(),
|
||||||
show_gizmos: true,
|
show_gizmos: true,
|
||||||
|
is_editor: true, // 默认为编辑器模式 | Default to editor mode
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,8 +222,9 @@ impl Engine {
|
|||||||
let [r, g, b, a] = self.renderer.get_clear_color();
|
let [r, g, b, a] = self.renderer.get_clear_color();
|
||||||
self.context.clear(r, g, b, a);
|
self.context.clear(r, g, b, a);
|
||||||
|
|
||||||
// Render grid first (background)
|
// Render grid first (background) - only in editor mode
|
||||||
if self.show_grid {
|
// 首先渲染网格(背景)- 仅在编辑器模式下
|
||||||
|
if self.is_editor && self.show_grid {
|
||||||
self.grid_renderer.render(self.context.gl(), self.renderer.camera());
|
self.grid_renderer.render(self.context.gl(), self.renderer.camera());
|
||||||
self.grid_renderer.render_axes(self.context.gl(), self.renderer.camera());
|
self.grid_renderer.render_axes(self.context.gl(), self.renderer.camera());
|
||||||
}
|
}
|
||||||
@@ -221,8 +232,9 @@ impl Engine {
|
|||||||
// Render sprites
|
// Render sprites
|
||||||
self.renderer.render(self.context.gl(), &self.texture_manager)?;
|
self.renderer.render(self.context.gl(), &self.texture_manager)?;
|
||||||
|
|
||||||
// Render gizmos on top
|
// Render gizmos on top - only in editor mode
|
||||||
if self.show_gizmos {
|
// 在顶部渲染 gizmos - 仅在编辑器模式下
|
||||||
|
if self.is_editor && self.show_gizmos {
|
||||||
self.gizmo_renderer.render(self.context.gl(), self.renderer.camera());
|
self.gizmo_renderer.render(self.context.gl(), self.renderer.camera());
|
||||||
// Render axis indicator in corner
|
// Render axis indicator in corner
|
||||||
// 在角落渲染坐标轴指示器
|
// 在角落渲染坐标轴指示器
|
||||||
@@ -411,6 +423,23 @@ impl Engine {
|
|||||||
self.show_gizmos
|
self.show_gizmos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set editor mode.
|
||||||
|
/// 设置编辑器模式。
|
||||||
|
///
|
||||||
|
/// When false (runtime mode), editor-only UI like grid, gizmos,
|
||||||
|
/// and axis indicator are automatically hidden regardless of their individual settings.
|
||||||
|
/// 当为 false(运行时模式)时,编辑器专用 UI(如网格、gizmos、坐标轴指示器)
|
||||||
|
/// 会自动隐藏,无论它们的单独设置如何。
|
||||||
|
pub fn set_editor_mode(&mut self, is_editor: bool) {
|
||||||
|
self.is_editor = is_editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get editor mode.
|
||||||
|
/// 获取编辑器模式。
|
||||||
|
pub fn is_editor(&self) -> bool {
|
||||||
|
self.is_editor
|
||||||
|
}
|
||||||
|
|
||||||
/// Set clear color for the active viewport.
|
/// Set clear color for the active viewport.
|
||||||
/// 设置活动视口的清除颜色。
|
/// 设置活动视口的清除颜色。
|
||||||
pub fn set_clear_color(&mut self, r: f32, g: f32, b: f32, a: f32) {
|
pub fn set_clear_color(&mut self, r: f32, g: f32, b: f32, a: f32) {
|
||||||
@@ -504,8 +533,9 @@ impl Engine {
|
|||||||
renderer_camera.rotation = camera.rotation;
|
renderer_camera.rotation = camera.rotation;
|
||||||
renderer_camera.set_viewport(camera.viewport_width(), camera.viewport_height());
|
renderer_camera.set_viewport(camera.viewport_width(), camera.viewport_height());
|
||||||
|
|
||||||
// Render grid if enabled
|
// Render grid if enabled - only in editor mode
|
||||||
if show_grid {
|
// 渲染网格(如果启用)- 仅在编辑器模式下
|
||||||
|
if self.is_editor && show_grid {
|
||||||
self.grid_renderer.render(viewport.gl(), &camera);
|
self.grid_renderer.render(viewport.gl(), &camera);
|
||||||
self.grid_renderer.render_axes(viewport.gl(), &camera);
|
self.grid_renderer.render_axes(viewport.gl(), &camera);
|
||||||
}
|
}
|
||||||
@@ -513,8 +543,9 @@ impl Engine {
|
|||||||
// Render sprites
|
// Render sprites
|
||||||
self.renderer.render(viewport.gl(), &self.texture_manager)?;
|
self.renderer.render(viewport.gl(), &self.texture_manager)?;
|
||||||
|
|
||||||
// Render gizmos if enabled
|
// Render gizmos if enabled - only in editor mode
|
||||||
if show_gizmos {
|
// 渲染 gizmos(如果启用)- 仅在编辑器模式下
|
||||||
|
if self.is_editor && show_gizmos {
|
||||||
self.gizmo_renderer.render(viewport.gl(), &camera);
|
self.gizmo_renderer.render(viewport.gl(), &camera);
|
||||||
// Render axis indicator in corner
|
// Render axis indicator in corner
|
||||||
// 在角落渲染坐标轴指示器
|
// 在角落渲染坐标轴指示器
|
||||||
|
|||||||
@@ -390,6 +390,24 @@ impl GameEngine {
|
|||||||
self.engine.set_show_gizmos(show);
|
self.engine.set_show_gizmos(show);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set editor mode.
|
||||||
|
/// 设置编辑器模式。
|
||||||
|
///
|
||||||
|
/// When false (runtime mode), editor-only UI like grid, gizmos,
|
||||||
|
/// and axis indicator are automatically hidden.
|
||||||
|
/// 当为 false(运行时模式)时,编辑器专用 UI(如网格、gizmos、坐标轴指示器)会自动隐藏。
|
||||||
|
#[wasm_bindgen(js_name = setEditorMode)]
|
||||||
|
pub fn set_editor_mode(&mut self, is_editor: bool) {
|
||||||
|
self.engine.set_editor_mode(is_editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get editor mode.
|
||||||
|
/// 获取编辑器模式。
|
||||||
|
#[wasm_bindgen(js_name = isEditorMode)]
|
||||||
|
pub fn is_editor_mode(&self) -> bool {
|
||||||
|
self.engine.is_editor()
|
||||||
|
}
|
||||||
|
|
||||||
// ===== Multi-viewport API =====
|
// ===== Multi-viewport API =====
|
||||||
// ===== 多视口 API =====
|
// ===== 多视口 API =====
|
||||||
|
|
||||||
|
|||||||
@@ -138,9 +138,9 @@ export class BrowserRuntime {
|
|||||||
this._runtime.assetManager.setReader(this._assetReader);
|
this._runtime.assetManager.setReader(this._assetReader);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Browser-specific settings (no editor UI)
|
// Disable editor mode (hides grid, gizmos, axis indicator)
|
||||||
this._runtime.setShowGrid(false);
|
// 禁用编辑器模式(隐藏网格、gizmos、坐标轴指示器)
|
||||||
this._runtime.setShowGizmos(false);
|
this._runtime.setEditorMode(false);
|
||||||
|
|
||||||
this._initialized = true;
|
this._initialized = true;
|
||||||
console.log('[Runtime] Initialized');
|
console.log('[Runtime] Initialized');
|
||||||
|
|||||||
@@ -218,6 +218,12 @@ export class GameRuntime {
|
|||||||
Core.setScene(this._scene);
|
Core.setScene(this._scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 编辑器模式下设置 isEditorMode,延迟组件生命周期回调
|
||||||
|
// Set isEditorMode in editor mode to defer component lifecycle callbacks
|
||||||
|
if (this._platform.isEditorMode()) {
|
||||||
|
this._scene.isEditorMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
// 6. 添加基础系统
|
// 6. 添加基础系统
|
||||||
this._scene.addSystem(new HierarchySystem());
|
this._scene.addSystem(new HierarchySystem());
|
||||||
this._scene.addSystem(new TransformSystem());
|
this._scene.addSystem(new TransformSystem());
|
||||||
@@ -402,6 +408,12 @@ export class GameRuntime {
|
|||||||
this._renderSystem.setPreviewMode(true);
|
this._renderSystem.setPreviewMode(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 调用场景 begin() 触发延迟的组件生命周期回调
|
||||||
|
// Call scene begin() to trigger deferred component lifecycle callbacks
|
||||||
|
if (this._scene) {
|
||||||
|
this._scene.begin();
|
||||||
|
}
|
||||||
|
|
||||||
// 启用游戏逻辑系统
|
// 启用游戏逻辑系统
|
||||||
this._enableGameLogicSystems();
|
this._enableGameLogicSystems();
|
||||||
|
|
||||||
@@ -576,6 +588,31 @@ export class GameRuntime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置编辑器模式
|
||||||
|
* Set editor mode
|
||||||
|
*
|
||||||
|
* When false (runtime mode), editor-only UI like grid, gizmos,
|
||||||
|
* and axis indicator are automatically hidden.
|
||||||
|
* 当为 false(运行时模式)时,编辑器专用 UI 会自动隐藏。
|
||||||
|
*/
|
||||||
|
setEditorMode(isEditor: boolean): void {
|
||||||
|
if (this._bridge) {
|
||||||
|
this._bridge.setEditorMode(isEditor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取编辑器模式
|
||||||
|
* Get editor mode
|
||||||
|
*/
|
||||||
|
isEditorMode(): boolean {
|
||||||
|
if (this._bridge) {
|
||||||
|
return this._bridge.isEditorMode();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置清除颜色
|
* 设置清除颜色
|
||||||
* Set clear color
|
* Set clear color
|
||||||
|
|||||||
Reference in New Issue
Block a user