Files
esengine/packages/runtime-core/src/GameRuntime.ts
YHH 536c4c5593 refactor(ui): UI 系统架构重构 (#309)
* feat(ui): 动态图集系统与渲染调试增强

## 核心功能

### 动态图集系统 (Dynamic Atlas)
- 新增 DynamicAtlasManager:运行时纹理打包,支持 MaxRects 算法
- 新增 DynamicAtlasService:自动纹理加载与图集管理
- 新增 BinPacker:高效矩形打包算法
- 支持动态/固定两种扩展策略
- 自动 UV 重映射,实现 UI 元素合批渲染

### Frame Debugger 增强
- 新增合批分析面板,显示批次中断原因
- 新增 UI 元素层级信息(depth, worldOrderInLayer)
- 新增实体高亮功能,点击可在场景中定位
- 新增动态图集可视化面板
- 改进渲染原语详情展示

### 闪光效果 (Shiny Effect)
- 新增 UIShinyEffectComponent:UI 闪光参数配置
- 新增 UIShinyEffectSystem:材质覆盖驱动的闪光动画
- 新增 ShinyEffectComponent/System(Sprite 版本)

## 引擎层改进

### Rust 纹理管理扩展
- create_blank_texture:创建空白 GPU 纹理
- update_texture_region:局部纹理更新
- 支持动态图集的 GPU 端操作

### 材质系统
- 新增 effects/ 目录:ShinyEffect 等效果实现
- 新增 interfaces/ 目录:IMaterial 等接口定义
- 新增 mixins/ 目录:可组合的材质功能

### EngineBridge 扩展
- 新增 createBlankTexture/updateTextureRegion 方法
- 改进纹理加载回调机制

## UI 渲染改进
- UIRenderCollector:支持合批调试信息
- 稳定排序:addIndex 保证渲染顺序一致性
- 九宫格渲染优化
- 材质覆盖支持

## 其他改进
- 国际化:新增 Frame Debugger 相关翻译
- 编辑器:新增渲染调试入口
- 文档:新增架构设计文档目录

* refactor(ui): 引入新基础组件架构与渲染工具函数

Phase 1 重构 - 组件职责分离与代码复用:

新增基础组件层:
- UIGraphicComponent: 所有可视 UI 元素的基类(颜色、透明度、raycast)
- UIImageComponent: 纹理显示组件(支持简单、切片、平铺、填充模式)
- UISelectableComponent: 可交互元素的基类(状态管理、颜色过渡)

新增渲染工具:
- UIRenderUtils: 提取共享的坐标计算、边框渲染、阴影渲染等工具函数
- getUIRenderTransform: 统一的变换数据提取
- renderBorder/renderShadow: 复用的边框和阴影渲染逻辑

新增渲染系统:
- UIGraphicRenderSystem: 处理新基础组件的统一渲染器

重构现有系统:
- UIRectRenderSystem: 使用新工具函数,移除重复代码
- UIButtonRenderSystem: 使用新工具函数,移除重复代码

这些改动为后续统一渲染系统奠定基础。

* refactor(ui): UIProgressBarRenderSystem 使用渲染工具函数

- 使用 getUIRenderTransform 替代手动变换计算
- 使用 renderBorder 工具函数替代重复的边框渲染
- 使用 lerpColor 工具函数替代重复的颜色插值
- 简化方法签名,使用 UIRenderTransform 类型
- 移除约 135 行重复代码

* refactor(ui): Slider 和 ScrollView 渲染系统使用工具函数

- UISliderRenderSystem: 使用 getUIRenderTransform,简化方法签名
- UIScrollViewRenderSystem: 使用 getUIRenderTransform,简化方法签名
- 统一使用 UIRenderTransform 类型减少参数传递
- 消除重复的变换计算代码

* refactor(ui): 使用 UIWidgetMarker 消除硬编码组件依赖

- 新增 UIWidgetMarker 标记组件
- UIRectRenderSystem 改为检查标记而非硬编码4种组件类型
- 各 Widget 渲染系统自动添加标记组件
- 减少模块间耦合,提高可扩展性

* feat(ui): 实现 Canvas 隔离机制

- 新增 UICanvasComponent 定义 Canvas 渲染组
- UITransformComponent 添加 Canvas 相关字段:canvasEntityId, worldSortingLayer, pixelPerfect
- UILayoutSystem 传播 Canvas 设置给子元素
- UIRenderUtils 使用 Canvas 继承的排序层
- 支持嵌套 Canvas 和不同渲染模式

* refactor(ui): 统一纹理管理工具函数

Phase 4: 纹理管理统一

新增:
- UITextureUtils.ts: 统一的纹理描述符接口和验证函数
  - UITextureDescriptor: 支持 GUID/textureId/path 多种纹理源
  - isValidTextureGuid: GUID 验证
  - getTextureKey: 获取用于合批的纹理键
  - normalizeTextureDescriptor: 规范化各种输入格式
- utils/index.ts: 工具函数导出

修改:
- UIGraphicRenderSystem: 使用新的纹理工具函数
- index.ts: 导出纹理工具类型和函数

* refactor(ui): 实现统一的脏标记机制

Phase 5: Dirty 标记机制

新增:
- UIDirtyFlags.ts: 位标记枚举和追踪工具
  - UIDirtyFlags: Visual/Layout/Transform/Material/Text 标记
  - IDirtyTrackable: 脏追踪接口
  - DirtyTracker: 辅助工具类
  - 帧级别脏状态追踪 (markFrameDirty, isFrameDirty)

修改:
- UIGraphicComponent: 实现 IDirtyTrackable
  - 属性 setter 自动设置脏标记
  - 保留 setDirty/clearDirty 向后兼容
- UIImageComponent: 所有属性支持脏追踪
  - textureGuid/imageType/fillAmount 等变化自动标记
- UIGraphicRenderSystem: 使用 clearDirtyFlags()

导出:
- UIDirtyFlags, IDirtyTrackable, DirtyTracker
- markFrameDirty, isFrameDirty, clearFrameDirty

* refactor(ui): 移除过时的 dirty flag API

移除 UIGraphicComponent 中的兼容性 API:
- 移除 _isDirty getter/setter
- 移除 setDirty() 方法
- 移除 clearDirty() 方法

现在统一使用新的 dirty flag 系统:
- isDirty() / hasDirtyFlag(flags)
- markDirty(flags) / clearDirtyFlags()

* fix(ui): 修复两个 TODO 功能

1. 滑块手柄命中测试 (UIInputSystem)
   - UISliderComponent 添加 getHandleBounds() 计算手柄边界
   - UISliderComponent 添加 isPointInHandle() 精确命中测试
   - UIInputSystem.handleSlider() 使用精确测试更新悬停状态

2. 径向填充渲染 (UIGraphicRenderSystem)
   - 实现 renderRadialFill() 方法
   - 支持 radial90/radial180/radial360 三种模式
   - 支持 fillOrigin (top/right/bottom/left) 和 fillClockwise
   - 使用多段矩形近似饼形填充效果

* feat(ui): 完善 UI 系统架构和九宫格渲染

* fix(ui): 修复文本渲染层级问题并清理调试代码

- 修复纹理就绪后调用 invalidateUIRenderCaches() 导致的无限循环
- 移除 UITextRenderSystem、UIButtonRenderSystem、UIRectRenderSystem 中的首帧调试输出
- 移除 UILayoutSystem 中的布局调试日志
- 清理所有 __UI_RENDER_DEBUG__ 条件日志

* refactor(ui): 优化渲染批处理和输入框组件

渲染系统:
- 修复 RenderBatcher 保持渲染顺序
- 优化 Rust SpriteBatch 避免合并非连续精灵
- 增强 EngineRenderSystem 纹理就绪检测

输入框组件:
- 增强 UIInputFieldComponent 功能
- 改进 UIInputSystem 输入处理
- 新增 TextMeasureService 文本测量服务

* fix(ui): 修复九宫格首帧渲染和InputField输入问题

- 修复九宫格首帧 size=0x0 问题:
  - Viewport.tsx: 预览模式读取图片尺寸存储到 importSettings
  - AssetDatabase: ISpriteSettings 添加 width/height 字段
  - AssetMetadataService: getTextureSpriteInfo 使用元数据尺寸作为后备
  - UIRectRenderSystem: 当 atlasEntry 不存在时使用 spriteInfo 尺寸
  - WebBuildPipeline: 构建时包含 importSettings
  - AssetManager: 从 catalog 初始化时复制 importSettings
  - AssetTypes: IAssetCatalogEntry 添加 importSettings 字段

- 修复 InputField 无法输入问题:
  - UIRuntimeModule: manifest 添加 pluginExport: 'UIPlugin'
  - 确保预览模式正确加载 UI 插件并绑定 UIInputSystem

- 添加调试日志用于排查纹理加载问题

* fix(sprite): 修复类型导出错误

MaterialPropertyOverride 和 MaterialOverrides 应从 @esengine/material-system 导出

* fix(ui-editor): 补充 AnchorPreset 拉伸预设的映射

添加 StretchTop, StretchMiddle, StretchBottom, StretchLeft, StretchCenter, StretchRight 的位置和锚点值映射
2025-12-19 15:33:36 +08:00

1069 lines
32 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Unified Game Runtime
* 统一游戏运行时
*
* 这是编辑器预览和独立运行的统一入口点
* This is the unified entry point for editor preview and standalone runtime
*/
import { Core, Scene, SceneSerializer, HierarchySystem, PluginServiceRegistry, createServiceToken } from '@esengine/ecs-framework';
import {
EngineBridge,
EngineRenderSystem,
EngineBridgeToken,
RenderSystemToken,
EngineIntegrationToken,
type IUIRenderDataProvider
} from '@esengine/ecs-engine-bindgen';
import {
TransformComponent,
TransformSystem,
InputSystem,
Input,
TransformTypeToken,
CanvasElementToken
} from '@esengine/engine-core';
import { AssetManager, EngineIntegration, AssetManagerToken, setGlobalAssetDatabase } from '@esengine/asset-system';
// ============================================================================
// 本地服务令牌定义 | Local Service Token Definitions
// ============================================================================
// 这些令牌使用 createServiceToken() 本地定义,而不是从源模块导入。
// 这是有意为之:
// 1. runtime-core 应保持与 ui/sprite/behavior-tree 等模块的松耦合
// 2. createServiceToken() 使用 Symbol.for(),确保相同名称在运行时匹配
// 3. 本地接口提供类型安全,无需引入模块依赖
//
// These tokens are defined locally using createServiceToken() instead of
// importing from source modules. This is intentional:
// 1. runtime-core should remain loosely coupled to ui/sprite/behavior-tree etc.
// 2. createServiceToken() uses Symbol.for(), ensuring same names match at runtime
// 3. Local interfaces provide type safety without introducing module dependencies
// ============================================================================
/**
* UI 输入系统接口(最小化定义,用于类型安全的服务访问)
* UI input system interface (minimal definition for type-safe service access)
*/
interface IUIInputSystem {
bindToCanvas(canvas: HTMLCanvasElement): void;
unbind?(): void;
}
/**
* 可启用/禁用的系统接口
* Interface for systems that can be enabled/disabled
*/
interface IEnableableSystem {
enabled: boolean;
}
/**
* 行为树系统接口
* Behavior tree system interface
*/
interface IBehaviorTreeSystem extends IEnableableSystem {
startAllAutoStartTrees?(): void;
}
/**
* 物理系统接口
* Physics system interface
*/
interface IPhysicsSystem extends IEnableableSystem {
reset?(): void;
}
/**
* Tilemap 系统接口
* Tilemap system interface
*/
interface ITilemapSystem {
clearCache?(): void;
}
// UI 模块服务令牌 | UI module service tokens
const UIRenderProviderToken = createServiceToken<IUIRenderDataProvider>('uiRenderProvider');
const UIInputSystemToken = createServiceToken<IUIInputSystem>('uiInputSystem');
// Sprite 模块服务令牌 | Sprite module service tokens
const SpriteAnimatorSystemToken = createServiceToken<IEnableableSystem>('spriteAnimatorSystem');
// BehaviorTree 模块服务令牌 | BehaviorTree module service tokens
const BehaviorTreeSystemToken = createServiceToken<IBehaviorTreeSystem>('behaviorTreeSystem');
// Physics 模块服务令牌 | Physics module service tokens
const Physics2DSystemToken = createServiceToken<IPhysicsSystem>('physics2DSystem');
// Tilemap 模块服务令牌 | Tilemap module service tokens
const TilemapSystemToken = createServiceToken<ITilemapSystem>('tilemapSystem');
import {
runtimePluginManager,
type SystemContext,
type IRuntimeModule
} from './PluginManager';
import {
loadEnabledPlugins,
type PluginPackageInfo,
type ProjectPluginConfig
} from './PluginLoader';
import {
BUILTIN_PLUGIN_PACKAGES,
mergeProjectConfig,
type ProjectConfig
} from './ProjectConfig';
import type { IPlatformAdapter, PlatformAdapterConfig } from './IPlatformAdapter';
/**
* 运行时配置
* Runtime configuration
*/
export interface GameRuntimeConfig {
/** 平台适配器 */
platform: IPlatformAdapter;
/** 项目配置 */
projectConfig?: Partial<ProjectConfig>;
/** Canvas ID */
canvasId: string;
/** 初始宽度 */
width?: number;
/** 初始高度 */
height?: number;
/** 是否自动启动渲染循环 */
autoStartRenderLoop?: boolean;
/** UI 画布尺寸 */
uiCanvasSize?: { width: number; height: number };
/**
* 跳过内部插件加载
* 编辑器模式下,插件由 editor-core 的 PluginManager 管理
* Skip internal plugin loading - editor mode uses editor-core's PluginManager
*/
skipPluginLoading?: boolean;
}
/**
* 运行时状态
* Runtime state
*/
export interface RuntimeState {
initialized: boolean;
running: boolean;
paused: boolean;
}
/**
* 统一游戏运行时
* Unified Game Runtime
*
* 提供编辑器预览和独立运行的统一实现
* Provides unified implementation for editor preview and standalone runtime
*/
export class GameRuntime {
private _platform: IPlatformAdapter;
private _bridge: EngineBridge | null = null;
private _scene: Scene | null = null;
private _renderSystem: EngineRenderSystem | null = null;
private _inputSystem: InputSystem | null = null;
private _assetManager: AssetManager | null = null;
private _engineIntegration: EngineIntegration | null = null;
private _projectConfig: ProjectConfig;
private _config: GameRuntimeConfig;
private _state: RuntimeState = {
initialized: false,
running: false,
paused: false
};
private _animationFrameId: number | null = null;
private _lastTime = 0;
// 系统上下文,供插件使用
private _systemContext: SystemContext | null = null;
// 场景快照(用于编辑器预览后恢复)
// 支持二进制格式以提升性能
private _sceneSnapshot: string | Uint8Array | null = null;
// Gizmo 注册表注入函数
private _gizmoDataProvider?: (component: any, entity: any, isSelected: boolean) => any;
private _hasGizmoProvider?: (component: any) => boolean;
constructor(config: GameRuntimeConfig) {
this._config = config;
this._platform = config.platform;
this._projectConfig = mergeProjectConfig(config.projectConfig || {});
}
/**
* 获取运行时状态
*/
get state(): RuntimeState {
return { ...this._state };
}
/**
* 获取场景
*/
get scene(): Scene | null {
return this._scene;
}
/**
* 获取引擎桥接
*/
get bridge(): EngineBridge | null {
return this._bridge;
}
/**
* 获取渲染系统
*/
get renderSystem(): EngineRenderSystem | null {
return this._renderSystem;
}
/**
* 获取资产管理器
*/
get assetManager(): AssetManager | null {
return this._assetManager;
}
/**
* 获取引擎集成
*/
get engineIntegration(): EngineIntegration | null {
return this._engineIntegration;
}
/**
* 获取系统上下文
*/
get systemContext(): SystemContext | null {
return this._systemContext;
}
/**
* 获取服务注册表(用于编辑器模式下注册外部创建的系统)
* Get service registry (for registering externally created systems in editor mode)
*/
getServiceRegistry(): PluginServiceRegistry | null {
return this._systemContext?.services ?? null;
}
/**
* 获取平台适配器
*/
get platform(): IPlatformAdapter {
return this._platform;
}
/**
* 初始化运行时
* Initialize runtime
*/
async initialize(): Promise<void> {
if (this._state.initialized) {
return;
}
try {
// 1. 初始化平台
await this._platform.initialize({
canvasId: this._config.canvasId,
width: this._config.width,
height: this._config.height,
isEditor: this._platform.isEditorMode()
});
// 2. 获取 WASM 模块并创建引擎桥接
const wasmModule = await this._platform.getWasmModule();
this._bridge = new EngineBridge({
canvasId: this._config.canvasId,
width: this._config.width,
height: this._config.height
});
await this._bridge.initializeWithModule(wasmModule);
// 3. 设置路径解析器
this._bridge.setPathResolver((path: string) => {
return this._platform.pathResolver.resolve(path);
});
// 4. 初始化 ECS Core
if (!Core.Instance) {
Core.create({ debug: false });
}
// 5. 创建或获取场景
if (Core.scene) {
this._scene = Core.scene as Scene;
} else {
this._scene = new Scene({ name: 'GameScene' });
Core.setScene(this._scene);
}
// 编辑器模式下设置 isEditorMode延迟组件生命周期回调
// Set isEditorMode in editor mode to defer component lifecycle callbacks
if (this._platform.isEditorMode()) {
this._scene.isEditorMode = true;
}
// 6. 添加基础系统
this._scene.addSystem(new HierarchySystem());
this._scene.addSystem(new TransformSystem());
// 7. 添加输入系统(最先更新,以便其他系统可以读取输入状态)
// Add input system (updates first so other systems can read input state)
this._inputSystem = new InputSystem({
disableInEditor: true // 编辑器模式下禁用,避免与编辑器输入冲突
});
this._scene.addSystem(this._inputSystem);
// 设置平台输入子系统 | Set platform input subsystem
const inputSubsystem = this._platform.getInputSubsystem?.();
if (inputSubsystem) {
this._inputSystem.setInputSubsystem(inputSubsystem);
}
// CameraSystem 由 CameraPlugin 通过插件系统创建
// CameraSystem is created by CameraPlugin via plugin system
this._renderSystem = new EngineRenderSystem(this._bridge, TransformComponent);
// 7. 设置 UI 画布尺寸
if (this._config.uiCanvasSize) {
this._renderSystem.setUICanvasSize(
this._config.uiCanvasSize.width,
this._config.uiCanvasSize.height
);
} else {
this._renderSystem.setUICanvasSize(1920, 1080);
}
// 8. 创建资产系统
this._assetManager = new AssetManager();
this._engineIntegration = new EngineIntegration(this._assetManager, this._bridge);
// 设置全局资产数据库(供渲染系统查询 sprite 元数据)
// Set global asset database (for render systems to query sprite metadata)
setGlobalAssetDatabase(this._assetManager.getDatabase());
// 9. 加载并初始化插件(编辑器模式下跳过,由 editor-core 的 PluginManager 处理)
if (!this._config.skipPluginLoading) {
await this._initializePlugins();
}
// 10. 创建系统上下文(使用 PluginServiceRegistry
const services = new PluginServiceRegistry();
// 注册核心服务 | Register core services
services.register(EngineBridgeToken, this._bridge);
services.register(RenderSystemToken, this._renderSystem);
services.register(EngineIntegrationToken, this._engineIntegration);
services.register(AssetManagerToken, this._assetManager);
services.register(TransformTypeToken, TransformComponent);
// 注册 Canvas 元素(用于坐标转换等)
// Register canvas element (for coordinate conversion, etc.)
const canvas = this._platform.getCanvas();
if (canvas) {
services.register(CanvasElementToken, canvas);
}
this._systemContext = {
isEditor: this._platform.isEditorMode(),
services
};
// 11. 让插件创建系统(编辑器模式下跳过,由 EngineService.initializeModuleSystems 处理)
if (!this._config.skipPluginLoading) {
runtimePluginManager.createSystemsForScene(this._scene, this._systemContext);
}
// 11. 设置 UI 渲染数据提供者(如果有)
const uiRenderProvider = this._systemContext.services.get(UIRenderProviderToken);
if (uiRenderProvider) {
this._renderSystem.setUIRenderDataProvider(uiRenderProvider);
}
// 12. 添加渲染系统(在所有其他系统之后)
this._scene.addSystem(this._renderSystem);
// 13. 启动默认 world
const defaultWorld = Core.worldManager.getWorld('__default__');
if (defaultWorld && !defaultWorld.isActive) {
defaultWorld.start();
}
// 14. 编辑器模式下的特殊处理
if (this._platform.isEditorMode()) {
// 禁用游戏逻辑系统
this._disableGameLogicSystems();
}
this._state.initialized = true;
// 15. 自动启动渲染循环
if (this._config.autoStartRenderLoop !== false) {
this._startRenderLoop();
}
} catch (error) {
console.error('[GameRuntime] Initialization failed:', error);
throw error;
}
}
/**
* 加载并初始化插件
*/
private async _initializePlugins(): Promise<void> {
// 检查是否已有插件注册(静态导入场景)
// Check if plugins are already registered (static import scenario)
const hasPlugins = runtimePluginManager.getPlugins().length > 0;
if (!hasPlugins) {
// 没有预注册的插件,尝试动态加载
// No pre-registered plugins, try dynamic loading
await loadEnabledPlugins(
{ plugins: this._projectConfig.plugins },
BUILTIN_PLUGIN_PACKAGES
);
}
// 初始化插件(注册组件和服务)
await runtimePluginManager.initializeRuntime(Core.services);
}
/**
* 禁用游戏逻辑系统(编辑器模式)
*/
private _disableGameLogicSystems(): void {
const services = this._systemContext?.services;
if (!services) return;
// 这些系统由插件创建,通过服务注册表获取引用
const animatorSystem = services.get(SpriteAnimatorSystemToken);
if (animatorSystem) {
animatorSystem.enabled = false;
}
const behaviorTreeSystem = services.get(BehaviorTreeSystemToken);
if (behaviorTreeSystem) {
behaviorTreeSystem.enabled = false;
}
const physicsSystem = services.get(Physics2DSystemToken);
if (physicsSystem) {
physicsSystem.enabled = false;
}
}
/**
* 启用游戏逻辑系统(预览/运行模式)
*/
private _enableGameLogicSystems(): void {
const services = this._systemContext?.services;
if (!services) return;
const animatorSystem = services.get(SpriteAnimatorSystemToken);
if (animatorSystem) {
animatorSystem.enabled = true;
}
const behaviorTreeSystem = services.get(BehaviorTreeSystemToken);
if (behaviorTreeSystem) {
behaviorTreeSystem.enabled = true;
behaviorTreeSystem.startAllAutoStartTrees?.();
}
const physicsSystem = services.get(Physics2DSystemToken);
if (physicsSystem) {
physicsSystem.enabled = true;
}
}
/**
* 启动渲染循环
*/
private _startRenderLoop(): void {
if (this._animationFrameId !== null) {
return;
}
this._lastTime = performance.now();
this._renderLoop();
}
/**
* 渲染循环
*/
private _renderLoop = (): void => {
const currentTime = performance.now();
const deltaTime = (currentTime - this._lastTime) / 1000;
this._lastTime = currentTime;
// 更新 ECS
Core.update(deltaTime);
this._animationFrameId = requestAnimationFrame(this._renderLoop);
};
/**
* 停止渲染循环
*/
private _stopRenderLoop(): void {
if (this._animationFrameId !== null) {
cancelAnimationFrame(this._animationFrameId);
this._animationFrameId = null;
}
}
/**
* 开始运行(启用游戏逻辑)
* Start running (enable game logic)
*/
start(): void {
if (!this._state.initialized || this._state.running) {
return;
}
this._state.running = true;
this._state.paused = false;
// 启用预览模式
if (this._renderSystem) {
this._renderSystem.setPreviewMode(true);
}
// 禁用编辑器模式,启用 InputSystem 和组件生命周期回调
// Disable editor mode to enable InputSystem and component lifecycle callbacks
if (this._scene) {
this._scene.isEditorMode = false;
}
// 调用场景 begin() 触发延迟的组件生命周期回调
// Call scene begin() to trigger deferred component lifecycle callbacks
if (this._scene) {
this._scene.begin();
}
// 启用游戏逻辑系统
this._enableGameLogicSystems();
// 绑定 UI 输入
const uiInputSystem = this._systemContext?.services.get(UIInputSystemToken);
if (uiInputSystem && this._config.canvasId) {
const canvas = document.getElementById(this._config.canvasId) as HTMLCanvasElement;
if (canvas) {
uiInputSystem.bindToCanvas(canvas);
}
}
// 确保渲染循环在运行
this._startRenderLoop();
}
/**
* 暂停运行
* Pause running
*/
pause(): void {
if (!this._state.running || this._state.paused) {
return;
}
this._state.paused = true;
}
/**
* 恢复运行
* Resume running
*/
resume(): void {
if (!this._state.running || !this._state.paused) {
return;
}
this._state.paused = false;
}
/**
* 停止运行(禁用游戏逻辑)
* Stop running (disable game logic)
*/
stop(): void {
if (!this._state.running) {
return;
}
this._state.running = false;
this._state.paused = false;
// 禁用预览模式
if (this._renderSystem) {
this._renderSystem.setPreviewMode(false);
}
// 恢复编辑器模式(如果是编辑器平台)
// Restore editor mode (if editor platform)
if (this._scene && this._platform.isEditorMode()) {
this._scene.isEditorMode = true;
}
// 解绑 UI 输入
const services = this._systemContext?.services;
const uiInputSystem = services?.get(UIInputSystemToken);
if (uiInputSystem) {
uiInputSystem.unbind?.();
}
// 禁用游戏逻辑系统
this._disableGameLogicSystems();
// 重置物理系统
const physicsSystem = services?.get(Physics2DSystemToken);
if (physicsSystem) {
physicsSystem.reset?.();
}
}
/**
* 单步执行
* Step forward one frame
*/
step(): void {
if (!this._state.initialized) {
return;
}
// 启用系统执行一帧
this._enableGameLogicSystems();
Core.update(1 / 60);
this._disableGameLogicSystems();
}
/**
* 加载场景数据
* Load scene data
*/
async loadScene(sceneData: string | object): Promise<void> {
if (!this._scene) {
throw new Error('Scene not initialized');
}
const jsonStr = typeof sceneData === 'string'
? sceneData
: JSON.stringify(sceneData);
SceneSerializer.deserialize(this._scene, jsonStr, {
strategy: 'replace',
preserveIds: true
});
}
/**
* 从 URL 加载场景
* Load scene from URL
*/
async loadSceneFromUrl(url: string): Promise<void> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to load scene from ${url}: ${response.status}`);
}
const sceneJson = await response.text();
await this.loadScene(sceneJson);
}
/**
* 从数据对象加载场景(用于单文件模式)
* Load scene from data object (for single-file mode)
*/
async loadSceneFromData(sceneData: unknown): Promise<void> {
const sceneJson = JSON.stringify(sceneData);
await this.loadScene(sceneJson);
}
/**
* 调整视口大小
* Resize viewport
*/
resize(width: number, height: number): void {
if (this._bridge) {
this._bridge.resize(width, height);
}
this._platform.resize(width, height);
}
/**
* 设置相机
* Set camera
*/
setCamera(config: { x: number; y: number; zoom: number; rotation?: number }): void {
if (this._bridge) {
this._bridge.setCamera({
x: config.x,
y: config.y,
zoom: config.zoom,
rotation: config.rotation ?? 0
});
}
}
/**
* 获取相机状态
* Get camera state
*/
getCamera(): { x: number; y: number; zoom: number; rotation: number } {
if (this._bridge) {
return this._bridge.getCamera();
}
return { x: 0, y: 0, zoom: 1, rotation: 0 };
}
/**
* 设置网格显示
* Set grid visibility
*/
setShowGrid(show: boolean): void {
if (this._bridge) {
this._bridge.setShowGrid(show);
}
}
/**
* 设置 Gizmo 显示
* Set gizmo visibility
*/
setShowGizmos(show: boolean): void {
if (this._renderSystem) {
this._renderSystem.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._bridge) {
this._bridge.setEditorMode(isEditor);
}
}
/**
* 获取编辑器模式
* Get editor mode
*/
isEditorMode(): boolean {
if (this._bridge) {
return this._bridge.isEditorMode();
}
return true;
}
/**
* 设置清除颜色
* Set clear color
*/
setClearColor(r: number, g: number, b: number, a: number = 1.0): void {
if (this._bridge) {
this._bridge.setClearColor(r, g, b, a);
}
}
/**
* 获取统计信息
* Get stats
*/
getStats(): { fps: number; drawCalls: number; spriteCount: number } {
if (!this._renderSystem) {
return { fps: 0, drawCalls: 0, spriteCount: 0 };
}
const engineStats = this._renderSystem.getStats();
return {
fps: engineStats?.fps ?? 0,
drawCalls: engineStats?.drawCalls ?? 0,
spriteCount: this._renderSystem.spriteCount
};
}
// ===== 编辑器特有功能 =====
// ===== Editor-specific features =====
/**
* 设置 Gizmo 注册表(编辑器模式)
* Set gizmo registry (editor mode)
*/
setGizmoRegistry(
gizmoDataProvider: (component: any, entity: any, isSelected: boolean) => any,
hasGizmoProvider: (component: any) => boolean
): void {
this._gizmoDataProvider = gizmoDataProvider;
this._hasGizmoProvider = hasGizmoProvider;
if (this._renderSystem) {
this._renderSystem.setGizmoRegistry(gizmoDataProvider, hasGizmoProvider);
}
}
/**
* 设置选中的实体 ID编辑器模式
* Set selected entity IDs (editor mode)
*/
setSelectedEntityIds(ids: number[]): void {
if (this._renderSystem) {
this._renderSystem.setSelectedEntityIds(ids);
}
}
/**
* 设置变换工具模式(编辑器模式)
* Set transform tool mode (editor mode)
*/
setTransformMode(mode: 'select' | 'move' | 'rotate' | 'scale'): void {
if (this._renderSystem) {
this._renderSystem.setTransformMode(mode);
}
}
/**
* 获取变换工具模式
* Get transform tool mode
*/
getTransformMode(): 'select' | 'move' | 'rotate' | 'scale' {
return this._renderSystem?.getTransformMode() ?? 'select';
}
/**
* 设置 UI 画布尺寸
* Set UI canvas size
*/
setUICanvasSize(width: number, height: number): void {
if (this._renderSystem) {
this._renderSystem.setUICanvasSize(width, height);
}
}
/**
* 获取 UI 画布尺寸
* Get UI canvas size
*/
getUICanvasSize(): { width: number; height: number } {
return this._renderSystem?.getUICanvasSize() ?? { width: 0, height: 0 };
}
/**
* 设置 UI 画布边界显示
* Set UI canvas boundary visibility
*/
setShowUICanvasBoundary(show: boolean): void {
if (this._renderSystem) {
this._renderSystem.setShowUICanvasBoundary(show);
}
}
/**
* 获取 UI 画布边界显示状态
* Get UI canvas boundary visibility
*/
getShowUICanvasBoundary(): boolean {
return this._renderSystem?.getShowUICanvasBoundary() ?? true;
}
// ===== 场景快照 API =====
// ===== Scene Snapshot API =====
/**
* 保存场景快照
* Save scene snapshot
*
* 使用二进制格式提升序列化性能,并支持 EntityRef 的正确序列化。
* 使用路径稳定 ID 后,不再需要清除纹理缓存。
*
* Uses binary format for better serialization performance and supports proper
* EntityRef serialization. With path-stable IDs, no need to clear texture cache.
*
* @param options 可选配置
* @param options.useJson 是否使用 JSON 格式(用于调试),默认 false 使用二进制
*/
saveSceneSnapshot(options?: { useJson?: boolean }): boolean {
if (!this._scene) {
console.warn('[GameRuntime] Cannot save snapshot: no scene');
return false;
}
try {
// 使用路径稳定 ID 后,不再清除纹理缓存
// 组件保存的 textureId 在 Play/Stop 后仍然有效
// With path-stable IDs, no longer clear texture cache
// Component's saved textureId remains valid after Play/Stop
// 使用二进制格式提升性能(默认)或 JSON 用于调试
// Use binary format for performance (default) or JSON for debugging
const format = options?.useJson ? 'json' : 'binary';
this._sceneSnapshot = SceneSerializer.serialize(this._scene, {
format,
pretty: false,
includeMetadata: false
});
return true;
} catch (error) {
console.error('[GameRuntime] Failed to save snapshot:', error);
return false;
}
}
/**
* 恢复场景快照
* Restore scene snapshot
*
* 使用两阶段反序列化确保 EntityRef 引用正确恢复:
* 1. 创建所有实体和组件
* 2. 解析所有 EntityRef 引用
*
* 使用路径稳定 ID 后,不再需要清除纹理缓存。
* 组件保存的 textureId 在恢复后仍然有效。
*
* Uses two-phase deserialization to ensure EntityRef references are properly restored:
* 1. Create all entities and components
* 2. Resolve all EntityRef references
*
* With path-stable IDs, no need to clear texture cache.
* Component's saved textureId remains valid after restore.
*/
async restoreSceneSnapshot(): Promise<boolean> {
if (!this._scene || !this._sceneSnapshot) {
console.warn('[GameRuntime] Cannot restore: no scene or snapshot');
return false;
}
try {
// 清除 Tilemap 缓存Tilemap 使用独立的缓存机制)
// Clear Tilemap cache (Tilemap uses its own cache mechanism)
const tilemapSystem = this._systemContext?.services.get(TilemapSystemToken);
if (tilemapSystem) {
tilemapSystem.clearCache?.();
}
// 使用路径稳定 ID 后,不再清除纹理缓存
// 组件保存的 textureId 在 Play/Stop 后仍然有效
// With path-stable IDs, no longer clear texture cache
// Component's saved textureId remains valid after Play/Stop
// 反序列化场景SceneSerializer 内部使用 SerializationContext 处理 EntityRef
// Deserialize scene (SceneSerializer internally uses SerializationContext for EntityRef)
SceneSerializer.deserialize(this._scene, this._sceneSnapshot, {
strategy: 'replace',
preserveIds: true
});
this._sceneSnapshot = null;
return true;
} catch (error) {
console.error('[GameRuntime] Failed to restore snapshot:', error);
return false;
}
}
/**
* 检查是否有快照
* Check if snapshot exists
*/
hasSnapshot(): boolean {
return this._sceneSnapshot !== null;
}
/**
* 获取快照大小(用于调试)
* Get snapshot size (for debugging)
*/
getSnapshotSize(): number {
if (!this._sceneSnapshot) {
return 0;
}
if (typeof this._sceneSnapshot === 'string') {
return this._sceneSnapshot.length;
}
return this._sceneSnapshot.byteLength;
}
// ===== 多视口 API =====
// ===== Multi-viewport API =====
/**
* 注册视口
* Register viewport
*/
registerViewport(id: string, canvasId: string): void {
if (this._bridge) {
this._bridge.registerViewport(id, canvasId);
}
}
/**
* 注销视口
* Unregister viewport
*/
unregisterViewport(id: string): void {
if (this._bridge) {
this._bridge.unregisterViewport(id);
}
}
/**
* 设置活动视口
* Set active viewport
*/
setActiveViewport(id: string): boolean {
if (this._bridge) {
return this._bridge.setActiveViewport(id);
}
return false;
}
/**
* 释放资源
* Dispose resources
*/
dispose(): void {
this.stop();
this._stopRenderLoop();
if (this._assetManager) {
this._assetManager.dispose();
this._assetManager = null;
// 清除全局资产数据库引用 | Clear global asset database reference
setGlobalAssetDatabase(null);
}
this._engineIntegration = null;
this._scene = null;
if (this._bridge) {
this._bridge.dispose();
this._bridge = null;
}
this._renderSystem = null;
this._inputSystem = null;
this._systemContext = null;
this._platform.dispose();
this._state.initialized = false;
}
}
/**
* 创建游戏运行时实例
* Create game runtime instance
*/
export function createGameRuntime(config: GameRuntimeConfig): GameRuntime {
return new GameRuntime(config);
}