refactor: reorganize package structure and decouple framework packages (#338)
* refactor: reorganize package structure and decouple framework packages ## Package Structure Reorganization - Reorganized 55 packages into categorized subdirectories: - packages/framework/ - Generic framework (Laya/Cocos compatible) - packages/engine/ - ESEngine core modules - packages/rendering/ - Rendering modules (WASM dependent) - packages/physics/ - Physics modules - packages/streaming/ - World streaming - packages/network-ext/ - Network extensions - packages/editor/ - Editor framework and plugins - packages/rust/ - Rust WASM engine - packages/tools/ - Build tools and SDK ## Framework Package Decoupling - Decoupled behavior-tree and blueprint packages from ESEngine dependencies - Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent) - ESEngine-specific code moved to esengine/ subpath exports - Framework packages now usable with Cocos/Laya without ESEngine ## CI Configuration - Updated CI to only type-check and lint framework packages - Added type-check:framework and lint:framework scripts ## Breaking Changes - Package import paths changed due to directory reorganization - ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine') * fix: update es-engine file path after directory reorganization * docs: update README to focus on framework over engine * ci: only build framework packages, remove Rust/WASM dependencies * fix: remove esengine subpath from behavior-tree and blueprint builds ESEngine integration code will only be available in full engine builds. Framework packages are now purely engine-agnostic. * fix: move network-protocols to framework, build both in CI * fix: update workflow paths from packages/core to packages/framework/core * fix: exclude esengine folder from type-check in behavior-tree and blueprint * fix: update network tsconfig references to new paths * fix: add test:ci:framework to only test framework packages in CI * fix: only build core and math npm packages in CI * fix: exclude test files from CodeQL and fix string escaping security issue
This commit is contained in:
43
packages/engine/engine-core/module.json
Normal file
43
packages/engine/engine-core/module.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"id": "engine-core",
|
||||
"name": "@esengine/engine-core",
|
||||
"globalKey": "engineCore",
|
||||
"displayName": "Engine Core",
|
||||
"description": "Engine lifecycle, scene management, game loop | 引擎生命周期、场景管理、游戏循环",
|
||||
"version": "1.0.0",
|
||||
"category": "Core",
|
||||
"icon": "Cpu",
|
||||
"tags": [
|
||||
"engine",
|
||||
"scene",
|
||||
"gameloop"
|
||||
],
|
||||
"isCore": true,
|
||||
"defaultEnabled": true,
|
||||
"isEngineModule": true,
|
||||
"hasRuntime": true,
|
||||
"canContainContent": false,
|
||||
"platforms": [
|
||||
"web",
|
||||
"desktop",
|
||||
"mobile"
|
||||
],
|
||||
"dependencies": [
|
||||
"core",
|
||||
"math"
|
||||
],
|
||||
"exports": {
|
||||
"other": [
|
||||
"Engine",
|
||||
"Scene",
|
||||
"SceneManager",
|
||||
"GameLoop",
|
||||
"Time"
|
||||
]
|
||||
},
|
||||
"requiresWasm": false,
|
||||
"outputPath": "dist/index.js",
|
||||
"coreServiceExports": ["createServiceToken", "PluginServiceRegistry"],
|
||||
"userScriptEntries": ["index.ts", "main.ts", "game.ts", "index.js", "main.js"],
|
||||
"userScriptExternals": ["@esengine/ecs-framework", "@esengine/core", "@esengine/engine-core", "@esengine/asset-system"]
|
||||
}
|
||||
48
packages/engine/engine-core/package.json
Normal file
48
packages/engine/engine-core/package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@esengine/engine-core",
|
||||
"version": "1.0.0",
|
||||
"description": "Engine core components - Transform, etc.",
|
||||
"esengine": {
|
||||
"plugin": true,
|
||||
"pluginExport": "EnginePlugin",
|
||||
"category": "core",
|
||||
"isEnginePlugin": true
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"build:watch": "tsup --watch",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rimraf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@esengine/ecs-framework": "workspace:*",
|
||||
"@esengine/ecs-framework-math": "workspace:*",
|
||||
"@esengine/platform-common": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@esengine/build-config": "workspace:*",
|
||||
"rimraf": "^5.0.5",
|
||||
"tsup": "^8.0.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"keywords": [
|
||||
"ecs",
|
||||
"engine",
|
||||
"transform"
|
||||
],
|
||||
"author": "yhh",
|
||||
"license": "MIT"
|
||||
}
|
||||
291
packages/engine/engine-core/src/EnginePlugin.ts
Normal file
291
packages/engine/engine-core/src/EnginePlugin.ts
Normal file
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* 插件系统核心类型定义
|
||||
* Plugin system core type definitions
|
||||
*
|
||||
* 这是运行时插件类型的唯一定义源。
|
||||
* 编辑器类型在 editor-core 中扩展这些类型。
|
||||
*
|
||||
* This is the single source of truth for runtime plugin types.
|
||||
* Editor types extend these in editor-core.
|
||||
*
|
||||
* @see docs/architecture/plugin-system-design.md
|
||||
*/
|
||||
|
||||
import type { IComponentRegistry, IScene, ServiceContainer } from '@esengine/ecs-framework';
|
||||
import { PluginServiceRegistry } from '@esengine/ecs-framework';
|
||||
import { TransformComponent } from './TransformComponent';
|
||||
import type { ModuleManifest } from './ModuleManifest';
|
||||
|
||||
// 导入 engine-core 特有的服务令牌
|
||||
// Import engine-core specific service tokens
|
||||
import {
|
||||
TransformTypeToken,
|
||||
CanvasElementToken,
|
||||
TextureServiceToken,
|
||||
DynamicAtlasServiceToken,
|
||||
CoordinateServiceToken,
|
||||
RenderConfigServiceToken,
|
||||
type ITextureService,
|
||||
type IDynamicAtlasService,
|
||||
type ICoordinateService,
|
||||
type IRenderConfigService
|
||||
} from './PluginServiceRegistry';
|
||||
|
||||
// 导出 engine-core 特有的服务令牌 | Export engine-core specific service tokens
|
||||
export {
|
||||
TransformTypeToken,
|
||||
CanvasElementToken,
|
||||
TextureServiceToken,
|
||||
DynamicAtlasServiceToken,
|
||||
CoordinateServiceToken,
|
||||
RenderConfigServiceToken,
|
||||
type ITextureService,
|
||||
type IDynamicAtlasService,
|
||||
type ICoordinateService,
|
||||
type IRenderConfigService
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 编辑器模块基础接口 | Editor Module Base Interface
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 编辑器模块基础接口
|
||||
* Base editor module interface
|
||||
*
|
||||
* 定义编辑器模块的核心生命周期方法。
|
||||
* 完整的 IEditorModuleLoader 接口在 editor-core 中扩展此接口。
|
||||
*
|
||||
* Defines core lifecycle methods for editor modules.
|
||||
* Full IEditorModuleLoader interface extends this in editor-core.
|
||||
*/
|
||||
export interface IEditorModuleBase {
|
||||
/**
|
||||
* 安装编辑器模块
|
||||
* Install editor module
|
||||
*/
|
||||
install(services: ServiceContainer): Promise<void>;
|
||||
|
||||
/**
|
||||
* 卸载编辑器模块
|
||||
* Uninstall editor module
|
||||
*/
|
||||
uninstall?(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 编辑器就绪回调
|
||||
* Editor ready callback
|
||||
*/
|
||||
onEditorReady?(): void | Promise<void>;
|
||||
|
||||
/**
|
||||
* 项目打开回调
|
||||
* Project open callback
|
||||
*/
|
||||
onProjectOpen?(projectPath: string): void | Promise<void>;
|
||||
|
||||
/**
|
||||
* 项目关闭回调
|
||||
* Project close callback
|
||||
*/
|
||||
onProjectClose?(): void | Promise<void>;
|
||||
|
||||
/**
|
||||
* 场景加载回调
|
||||
* Scene loaded callback
|
||||
*/
|
||||
onSceneLoaded?(scenePath: string): void;
|
||||
|
||||
/**
|
||||
* 场景保存前回调
|
||||
* Before scene save callback
|
||||
*/
|
||||
onSceneSaving?(scenePath: string): boolean | void;
|
||||
|
||||
/**
|
||||
* 设置语言
|
||||
* Set locale
|
||||
*/
|
||||
setLocale?(locale: string): void;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 加载阶段 | Loading Phase
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 加载阶段 - 控制插件模块的加载顺序
|
||||
* Loading phase - controls the loading order of plugin modules
|
||||
*/
|
||||
export type LoadingPhase =
|
||||
| 'earliest' // 最早加载(核心模块) | Earliest (core modules)
|
||||
| 'preDefault' // 默认之前 | Before default
|
||||
| 'default' // 默认阶段 | Default phase
|
||||
| 'postDefault' // 默认之后 | After default
|
||||
| 'postEngine'; // 引擎初始化后 | After engine init
|
||||
|
||||
// ============================================================================
|
||||
// 系统上下文 | System Context
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 系统创建上下文
|
||||
* System creation context
|
||||
*
|
||||
* 包含运行时配置和插件服务注册表。
|
||||
* Contains runtime configuration and plugin service registry.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 导入需要的 Token | Import needed tokens
|
||||
* import { Physics2DQueryToken } from '@esengine/physics-rapier2d';
|
||||
* import { AssetManagerToken } from '@esengine/asset-system';
|
||||
*
|
||||
* // 注册服务 | Register service
|
||||
* context.services.register(Physics2DQueryToken, physicsSystem);
|
||||
*
|
||||
* // 获取服务(可选)| Get service (optional)
|
||||
* const physics = context.services.get(Physics2DQueryToken);
|
||||
*
|
||||
* // 获取服务(必需)| Get service (required)
|
||||
* const physics = context.services.require(Physics2DQueryToken);
|
||||
* ```
|
||||
*/
|
||||
export interface SystemContext {
|
||||
/** 是否为编辑器模式 | Is editor mode */
|
||||
readonly isEditor: boolean;
|
||||
|
||||
/**
|
||||
* 插件服务注册表
|
||||
* Plugin service registry
|
||||
*
|
||||
* 用于跨插件共享服务。
|
||||
* For sharing services between plugins.
|
||||
*/
|
||||
readonly services: PluginServiceRegistry;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 运行时模块 | Runtime Module
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 运行时模块接口
|
||||
* Runtime module interface
|
||||
*/
|
||||
export interface IRuntimeModule {
|
||||
/**
|
||||
* 注册组件到 ComponentRegistry
|
||||
* Register components to ComponentRegistry
|
||||
*/
|
||||
registerComponents?(registry: IComponentRegistry): void;
|
||||
|
||||
/**
|
||||
* 注册服务到 ServiceContainer
|
||||
* Register services to ServiceContainer
|
||||
*/
|
||||
registerServices?(services: ServiceContainer): void;
|
||||
|
||||
/**
|
||||
* 为场景创建系统
|
||||
* Create systems for scene
|
||||
*/
|
||||
createSystems?(scene: IScene, context: SystemContext): void;
|
||||
|
||||
/**
|
||||
* 所有系统创建完成后调用
|
||||
* Called after all systems are created
|
||||
*/
|
||||
onSystemsCreated?(scene: IScene, context: SystemContext): void;
|
||||
|
||||
/**
|
||||
* 模块初始化完成回调
|
||||
* Module initialization complete callback
|
||||
*/
|
||||
onInitialize?(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 模块销毁回调
|
||||
* Module destroy callback
|
||||
*/
|
||||
onDestroy?(): void;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 插件接口 | Plugin Interface
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 插件接口
|
||||
* Plugin interface
|
||||
*
|
||||
* 这是所有插件包导出的统一类型。
|
||||
* 使用泛型允许编辑器模块使用更具体的类型。
|
||||
*
|
||||
* This is the unified type that all plugin packages export.
|
||||
* Uses generics to allow editor modules to use more specific types.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 纯运行时插件 | Pure runtime plugin
|
||||
* const MyPlugin: IRuntimePlugin = {
|
||||
* manifest,
|
||||
* runtimeModule: new MyRuntimeModule()
|
||||
* };
|
||||
*
|
||||
* // 编辑器插件(在 editor-core 中定义 IEditorPlugin)
|
||||
* // Editor plugin (IEditorPlugin defined in editor-core)
|
||||
* const MyEditorPlugin: IEditorPlugin = {
|
||||
* manifest,
|
||||
* runtimeModule: new MyRuntimeModule(),
|
||||
* editorModule: new MyEditorModule()
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
export interface IRuntimePlugin<TEditorModule = unknown> {
|
||||
/** 模块清单 | Module manifest */
|
||||
readonly manifest: ModuleManifest;
|
||||
/** 运行时模块(可选) | Runtime module (optional) */
|
||||
readonly runtimeModule?: IRuntimeModule;
|
||||
/**
|
||||
* 编辑器模块(可选)
|
||||
* Editor module (optional)
|
||||
*
|
||||
* 泛型参数允许 editor-core 使用 IEditorModuleLoader 类型。
|
||||
* Generic parameter allows editor-core to use IEditorModuleLoader type.
|
||||
*/
|
||||
readonly editorModule?: TEditorModule;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Engine Core 插件 | Engine Core Plugin
|
||||
// ============================================================================
|
||||
|
||||
class EngineRuntimeModule implements IRuntimeModule {
|
||||
registerComponents(registry: IComponentRegistry): void {
|
||||
registry.register(TransformComponent);
|
||||
}
|
||||
}
|
||||
|
||||
const manifest: ModuleManifest = {
|
||||
id: 'engine-core',
|
||||
name: '@esengine/engine-core',
|
||||
displayName: 'Engine Core',
|
||||
description: 'Transform 等核心组件',
|
||||
version: '1.0.0',
|
||||
category: 'Core',
|
||||
icon: 'Box',
|
||||
isCore: true,
|
||||
defaultEnabled: true,
|
||||
isEngineModule: true,
|
||||
dependencies: ['core', 'math'],
|
||||
exports: {
|
||||
components: ['TransformComponent', 'HierarchyComponent'],
|
||||
systems: ['TransformSystem', 'HierarchySystem']
|
||||
}
|
||||
};
|
||||
|
||||
export const EnginePlugin: IRuntimePlugin = {
|
||||
manifest,
|
||||
runtimeModule: new EngineRuntimeModule()
|
||||
};
|
||||
2
packages/engine/engine-core/src/HierarchyComponent.ts
Normal file
2
packages/engine/engine-core/src/HierarchyComponent.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
// Re-export from ecs-framework
|
||||
export { HierarchyComponent } from '@esengine/ecs-framework';
|
||||
2
packages/engine/engine-core/src/HierarchySystem.ts
Normal file
2
packages/engine/engine-core/src/HierarchySystem.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
// Re-export from ecs-framework
|
||||
export { HierarchySystem } from '@esengine/ecs-framework';
|
||||
534
packages/engine/engine-core/src/Input/InputManager.ts
Normal file
534
packages/engine/engine-core/src/Input/InputManager.ts
Normal file
@@ -0,0 +1,534 @@
|
||||
/**
|
||||
* 输入管理器 - 统一管理所有输入状态
|
||||
* Input Manager - Unified input state management
|
||||
*
|
||||
* 提供简单易用的 API 供游戏代码查询输入状态。
|
||||
* Provides simple API for game code to query input state.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 在游戏系统中使用 | Use in game system
|
||||
* class PlayerMovementSystem extends EntitySystem {
|
||||
* protected process(entities: readonly Entity[]): void {
|
||||
* const input = Input;
|
||||
*
|
||||
* for (const entity of entities) {
|
||||
* const transform = entity.getComponent(TransformComponent);
|
||||
*
|
||||
* // 键盘移动 | Keyboard movement
|
||||
* if (input.isKeyDown('KeyW') || input.isKeyDown('ArrowUp')) {
|
||||
* transform.position.y -= 5;
|
||||
* }
|
||||
* if (input.isKeyDown('KeyS') || input.isKeyDown('ArrowDown')) {
|
||||
* transform.position.y += 5;
|
||||
* }
|
||||
*
|
||||
* // 鼠标点击 | Mouse click
|
||||
* if (input.isMouseButtonJustPressed(MouseButton.Left)) {
|
||||
* console.log('Clicked at:', input.mousePosition);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { MouseButton } from '@esengine/platform-common';
|
||||
import type { KeyboardEventInfo, MouseEventInfo, WheelEventInfo, TouchInfo } from '@esengine/platform-common';
|
||||
import type { IVector2 } from '@esengine/ecs-framework-math';
|
||||
|
||||
/**
|
||||
* 按键状态
|
||||
* Key state
|
||||
*/
|
||||
export interface KeyState {
|
||||
/** 是否按下 | Is pressed */
|
||||
pressed: boolean;
|
||||
/** 本帧刚按下 | Just pressed this frame */
|
||||
justPressed: boolean;
|
||||
/** 本帧刚释放 | Just released this frame */
|
||||
justReleased: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 鼠标按钮状态
|
||||
* Mouse button state
|
||||
*/
|
||||
export interface MouseButtonState {
|
||||
/** 是否按下 | Is pressed */
|
||||
pressed: boolean;
|
||||
/** 本帧刚按下 | Just pressed this frame */
|
||||
justPressed: boolean;
|
||||
/** 本帧刚释放 | Just released this frame */
|
||||
justReleased: boolean;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 输入管理器类
|
||||
* Input Manager class
|
||||
*/
|
||||
export class InputManager {
|
||||
// ========== 键盘状态 | Keyboard state ==========
|
||||
private _keyStates: Map<string, KeyState> = new Map();
|
||||
private _keysJustPressed: Set<string> = new Set();
|
||||
private _keysJustReleased: Set<string> = new Set();
|
||||
|
||||
// ========== 鼠标状态 | Mouse state ==========
|
||||
private _mousePosition: IVector2 = { x: 0, y: 0 };
|
||||
private _mouseMovement: IVector2 = { x: 0, y: 0 };
|
||||
private _mouseButtonStates: Map<MouseButton, MouseButtonState> = new Map();
|
||||
private _mouseButtonsJustPressed: Set<MouseButton> = new Set();
|
||||
private _mouseButtonsJustReleased: Set<MouseButton> = new Set();
|
||||
private _scrollDelta: IVector2 = { x: 0, y: 0 };
|
||||
|
||||
// ========== 触摸状态 | Touch state ==========
|
||||
private _touches: Map<number, TouchInfo> = new Map();
|
||||
private _touchesJustStarted: Set<number> = new Set();
|
||||
private _touchesJustEnded: Set<number> = new Set();
|
||||
|
||||
// ========== 修饰键状态 | Modifier key state ==========
|
||||
private _altKey: boolean = false;
|
||||
private _ctrlKey: boolean = false;
|
||||
private _shiftKey: boolean = false;
|
||||
private _metaKey: boolean = false;
|
||||
|
||||
constructor() {
|
||||
// 初始化鼠标按钮状态 | Initialize mouse button states
|
||||
this._mouseButtonStates.set(MouseButton.Left, { pressed: false, justPressed: false, justReleased: false });
|
||||
this._mouseButtonStates.set(MouseButton.Middle, { pressed: false, justPressed: false, justReleased: false });
|
||||
this._mouseButtonStates.set(MouseButton.Right, { pressed: false, justPressed: false, justReleased: false });
|
||||
}
|
||||
|
||||
// ========== 键盘 API | Keyboard API ==========
|
||||
|
||||
/**
|
||||
* 检查按键是否按下
|
||||
* Check if a key is pressed
|
||||
*
|
||||
* @param code 按键代码 (如 'KeyW', 'Space', 'ArrowUp') | Key code
|
||||
*/
|
||||
isKeyDown(code: string): boolean {
|
||||
return this._keyStates.get(code)?.pressed ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查按键是否本帧刚按下
|
||||
* Check if a key was just pressed this frame
|
||||
*
|
||||
* @param code 按键代码 | Key code
|
||||
*/
|
||||
isKeyJustPressed(code: string): boolean {
|
||||
return this._keyStates.get(code)?.justPressed ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查按键是否本帧刚释放
|
||||
* Check if a key was just released this frame
|
||||
*
|
||||
* @param code 按键代码 | Key code
|
||||
*/
|
||||
isKeyJustReleased(code: string): boolean {
|
||||
return this._keyStates.get(code)?.justReleased ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有按下的按键
|
||||
* Get all pressed keys
|
||||
*/
|
||||
getPressedKeys(): string[] {
|
||||
const result: string[] = [];
|
||||
this._keyStates.forEach((state, code) => {
|
||||
if (state.pressed) {
|
||||
result.push(code);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// ========== 鼠标 API | Mouse API ==========
|
||||
|
||||
/**
|
||||
* 获取鼠标位置(屏幕坐标)
|
||||
* Get mouse position (screen coordinates)
|
||||
*/
|
||||
get mousePosition(): Readonly<IVector2> {
|
||||
return this._mousePosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取鼠标本帧移动量
|
||||
* Get mouse movement this frame
|
||||
*/
|
||||
get mouseMovement(): Readonly<IVector2> {
|
||||
return this._mouseMovement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取滚轮滚动量
|
||||
* Get scroll delta
|
||||
*/
|
||||
get scrollDelta(): Readonly<IVector2> {
|
||||
return this._scrollDelta;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查鼠标按钮是否按下
|
||||
* Check if a mouse button is pressed
|
||||
*
|
||||
* @param button 鼠标按钮 | Mouse button
|
||||
*/
|
||||
isMouseButtonDown(button: MouseButton): boolean {
|
||||
return this._mouseButtonStates.get(button)?.pressed ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查鼠标按钮是否本帧刚按下
|
||||
* Check if a mouse button was just pressed this frame
|
||||
*
|
||||
* @param button 鼠标按钮 | Mouse button
|
||||
*/
|
||||
isMouseButtonJustPressed(button: MouseButton): boolean {
|
||||
return this._mouseButtonStates.get(button)?.justPressed ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查鼠标按钮是否本帧刚释放
|
||||
* Check if a mouse button was just released this frame
|
||||
*
|
||||
* @param button 鼠标按钮 | Mouse button
|
||||
*/
|
||||
isMouseButtonJustReleased(button: MouseButton): boolean {
|
||||
return this._mouseButtonStates.get(button)?.justReleased ?? false;
|
||||
}
|
||||
|
||||
// ========== 触摸 API | Touch API ==========
|
||||
|
||||
/**
|
||||
* 获取所有触摸点
|
||||
* Get all touch points
|
||||
*/
|
||||
get touches(): ReadonlyMap<number, TouchInfo> {
|
||||
return this._touches;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取触摸点数量
|
||||
* Get touch count
|
||||
*/
|
||||
get touchCount(): number {
|
||||
return this._touches.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定触摸点
|
||||
* Get a specific touch point
|
||||
*
|
||||
* @param identifier 触摸点标识符 | Touch identifier
|
||||
*/
|
||||
getTouch(identifier: number): TouchInfo | undefined {
|
||||
return this._touches.get(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有触摸
|
||||
* Check if there are any touches
|
||||
*/
|
||||
get isTouching(): boolean {
|
||||
return this._touches.size > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查触摸点是否本帧刚开始
|
||||
* Check if a touch just started this frame
|
||||
*
|
||||
* @param identifier 触摸点标识符 | Touch identifier
|
||||
*/
|
||||
isTouchJustStarted(identifier: number): boolean {
|
||||
return this._touchesJustStarted.has(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查触摸点是否本帧刚结束
|
||||
* Check if a touch just ended this frame
|
||||
*
|
||||
* @param identifier 触摸点标识符 | Touch identifier
|
||||
*/
|
||||
isTouchJustEnded(identifier: number): boolean {
|
||||
return this._touchesJustEnded.has(identifier);
|
||||
}
|
||||
|
||||
// ========== 修饰键 API | Modifier keys API ==========
|
||||
|
||||
/** Alt 键是否按下 | Is Alt key pressed */
|
||||
get altKey(): boolean { return this._altKey; }
|
||||
|
||||
/** Ctrl 键是否按下 | Is Ctrl key pressed */
|
||||
get ctrlKey(): boolean { return this._ctrlKey; }
|
||||
|
||||
/** Shift 键是否按下 | Is Shift key pressed */
|
||||
get shiftKey(): boolean { return this._shiftKey; }
|
||||
|
||||
/** Meta 键是否按下 (Windows/Command) | Is Meta key pressed */
|
||||
get metaKey(): boolean { return this._metaKey; }
|
||||
|
||||
// ========== 内部更新方法 | Internal update methods ==========
|
||||
|
||||
/**
|
||||
* 处理键盘按下事件
|
||||
* Handle key down event
|
||||
* @internal
|
||||
*/
|
||||
handleKeyDown(event: KeyboardEventInfo): void {
|
||||
let state = this._keyStates.get(event.code);
|
||||
if (!state) {
|
||||
state = { pressed: false, justPressed: false, justReleased: false };
|
||||
this._keyStates.set(event.code, state);
|
||||
}
|
||||
|
||||
if (!state.pressed && !event.repeat) {
|
||||
state.justPressed = true;
|
||||
this._keysJustPressed.add(event.code);
|
||||
}
|
||||
state.pressed = true;
|
||||
|
||||
// 更新修饰键 | Update modifier keys
|
||||
this._altKey = event.altKey;
|
||||
this._ctrlKey = event.ctrlKey;
|
||||
this._shiftKey = event.shiftKey;
|
||||
this._metaKey = event.metaKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理键盘释放事件
|
||||
* Handle key up event
|
||||
* @internal
|
||||
*/
|
||||
handleKeyUp(event: KeyboardEventInfo): void {
|
||||
let state = this._keyStates.get(event.code);
|
||||
if (!state) {
|
||||
state = { pressed: false, justPressed: false, justReleased: false };
|
||||
this._keyStates.set(event.code, state);
|
||||
}
|
||||
|
||||
if (state.pressed) {
|
||||
state.justReleased = true;
|
||||
this._keysJustReleased.add(event.code);
|
||||
}
|
||||
state.pressed = false;
|
||||
|
||||
// 更新修饰键 | Update modifier keys
|
||||
this._altKey = event.altKey;
|
||||
this._ctrlKey = event.ctrlKey;
|
||||
this._shiftKey = event.shiftKey;
|
||||
this._metaKey = event.metaKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理鼠标移动事件
|
||||
* Handle mouse move event
|
||||
* @internal
|
||||
*/
|
||||
handleMouseMove(event: MouseEventInfo): void {
|
||||
this._mousePosition.x = event.x;
|
||||
this._mousePosition.y = event.y;
|
||||
this._mouseMovement.x += event.movementX;
|
||||
this._mouseMovement.y += event.movementY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理鼠标按下事件
|
||||
* Handle mouse down event
|
||||
* @internal
|
||||
*/
|
||||
handleMouseDown(event: MouseEventInfo): void {
|
||||
const button = event.button as MouseButton;
|
||||
let state = this._mouseButtonStates.get(button);
|
||||
if (!state) {
|
||||
state = { pressed: false, justPressed: false, justReleased: false };
|
||||
this._mouseButtonStates.set(button, state);
|
||||
}
|
||||
|
||||
if (!state.pressed) {
|
||||
state.justPressed = true;
|
||||
this._mouseButtonsJustPressed.add(button);
|
||||
}
|
||||
state.pressed = true;
|
||||
|
||||
this._mousePosition.x = event.x;
|
||||
this._mousePosition.y = event.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理鼠标释放事件
|
||||
* Handle mouse up event
|
||||
* @internal
|
||||
*/
|
||||
handleMouseUp(event: MouseEventInfo): void {
|
||||
const button = event.button as MouseButton;
|
||||
let state = this._mouseButtonStates.get(button);
|
||||
if (!state) {
|
||||
state = { pressed: false, justPressed: false, justReleased: false };
|
||||
this._mouseButtonStates.set(button, state);
|
||||
}
|
||||
|
||||
if (state.pressed) {
|
||||
state.justReleased = true;
|
||||
this._mouseButtonsJustReleased.add(button);
|
||||
}
|
||||
state.pressed = false;
|
||||
|
||||
this._mousePosition.x = event.x;
|
||||
this._mousePosition.y = event.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理鼠标滚轮事件
|
||||
* Handle mouse wheel event
|
||||
* @internal
|
||||
*/
|
||||
handleWheel(event: WheelEventInfo): void {
|
||||
this._scrollDelta.x += event.deltaX;
|
||||
this._scrollDelta.y += event.deltaY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理触摸开始事件
|
||||
* Handle touch start event
|
||||
* @internal
|
||||
*/
|
||||
handleTouchStart(touches: TouchInfo[]): void {
|
||||
for (const touch of touches) {
|
||||
this._touches.set(touch.identifier, touch);
|
||||
this._touchesJustStarted.add(touch.identifier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理触摸移动事件
|
||||
* Handle touch move event
|
||||
* @internal
|
||||
*/
|
||||
handleTouchMove(touches: TouchInfo[]): void {
|
||||
for (const touch of touches) {
|
||||
this._touches.set(touch.identifier, touch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理触摸结束事件
|
||||
* Handle touch end event
|
||||
* @internal
|
||||
*/
|
||||
handleTouchEnd(touches: TouchInfo[]): void {
|
||||
for (const touch of touches) {
|
||||
this._touches.delete(touch.identifier);
|
||||
this._touchesJustEnded.add(touch.identifier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 帧末清理临时状态
|
||||
* Clear temporary state at end of frame
|
||||
* @internal
|
||||
*/
|
||||
endFrame(): void {
|
||||
// 清理键盘帧状态 | Clear keyboard frame state
|
||||
for (const code of this._keysJustPressed) {
|
||||
const state = this._keyStates.get(code);
|
||||
if (state) {
|
||||
state.justPressed = false;
|
||||
}
|
||||
}
|
||||
this._keysJustPressed.clear();
|
||||
|
||||
for (const code of this._keysJustReleased) {
|
||||
const state = this._keyStates.get(code);
|
||||
if (state) {
|
||||
state.justReleased = false;
|
||||
}
|
||||
}
|
||||
this._keysJustReleased.clear();
|
||||
|
||||
// 清理鼠标帧状态 | Clear mouse frame state
|
||||
for (const button of this._mouseButtonsJustPressed) {
|
||||
const state = this._mouseButtonStates.get(button);
|
||||
if (state) {
|
||||
state.justPressed = false;
|
||||
}
|
||||
}
|
||||
this._mouseButtonsJustPressed.clear();
|
||||
|
||||
for (const button of this._mouseButtonsJustReleased) {
|
||||
const state = this._mouseButtonStates.get(button);
|
||||
if (state) {
|
||||
state.justReleased = false;
|
||||
}
|
||||
}
|
||||
this._mouseButtonsJustReleased.clear();
|
||||
|
||||
// 清理鼠标移动和滚动 | Clear mouse movement and scroll
|
||||
this._mouseMovement.x = 0;
|
||||
this._mouseMovement.y = 0;
|
||||
this._scrollDelta.x = 0;
|
||||
this._scrollDelta.y = 0;
|
||||
|
||||
// 清理触摸帧状态 | Clear touch frame state
|
||||
this._touchesJustStarted.clear();
|
||||
this._touchesJustEnded.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置所有输入状态
|
||||
* Reset all input state
|
||||
*/
|
||||
reset(): void {
|
||||
this._keyStates.clear();
|
||||
this._keysJustPressed.clear();
|
||||
this._keysJustReleased.clear();
|
||||
|
||||
this._mousePosition = { x: 0, y: 0 };
|
||||
this._mouseMovement = { x: 0, y: 0 };
|
||||
this._scrollDelta = { x: 0, y: 0 };
|
||||
this._mouseButtonStates.forEach(state => {
|
||||
state.pressed = false;
|
||||
state.justPressed = false;
|
||||
state.justReleased = false;
|
||||
});
|
||||
this._mouseButtonsJustPressed.clear();
|
||||
this._mouseButtonsJustReleased.clear();
|
||||
|
||||
this._touches.clear();
|
||||
this._touchesJustStarted.clear();
|
||||
this._touchesJustEnded.clear();
|
||||
|
||||
this._altKey = false;
|
||||
this._ctrlKey = false;
|
||||
this._shiftKey = false;
|
||||
this._metaKey = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局输入管理器实例
|
||||
* Global input manager instance
|
||||
*
|
||||
* 游戏代码可以直接使用这个实例查询输入状态。
|
||||
* Game code can use this instance directly to query input state.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { Input, MouseButton } from '@esengine/engine-core';
|
||||
*
|
||||
* // 检查按键
|
||||
* if (Input.isKeyDown('KeyW')) {
|
||||
* // 向上移动
|
||||
* }
|
||||
*
|
||||
* // 检查鼠标
|
||||
* if (Input.isMouseButtonJustPressed(MouseButton.Left)) {
|
||||
* console.log('点击位置:', Input.mousePosition);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const Input = new InputManager();
|
||||
264
packages/engine/engine-core/src/Input/InputSystem.ts
Normal file
264
packages/engine/engine-core/src/Input/InputSystem.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* 输入系统 - 将平台输入事件连接到 InputManager
|
||||
* Input System - Connects platform input events to InputManager
|
||||
*
|
||||
* 在 ECS 更新循环中运行,负责:
|
||||
* 1. 在帧开始时已经由事件驱动更新了 InputManager
|
||||
* 2. 在帧末清理临时状态(justPressed, justReleased 等)
|
||||
*
|
||||
* Runs in ECS update loop, responsible for:
|
||||
* 1. InputManager is already updated by events at frame start
|
||||
* 2. Clear temporary state at frame end (justPressed, justReleased, etc.)
|
||||
*/
|
||||
|
||||
import { EntitySystem, Matcher, ECSSystem } from '@esengine/ecs-framework';
|
||||
import type { Entity } from '@esengine/ecs-framework';
|
||||
import type {
|
||||
IPlatformInputSubsystem,
|
||||
KeyboardEventInfo,
|
||||
MouseEventInfo,
|
||||
WheelEventInfo,
|
||||
TouchEvent
|
||||
} from '@esengine/platform-common';
|
||||
import { Input, InputManager } from './InputManager';
|
||||
|
||||
/**
|
||||
* 输入系统配置
|
||||
* Input system configuration
|
||||
*/
|
||||
export interface InputSystemConfig {
|
||||
/**
|
||||
* 输入管理器实例,默认使用全局 Input
|
||||
* Input manager instance, defaults to global Input
|
||||
*/
|
||||
inputManager?: InputManager;
|
||||
|
||||
/**
|
||||
* 是否在编辑器模式下禁用(防止与编辑器输入冲突)
|
||||
* Whether to disable in editor mode (prevent conflict with editor input)
|
||||
*/
|
||||
disableInEditor?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入系统
|
||||
* Input System
|
||||
*
|
||||
* 处理平台输入事件并更新 InputManager 状态。
|
||||
* Handles platform input events and updates InputManager state.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 在 GameRuntime 中注册
|
||||
* const inputSystem = new InputSystem({
|
||||
* inputSubsystem: webInputSubsystem
|
||||
* });
|
||||
* scene.addSystem(inputSystem);
|
||||
*
|
||||
* // 在游戏系统中使用
|
||||
* import { Input, MouseButton } from '@esengine/engine-core';
|
||||
*
|
||||
* class PlayerSystem extends EntitySystem {
|
||||
* protected process(entities: readonly Entity[]): void {
|
||||
* if (Input.isKeyDown('KeyW')) {
|
||||
* // 移动玩家
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@ECSSystem('InputSystem', { updateOrder: -1000 }) // 最先更新 | Update first
|
||||
export class InputSystem extends EntitySystem {
|
||||
private _inputManager: InputManager;
|
||||
private _inputSubsystem: IPlatformInputSubsystem | null = null;
|
||||
private _disableInEditor: boolean;
|
||||
private _isInitialized: boolean = false;
|
||||
|
||||
constructor(config: InputSystemConfig = {}) {
|
||||
// 不匹配任何实体,只用于生命周期 | Match no entities, only for lifecycle
|
||||
super(Matcher.nothing());
|
||||
|
||||
this._inputManager = config.inputManager ?? Input;
|
||||
this._disableInEditor = config.disableInEditor ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置平台输入子系统
|
||||
* Set platform input subsystem
|
||||
*
|
||||
* @param subsystem 平台输入子系统 | Platform input subsystem
|
||||
*/
|
||||
setInputSubsystem(subsystem: IPlatformInputSubsystem): void {
|
||||
// 如果已有子系统,先解绑 | Unbind if already has subsystem
|
||||
if (this._inputSubsystem && this._isInitialized) {
|
||||
this.unbindEvents();
|
||||
}
|
||||
|
||||
this._inputSubsystem = subsystem;
|
||||
|
||||
// 如果已初始化,立即绑定 | Bind immediately if initialized
|
||||
if (this._isInitialized) {
|
||||
this.bindEvents();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输入管理器
|
||||
* Get input manager
|
||||
*/
|
||||
get inputManager(): InputManager {
|
||||
return this._inputManager;
|
||||
}
|
||||
|
||||
protected override onInitialize(): void {
|
||||
this._isInitialized = true;
|
||||
|
||||
if (this._inputSubsystem) {
|
||||
this.bindEvents();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定平台输入事件
|
||||
* Bind platform input events
|
||||
*/
|
||||
private bindEvents(): void {
|
||||
if (!this._inputSubsystem) return;
|
||||
|
||||
const sub = this._inputSubsystem;
|
||||
|
||||
// 键盘事件 | Keyboard events
|
||||
if (sub.onKeyDown) {
|
||||
sub.onKeyDown(this._handleKeyDown);
|
||||
}
|
||||
if (sub.onKeyUp) {
|
||||
sub.onKeyUp(this._handleKeyUp);
|
||||
}
|
||||
|
||||
// 鼠标事件 | Mouse events
|
||||
if (sub.onMouseMove) {
|
||||
sub.onMouseMove(this._handleMouseMove);
|
||||
}
|
||||
if (sub.onMouseDown) {
|
||||
sub.onMouseDown(this._handleMouseDown);
|
||||
}
|
||||
if (sub.onMouseUp) {
|
||||
sub.onMouseUp(this._handleMouseUp);
|
||||
}
|
||||
if (sub.onWheel) {
|
||||
sub.onWheel(this._handleWheel);
|
||||
}
|
||||
|
||||
// 触摸事件 | Touch events
|
||||
sub.onTouchStart(this._handleTouchStart);
|
||||
sub.onTouchMove(this._handleTouchMove);
|
||||
sub.onTouchEnd(this._handleTouchEnd);
|
||||
sub.onTouchCancel(this._handleTouchEnd); // 取消当作结束处理 | Treat cancel as end
|
||||
}
|
||||
|
||||
/**
|
||||
* 解绑平台输入事件
|
||||
* Unbind platform input events
|
||||
*/
|
||||
private unbindEvents(): void {
|
||||
if (!this._inputSubsystem) return;
|
||||
|
||||
const sub = this._inputSubsystem;
|
||||
|
||||
// 键盘事件 | Keyboard events
|
||||
if (sub.offKeyDown) {
|
||||
sub.offKeyDown(this._handleKeyDown);
|
||||
}
|
||||
if (sub.offKeyUp) {
|
||||
sub.offKeyUp(this._handleKeyUp);
|
||||
}
|
||||
|
||||
// 鼠标事件 | Mouse events
|
||||
if (sub.offMouseMove) {
|
||||
sub.offMouseMove(this._handleMouseMove);
|
||||
}
|
||||
if (sub.offMouseDown) {
|
||||
sub.offMouseDown(this._handleMouseDown);
|
||||
}
|
||||
if (sub.offMouseUp) {
|
||||
sub.offMouseUp(this._handleMouseUp);
|
||||
}
|
||||
if (sub.offWheel) {
|
||||
sub.offWheel(this._handleWheel);
|
||||
}
|
||||
|
||||
// 触摸事件 | Touch events
|
||||
sub.offTouchStart(this._handleTouchStart);
|
||||
sub.offTouchMove(this._handleTouchMove);
|
||||
sub.offTouchEnd(this._handleTouchEnd);
|
||||
sub.offTouchCancel(this._handleTouchEnd);
|
||||
}
|
||||
|
||||
// ========== 事件处理函数 | Event handlers ==========
|
||||
// 使用箭头函数保持 this 绑定 | Use arrow functions to preserve this binding
|
||||
|
||||
private _handleKeyDown = (event: KeyboardEventInfo): void => {
|
||||
this._inputManager.handleKeyDown(event);
|
||||
};
|
||||
|
||||
private _handleKeyUp = (event: KeyboardEventInfo): void => {
|
||||
this._inputManager.handleKeyUp(event);
|
||||
};
|
||||
|
||||
private _handleMouseMove = (event: MouseEventInfo): void => {
|
||||
this._inputManager.handleMouseMove(event);
|
||||
};
|
||||
|
||||
private _handleMouseDown = (event: MouseEventInfo): void => {
|
||||
this._inputManager.handleMouseDown(event);
|
||||
};
|
||||
|
||||
private _handleMouseUp = (event: MouseEventInfo): void => {
|
||||
this._inputManager.handleMouseUp(event);
|
||||
};
|
||||
|
||||
private _handleWheel = (event: WheelEventInfo): void => {
|
||||
this._inputManager.handleWheel(event);
|
||||
};
|
||||
|
||||
private _handleTouchStart = (event: TouchEvent): void => {
|
||||
this._inputManager.handleTouchStart(event.changedTouches);
|
||||
};
|
||||
|
||||
private _handleTouchMove = (event: TouchEvent): void => {
|
||||
this._inputManager.handleTouchMove(event.changedTouches);
|
||||
};
|
||||
|
||||
private _handleTouchEnd = (event: TouchEvent): void => {
|
||||
this._inputManager.handleTouchEnd(event.changedTouches);
|
||||
};
|
||||
|
||||
// ========== 系统生命周期 | System lifecycle ==========
|
||||
|
||||
protected override process(_entities: readonly Entity[]): void {
|
||||
// 不处理实体,仅用于生命周期 | No entity processing, only for lifecycle
|
||||
}
|
||||
|
||||
protected override lateProcess(_entities: readonly Entity[]): void {
|
||||
// 在帧末清理临时状态 | Clear temporary state at end of frame
|
||||
this._inputManager.endFrame();
|
||||
}
|
||||
|
||||
protected override onDestroy(): void {
|
||||
this.unbindEvents();
|
||||
this._inputManager.reset();
|
||||
this._isInitialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否应该启用输入
|
||||
* Check if input should be enabled
|
||||
*/
|
||||
protected override onCheckProcessing(): boolean {
|
||||
// 如果设置了编辑器模式禁用,检查场景是否在编辑器模式
|
||||
if (this._disableInEditor && this.scene?.isEditorMode) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
41
packages/engine/engine-core/src/Input/index.ts
Normal file
41
packages/engine/engine-core/src/Input/index.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 输入系统模块
|
||||
* Input System Module
|
||||
*
|
||||
* 提供统一的输入处理能力,支持键盘、鼠标和触摸输入。
|
||||
* Provides unified input handling for keyboard, mouse, and touch.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { Input, InputSystem, MouseButton } from '@esengine/engine-core';
|
||||
*
|
||||
* // 在游戏代码中查询输入状态
|
||||
* if (Input.isKeyDown('KeyW')) {
|
||||
* // 向上移动
|
||||
* }
|
||||
*
|
||||
* if (Input.isMouseButtonJustPressed(MouseButton.Left)) {
|
||||
* console.log('点击位置:', Input.mousePosition);
|
||||
* }
|
||||
*
|
||||
* // 触摸输入
|
||||
* if (Input.isTouching) {
|
||||
* for (const [id, touch] of Input.touches) {
|
||||
* console.log('触摸点:', id, touch.x, touch.y);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
export { InputManager, Input, type KeyState, type MouseButtonState } from './InputManager';
|
||||
export { InputSystem, type InputSystemConfig } from './InputSystem';
|
||||
|
||||
// 重导出平台公共类型 | Re-export platform common types
|
||||
export { MouseButton } from '@esengine/platform-common';
|
||||
export type {
|
||||
KeyboardEventInfo,
|
||||
MouseEventInfo,
|
||||
WheelEventInfo,
|
||||
TouchInfo,
|
||||
TouchEvent
|
||||
} from '@esengine/platform-common';
|
||||
301
packages/engine/engine-core/src/ModuleManifest.ts
Normal file
301
packages/engine/engine-core/src/ModuleManifest.ts
Normal file
@@ -0,0 +1,301 @@
|
||||
/**
|
||||
* Module Manifest Types.
|
||||
* 模块清单类型定义。
|
||||
*
|
||||
* This is the single source of truth for module/plugin configuration.
|
||||
* Each engine module should have a module.json file containing this information.
|
||||
* 这是模块/插件配置的唯一数据源。
|
||||
* 每个引擎模块应该有一个包含此信息的 module.json 文件。
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module category for organization in UI.
|
||||
* 用于 UI 组织的模块分类。
|
||||
*/
|
||||
export type ModuleCategory =
|
||||
| 'Core'
|
||||
| 'Rendering'
|
||||
| 'Physics'
|
||||
| 'AI'
|
||||
| 'Audio'
|
||||
| 'Networking'
|
||||
| 'Other';
|
||||
|
||||
/**
|
||||
* Platform requirements for the module.
|
||||
* 模块的平台要求。
|
||||
*/
|
||||
export type ModulePlatform = 'web' | 'desktop' | 'mobile';
|
||||
|
||||
/**
|
||||
* Module exports definition.
|
||||
* 模块导出定义。
|
||||
*/
|
||||
export interface ModuleExports {
|
||||
/** Exported component classes | 导出的组件类 */
|
||||
components?: string[];
|
||||
|
||||
/** Exported system classes | 导出的系统类 */
|
||||
systems?: string[];
|
||||
|
||||
/** Exported asset loaders | 导出的资源加载器 */
|
||||
loaders?: string[];
|
||||
|
||||
/** Other exported items | 其他导出项 */
|
||||
other?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Module manifest definition (Unified Plugin/Module config).
|
||||
* 模块清单定义(统一的插件/模块配置)。
|
||||
*
|
||||
* This interface matches the structure of module.json files.
|
||||
* 此接口匹配 module.json 文件的结构。
|
||||
*/
|
||||
export interface ModuleManifest {
|
||||
// ==================== Core Identifiers ====================
|
||||
|
||||
/** Unique module identifier | 唯一模块标识符 */
|
||||
id: string;
|
||||
|
||||
/** Package name (npm style) | 包名(npm 风格) */
|
||||
name: string;
|
||||
|
||||
/** Display name for UI | UI 显示名称 */
|
||||
displayName: string;
|
||||
|
||||
/** Module description | 模块描述 */
|
||||
description: string;
|
||||
|
||||
/** Module version | 模块版本 */
|
||||
version: string;
|
||||
|
||||
// ==================== Classification ====================
|
||||
|
||||
/** Category for grouping in UI | UI 分组分类 */
|
||||
category: ModuleCategory;
|
||||
|
||||
/** Tags for search and filtering | 用于搜索和过滤的标签 */
|
||||
tags?: string[];
|
||||
|
||||
/** Icon name (Lucide icon) | 图标名(Lucide 图标) */
|
||||
icon?: string;
|
||||
|
||||
// ==================== Lifecycle ====================
|
||||
|
||||
/** Whether this is a core module (cannot be disabled) | 是否为核心模块(不能禁用) */
|
||||
isCore: boolean;
|
||||
|
||||
/** Whether enabled by default in new projects | 新项目中是否默认启用 */
|
||||
defaultEnabled: boolean;
|
||||
|
||||
/** Whether this is an engine built-in module | 是否为引擎内置模块 */
|
||||
isEngineModule?: boolean;
|
||||
|
||||
// ==================== Content ====================
|
||||
|
||||
/** Whether this module can contain content/assets | 是否可以包含内容/资源 */
|
||||
canContainContent?: boolean;
|
||||
|
||||
/** Platform requirements | 平台要求 */
|
||||
platforms?: ModulePlatform[];
|
||||
|
||||
// ==================== Dependencies ====================
|
||||
|
||||
/** Module IDs this module depends on | 此模块依赖的模块 ID */
|
||||
dependencies: string[];
|
||||
|
||||
/**
|
||||
* External package dependencies that need to be included in import map.
|
||||
* 需要包含在 import map 中的外部包依赖。
|
||||
*
|
||||
* These are runtime dependencies that are dynamically imported by the module.
|
||||
* 这些是模块动态导入的运行时依赖。
|
||||
*
|
||||
* Example: ["@esengine/rapier2d"]
|
||||
*/
|
||||
externalDependencies?: string[];
|
||||
|
||||
// ==================== Exports ====================
|
||||
|
||||
/** Exported items for dependency checking | 导出项用于依赖检查 */
|
||||
exports: ModuleExports;
|
||||
|
||||
// ==================== Asset Configuration ====================
|
||||
// ==================== 资产配置 ====================
|
||||
|
||||
/**
|
||||
* Asset file extensions supported by this module.
|
||||
* 此模块支持的资产文件扩展名。
|
||||
*
|
||||
* Used by build pipeline to determine which files to copy and their types.
|
||||
* 构建管线使用此配置决定复制哪些文件及其类型。
|
||||
*
|
||||
* Example:
|
||||
* ```json
|
||||
* {
|
||||
* "assetExtensions": {
|
||||
* ".particle": "particle",
|
||||
* ".particle.json": "particle",
|
||||
* ".btree": "behavior-tree"
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
assetExtensions?: Record<string, string>;
|
||||
|
||||
// ==================== Editor Integration ====================
|
||||
|
||||
/**
|
||||
* Associated editor package name.
|
||||
* 关联的编辑器包名。
|
||||
* e.g., "@esengine/tilemap-editor" for "@esengine/tilemap"
|
||||
*/
|
||||
editorPackage?: string;
|
||||
|
||||
// ==================== Performance (auto-calculated at build time) ====================
|
||||
// ==================== 性能(构建时自动计算) ====================
|
||||
|
||||
/** JS bundle size in bytes | JS 包大小(字节) */
|
||||
jsSize?: number;
|
||||
|
||||
/** Whether this module requires WASM | 是否需要 WASM */
|
||||
requiresWasm?: boolean;
|
||||
|
||||
/** WASM file size in bytes | WASM 文件大小(字节) */
|
||||
wasmSize?: number;
|
||||
|
||||
/**
|
||||
* Unified WASM configuration for all WASM-related modules.
|
||||
* 统一的 WASM 配置,用于所有 WASM 相关模块。
|
||||
*
|
||||
* This replaces the legacy wasmPaths, runtimeWasmPath, and wasmBindings fields.
|
||||
* 此配置替代旧的 wasmPaths、runtimeWasmPath 和 wasmBindings 字段。
|
||||
*/
|
||||
wasmConfig?: {
|
||||
/**
|
||||
* List of WASM files to copy during build.
|
||||
* 构建时需要复制的 WASM 文件列表。
|
||||
*
|
||||
* Each entry specifies source location and output destination.
|
||||
* 每个条目指定源位置和输出目标。
|
||||
*/
|
||||
files: Array<{
|
||||
/**
|
||||
* Source file path relative to engine modules directory.
|
||||
* 源文件路径,相对于引擎模块目录。
|
||||
*
|
||||
* Supports multiple candidate paths (first existing one is used).
|
||||
* 支持多个候选路径(使用第一个存在的)。
|
||||
*
|
||||
* Example: ["rapier2d/pkg/rapier_wasm2d_bg.wasm", "rapier2d/rapier_wasm2d_bg.wasm"]
|
||||
*/
|
||||
src: string | string[];
|
||||
|
||||
/**
|
||||
* Destination path relative to build output directory.
|
||||
* 目标路径,相对于构建输出目录。
|
||||
*
|
||||
* Example: "wasm/rapier_wasm2d_bg.wasm" or "libs/es-engine/es_engine_bg.wasm"
|
||||
*/
|
||||
dst: string;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Runtime WASM path for dynamic loading (used by JS code at runtime).
|
||||
* 运行时 WASM 路径,用于动态加载(JS 代码在运行时使用)。
|
||||
*
|
||||
* This is the path that runtime code uses to fetch the WASM file.
|
||||
* 这是运行时代码用来获取 WASM 文件的路径。
|
||||
*
|
||||
* Example: "wasm/rapier_wasm2d_bg.wasm"
|
||||
*/
|
||||
runtimePath?: string;
|
||||
|
||||
/**
|
||||
* Whether this is the core engine WASM module.
|
||||
* 是否是核心引擎 WASM 模块。
|
||||
*
|
||||
* The core engine WASM (e.g., es_engine) must be initialized first
|
||||
* before the runtime can start. Only one module should have this flag.
|
||||
* 核心引擎 WASM(如 es_engine)必须在运行时启动前首先初始化。
|
||||
* 只有一个模块应该设置此标志。
|
||||
*/
|
||||
isEngineCore?: boolean;
|
||||
};
|
||||
|
||||
// ==================== Build Configuration ====================
|
||||
// ==================== 构建配置 ====================
|
||||
|
||||
/**
|
||||
* Output path for the built module file.
|
||||
* 构建后模块文件的输出路径。
|
||||
*
|
||||
* Example: "dist/index.mjs"
|
||||
*/
|
||||
outputPath?: string;
|
||||
|
||||
/**
|
||||
* Plugin export name for dynamic loading.
|
||||
* 用于动态加载的插件导出名。
|
||||
*
|
||||
* Example: "SpritePlugin"
|
||||
*/
|
||||
pluginExport?: string;
|
||||
|
||||
/**
|
||||
* Additional files to include when copying module.
|
||||
* 复制模块时需要包含的额外文件。
|
||||
*
|
||||
* Glob patterns relative to the module's dist directory.
|
||||
* 相对于模块 dist 目录的 glob 模式。
|
||||
*
|
||||
* Example: ["chunk-*.js", "worker.js"]
|
||||
*/
|
||||
includes?: string[];
|
||||
|
||||
// ==================== Build Pipeline Configuration ====================
|
||||
// ==================== 构建管线配置 ====================
|
||||
|
||||
/**
|
||||
* Core service exports that should be explicitly exported first.
|
||||
* 需要显式优先导出的核心服务。
|
||||
*
|
||||
* Used to avoid naming conflicts when re-exporting modules.
|
||||
* 用于避免重新导出模块时的命名冲突。
|
||||
*
|
||||
* Example: ["createServiceToken", "PluginServiceRegistry"]
|
||||
*/
|
||||
coreServiceExports?: string[];
|
||||
|
||||
/**
|
||||
* Whether this module is the runtime entry point (provides default export).
|
||||
* 此模块是否为运行时入口点(提供默认导出)。
|
||||
*
|
||||
* Only one module should have this set to true (typically platform-web).
|
||||
* 只有一个模块应该设置为 true(通常是 platform-web)。
|
||||
*/
|
||||
isRuntimeEntry?: boolean;
|
||||
|
||||
/**
|
||||
* Standard entry file names to search for user scripts.
|
||||
* 用于搜索用户脚本的标准入口文件名。
|
||||
*
|
||||
* Build pipeline will try these files in order.
|
||||
* 构建管线将按顺序尝试这些文件。
|
||||
*
|
||||
* Example: ["index.ts", "main.ts", "game.ts"]
|
||||
*/
|
||||
userScriptEntries?: string[];
|
||||
|
||||
/**
|
||||
* Additional external packages that user scripts might import.
|
||||
* 用户脚本可能导入的额外外部包。
|
||||
*
|
||||
* These packages will be marked as external during bundling.
|
||||
* 这些包在打包时将被标记为外部依赖。
|
||||
*
|
||||
* Example: ["@esengine/ecs-framework", "@esengine/core"]
|
||||
*/
|
||||
userScriptExternals?: string[];
|
||||
}
|
||||
121
packages/engine/engine-core/src/PluginServiceRegistry.ts
Normal file
121
packages/engine/engine-core/src/PluginServiceRegistry.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 插件服务令牌(engine-core 特定)
|
||||
* Plugin Service Tokens (engine-core specific)
|
||||
*
|
||||
* 核心类型 (PluginServiceRegistry, createServiceToken, ServiceToken) 从 @esengine/ecs-framework 导入。
|
||||
* 这里只定义 engine-core 特有的服务令牌。
|
||||
*
|
||||
* Core types (PluginServiceRegistry, createServiceToken, ServiceToken) are imported from @esengine/ecs-framework.
|
||||
* This file only defines engine-core specific service tokens.
|
||||
*/
|
||||
|
||||
import { createServiceToken } from '@esengine/ecs-framework';
|
||||
|
||||
// Re-export from ecs-framework for backwards compatibility
|
||||
export { PluginServiceRegistry, createServiceToken, type ServiceToken } from '@esengine/ecs-framework';
|
||||
|
||||
// ============================================================================
|
||||
// engine-core 内部 Token | engine-core Internal Tokens
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Transform 组件类型 | Transform component type
|
||||
*
|
||||
* 使用 any 类型以允许各模块使用自己的 ITransformComponent 接口定义。
|
||||
* Using any type to allow modules to use their own ITransformComponent interface definition.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const TransformTypeToken = createServiceToken<new (...args: any[]) => any>('transformType');
|
||||
|
||||
/**
|
||||
* Canvas 元素的服务令牌
|
||||
* Service token for the canvas element
|
||||
*/
|
||||
export const CanvasElementToken = createServiceToken<HTMLCanvasElement>('canvasElement');
|
||||
|
||||
// ============================================================================
|
||||
// 渲染服务接口 | Render Service Interfaces
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 纹理服务接口
|
||||
* Texture service interface
|
||||
*
|
||||
* 负责纹理的加载、状态查询和管理。
|
||||
* Responsible for texture loading, state querying, and management.
|
||||
*/
|
||||
export interface ITextureService {
|
||||
/** 加载纹理 | Load texture */
|
||||
loadTexture(id: number, url: string): Promise<void>;
|
||||
|
||||
/** 获取纹理加载状态 | Get texture loading state */
|
||||
getTextureState(id: number): string;
|
||||
|
||||
/** 检查纹理是否就绪 | Check if texture is ready */
|
||||
isTextureReady(id: number): boolean;
|
||||
|
||||
/** 获取正在加载的纹理数量 | Get loading texture count */
|
||||
getTextureLoadingCount(): number;
|
||||
|
||||
/** 异步加载纹理(等待完成)| Load texture async (wait for completion) */
|
||||
loadTextureAsync(id: number, url: string): Promise<void>;
|
||||
|
||||
/** 等待所有加载中的纹理完成 | Wait for all textures to load */
|
||||
waitForAllTextures(timeout?: number): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态图集服务接口
|
||||
* Dynamic atlas service interface
|
||||
*/
|
||||
export interface IDynamicAtlasService {
|
||||
/** 创建空白纹理 | Create blank texture */
|
||||
createBlankTexture(width: number, height: number): number;
|
||||
|
||||
/** 更新纹理区域 | Update texture region */
|
||||
updateTextureRegion(
|
||||
id: number,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
pixels: Uint8Array
|
||||
): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 坐标转换服务接口
|
||||
* Coordinate transform service interface
|
||||
*/
|
||||
export interface ICoordinateService {
|
||||
/** 屏幕坐标转世界坐标 | Screen to world */
|
||||
screenToWorld(screenX: number, screenY: number): { x: number; y: number };
|
||||
|
||||
/** 世界坐标转屏幕坐标 | World to screen */
|
||||
worldToScreen(worldX: number, worldY: number): { x: number; y: number };
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染配置服务接口
|
||||
* Render config service interface
|
||||
*/
|
||||
export interface IRenderConfigService {
|
||||
/** 设置清除颜色 | Set clear color */
|
||||
setClearColor(r: number, g: number, b: number, a: number): void;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 服务令牌 | Service Tokens
|
||||
// ============================================================================
|
||||
|
||||
/** 纹理服务令牌 | Texture service token */
|
||||
export const TextureServiceToken = createServiceToken<ITextureService>('textureService');
|
||||
|
||||
/** 动态图集服务令牌 | Dynamic atlas service token */
|
||||
export const DynamicAtlasServiceToken = createServiceToken<IDynamicAtlasService>('dynamicAtlasService');
|
||||
|
||||
/** 坐标转换服务令牌 | Coordinate service token */
|
||||
export const CoordinateServiceToken = createServiceToken<ICoordinateService>('coordinateService');
|
||||
|
||||
/** 渲染配置服务令牌 | Render config service token */
|
||||
export const RenderConfigServiceToken = createServiceToken<IRenderConfigService>('renderConfigService');
|
||||
347
packages/engine/engine-core/src/SortingLayer.ts
Normal file
347
packages/engine/engine-core/src/SortingLayer.ts
Normal file
@@ -0,0 +1,347 @@
|
||||
/**
|
||||
* 排序层系统 - 控制渲染顺序
|
||||
* Sorting Layer System - Controls render order
|
||||
*
|
||||
* 提供排序层和层内排序功能,用于精确控制 2D 对象的渲染顺序。
|
||||
* Provides sorting layer and order-in-layer functionality for precise 2D rendering order control.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 获取排序层管理器
|
||||
* const manager = SortingLayerManager.instance;
|
||||
*
|
||||
* // 获取层的全局顺序
|
||||
* const order = manager.getLayerOrder('UI'); // 200
|
||||
*
|
||||
* // 计算排序键(用于渲染排序)
|
||||
* const sortKey = manager.getSortKey('UI', 10); // 200 * 10000 + 10 = 2000010
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { createServiceToken } from './PluginServiceRegistry';
|
||||
|
||||
/**
|
||||
* 排序层配置
|
||||
* Sorting layer configuration
|
||||
*/
|
||||
export interface SortingLayerConfig {
|
||||
/** 层名称 | Layer name */
|
||||
name: string;
|
||||
/** 全局顺序(数值越大越靠前)| Global order (higher = rendered later/on top) */
|
||||
order: number;
|
||||
/**
|
||||
* 是否在屏幕空间渲染
|
||||
* Whether to render in screen space
|
||||
*
|
||||
* 屏幕空间层使用固定正交投影,不受世界相机影响。
|
||||
* Screen space layers use fixed orthographic projection, not affected by world camera.
|
||||
*/
|
||||
bScreenSpace?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 可排序接口 - 所有可渲染组件应实现此接口
|
||||
* Sortable interface - All renderable components should implement this
|
||||
*/
|
||||
export interface ISortable {
|
||||
/** 排序层名称 | Sorting layer name */
|
||||
sortingLayer: string;
|
||||
/** 层内顺序 | Order within layer */
|
||||
orderInLayer: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认排序层
|
||||
* Default sorting layers
|
||||
*
|
||||
* 渲染顺序(从后到前):
|
||||
* Render order (back to front):
|
||||
*
|
||||
* 世界空间 | World Space:
|
||||
* - Background (-100): 背景
|
||||
* - Default (0): 默认游戏对象
|
||||
* - Foreground (100): 前景对象
|
||||
* - WorldOverlay (150): 世界空间特效(技能、伤害数字等)
|
||||
*
|
||||
* 屏幕空间 | Screen Space:
|
||||
* - UI (200): UI 元素
|
||||
* - ScreenOverlay (300): 屏幕空间特效(点击特效、Toast)
|
||||
* - Modal (400): 模态对话框、全屏遮罩
|
||||
*/
|
||||
export const DEFAULT_SORTING_LAYERS: SortingLayerConfig[] = [
|
||||
// 世界空间层 | World space layers
|
||||
{ name: 'Background', order: -100 },
|
||||
{ name: 'Default', order: 0 },
|
||||
{ name: 'Foreground', order: 100 },
|
||||
{ name: 'WorldOverlay', order: 150 },
|
||||
|
||||
// 屏幕空间层 | Screen space layers
|
||||
{ name: 'UI', order: 200, bScreenSpace: true },
|
||||
{ name: 'ScreenOverlay', order: 300, bScreenSpace: true },
|
||||
{ name: 'Modal', order: 400, bScreenSpace: true },
|
||||
];
|
||||
|
||||
/**
|
||||
* 排序层名称常量
|
||||
* Sorting layer name constants
|
||||
*/
|
||||
export const SortingLayers = {
|
||||
Background: 'Background',
|
||||
Default: 'Default',
|
||||
Foreground: 'Foreground',
|
||||
WorldOverlay: 'WorldOverlay',
|
||||
UI: 'UI',
|
||||
ScreenOverlay: 'ScreenOverlay',
|
||||
Modal: 'Modal',
|
||||
} as const;
|
||||
|
||||
export type SortingLayerName = typeof SortingLayers[keyof typeof SortingLayers] | string;
|
||||
|
||||
/**
|
||||
* 排序层管理器接口
|
||||
* Sorting layer manager interface
|
||||
*/
|
||||
export interface ISortingLayerManager {
|
||||
/**
|
||||
* 获取所有排序层
|
||||
* Get all sorting layers
|
||||
*/
|
||||
getLayers(): readonly SortingLayerConfig[];
|
||||
|
||||
/**
|
||||
* 获取层的全局顺序
|
||||
* Get layer's global order
|
||||
*/
|
||||
getLayerOrder(layerName: string): number;
|
||||
|
||||
/**
|
||||
* 计算排序键
|
||||
* Calculate sort key
|
||||
*
|
||||
* sortKey = layerOrder * 10000 + orderInLayer
|
||||
*/
|
||||
getSortKey(layerName: string, orderInLayer: number): number;
|
||||
|
||||
/**
|
||||
* 添加自定义层
|
||||
* Add custom layer
|
||||
*/
|
||||
addLayer(name: string, order: number, bScreenSpace?: boolean): void;
|
||||
|
||||
/**
|
||||
* 移除自定义层
|
||||
* Remove custom layer
|
||||
*/
|
||||
removeLayer(name: string): boolean;
|
||||
|
||||
/**
|
||||
* 获取层名称列表(按顺序)
|
||||
* Get layer names in order
|
||||
*/
|
||||
getLayerNames(): string[];
|
||||
|
||||
/**
|
||||
* 检查层是否为屏幕空间层
|
||||
* Check if layer is screen space
|
||||
*/
|
||||
isScreenSpace(layerName: string): boolean;
|
||||
|
||||
/**
|
||||
* 获取所有屏幕空间层名称
|
||||
* Get all screen space layer names
|
||||
*/
|
||||
getScreenSpaceLayers(): string[];
|
||||
|
||||
/**
|
||||
* 获取所有世界空间层名称
|
||||
* Get all world space layer names
|
||||
*/
|
||||
getWorldSpaceLayers(): string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序层管理器
|
||||
* Sorting Layer Manager
|
||||
*
|
||||
* 管理渲染排序层,提供统一的排序键计算。
|
||||
* Manages render sorting layers and provides unified sort key calculation.
|
||||
*/
|
||||
export class SortingLayerManager implements ISortingLayerManager {
|
||||
private static _instance: SortingLayerManager | null = null;
|
||||
|
||||
private _layers: Map<string, SortingLayerConfig> = new Map();
|
||||
private _sortedLayers: SortingLayerConfig[] = [];
|
||||
|
||||
/**
|
||||
* 获取单例实例
|
||||
* Get singleton instance
|
||||
*/
|
||||
static get instance(): SortingLayerManager {
|
||||
if (!SortingLayerManager._instance) {
|
||||
SortingLayerManager._instance = new SortingLayerManager();
|
||||
}
|
||||
return SortingLayerManager._instance;
|
||||
}
|
||||
|
||||
constructor(layers?: SortingLayerConfig[]) {
|
||||
const initialLayers = layers ?? DEFAULT_SORTING_LAYERS;
|
||||
for (const layer of initialLayers) {
|
||||
this._layers.set(layer.name, layer);
|
||||
}
|
||||
this._updateSortedLayers();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有排序层(按顺序)
|
||||
* Get all sorting layers (in order)
|
||||
*/
|
||||
getLayers(): readonly SortingLayerConfig[] {
|
||||
return this._sortedLayers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取层的全局顺序
|
||||
* Get layer's global order
|
||||
*
|
||||
* @param layerName 层名称 | Layer name
|
||||
* @returns 全局顺序,未找到返回 0 | Global order, returns 0 if not found
|
||||
*/
|
||||
getLayerOrder(layerName: string): number {
|
||||
return this._layers.get(layerName)?.order ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算排序键
|
||||
* Calculate sort key
|
||||
*
|
||||
* 用于渲染排序,数值越大越后渲染(显示在上面)。
|
||||
* Used for render sorting, higher value = rendered later (on top).
|
||||
*
|
||||
* @param layerName 层名称 | Layer name
|
||||
* @param orderInLayer 层内顺序 | Order within layer
|
||||
* @returns 排序键 | Sort key
|
||||
*/
|
||||
getSortKey(layerName: string, orderInLayer: number): number {
|
||||
const layerOrder = this.getLayerOrder(layerName);
|
||||
// 使用 10000 作为乘数,允许 -9999 到 9999 的 orderInLayer
|
||||
// Use 10000 as multiplier, allows orderInLayer from -9999 to 9999
|
||||
return layerOrder * 10000 + orderInLayer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加自定义层
|
||||
* Add custom layer
|
||||
*
|
||||
* @param name 层名称 | Layer name
|
||||
* @param order 全局顺序 | Global order
|
||||
* @param bScreenSpace 是否为屏幕空间层 | Whether screen space layer
|
||||
*/
|
||||
addLayer(name: string, order: number, bScreenSpace: boolean = false): void {
|
||||
this._layers.set(name, { name, order, bScreenSpace });
|
||||
this._updateSortedLayers();
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除自定义层
|
||||
* Remove custom layer
|
||||
*
|
||||
* 注意:不能移除默认层。
|
||||
* Note: Cannot remove default layers.
|
||||
*
|
||||
* @param name 层名称 | Layer name
|
||||
* @returns 是否成功移除 | Whether successfully removed
|
||||
*/
|
||||
removeLayer(name: string): boolean {
|
||||
// 检查是否为默认层 | Check if default layer
|
||||
if (DEFAULT_SORTING_LAYERS.some(l => l.name === name)) {
|
||||
console.warn(`[SortingLayerManager] Cannot remove default layer: ${name}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = this._layers.delete(name);
|
||||
if (result) {
|
||||
this._updateSortedLayers();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取层名称列表(按顺序)
|
||||
* Get layer names in order
|
||||
*/
|
||||
getLayerNames(): string[] {
|
||||
return this._sortedLayers.map(l => l.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查层是否存在
|
||||
* Check if layer exists
|
||||
*/
|
||||
hasLayer(name: string): boolean {
|
||||
return this._layers.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查层是否为屏幕空间层
|
||||
* Check if layer is screen space
|
||||
*
|
||||
* @param layerName 层名称 | Layer name
|
||||
* @returns 是否为屏幕空间层,未找到返回 false | Whether screen space, returns false if not found
|
||||
*/
|
||||
isScreenSpace(layerName: string): boolean {
|
||||
return this._layers.get(layerName)?.bScreenSpace ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有屏幕空间层名称
|
||||
* Get all screen space layer names
|
||||
*/
|
||||
getScreenSpaceLayers(): string[] {
|
||||
return this._sortedLayers
|
||||
.filter(l => l.bScreenSpace)
|
||||
.map(l => l.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有世界空间层名称
|
||||
* Get all world space layer names
|
||||
*/
|
||||
getWorldSpaceLayers(): string[] {
|
||||
return this._sortedLayers
|
||||
.filter(l => !l.bScreenSpace)
|
||||
.map(l => l.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置为默认层
|
||||
* Reset to default layers
|
||||
*/
|
||||
reset(): void {
|
||||
this._layers.clear();
|
||||
for (const layer of DEFAULT_SORTING_LAYERS) {
|
||||
this._layers.set(layer.name, layer);
|
||||
}
|
||||
this._updateSortedLayers();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新排序后的层列表
|
||||
* Update sorted layer list
|
||||
*/
|
||||
private _updateSortedLayers(): void {
|
||||
this._sortedLayers = Array.from(this._layers.values())
|
||||
.sort((a, b) => a.order - b.order);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序层管理器服务令牌
|
||||
* Sorting layer manager service token
|
||||
*/
|
||||
export const SortingLayerManagerToken = createServiceToken<ISortingLayerManager>('sortingLayerManager');
|
||||
|
||||
/**
|
||||
* 全局排序层管理器实例
|
||||
* Global sorting layer manager instance
|
||||
*/
|
||||
export const sortingLayerManager = SortingLayerManager.instance;
|
||||
241
packages/engine/engine-core/src/TransformComponent.ts
Normal file
241
packages/engine/engine-core/src/TransformComponent.ts
Normal file
@@ -0,0 +1,241 @@
|
||||
import { Component, ECSComponent, Serializable, Serialize, Property } from '@esengine/ecs-framework';
|
||||
import type { IVector3 } from '@esengine/ecs-framework-math';
|
||||
|
||||
/**
|
||||
* 3x3 矩阵(用于 2D 变换:旋转 + 缩放)
|
||||
* 实际存储为 [a, b, c, d, tx, ty] 形式的仿射变换
|
||||
*
|
||||
* 3x3 matrix for 2D transforms (rotation + scale).
|
||||
* Stored as affine transform [a, b, c, d, tx, ty].
|
||||
*/
|
||||
export interface Matrix2D {
|
||||
a: number; // scaleX * cos(rotation)
|
||||
b: number; // scaleX * sin(rotation)
|
||||
c: number; // scaleY * -sin(rotation)
|
||||
d: number; // scaleY * cos(rotation)
|
||||
tx: number; // translateX
|
||||
ty: number; // translateY
|
||||
}
|
||||
|
||||
/**
|
||||
* Reactive Vector3 that automatically sets dirty flag on parent transform.
|
||||
* 响应式 Vector3,在修改时自动设置父变换的脏标记。
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ReactiveVector3 implements IVector3 {
|
||||
private _x: number;
|
||||
private _y: number;
|
||||
private _z: number;
|
||||
private _owner: TransformComponent;
|
||||
|
||||
constructor(owner: TransformComponent, x: number = 0, y: number = 0, z: number = 0) {
|
||||
this._owner = owner;
|
||||
this._x = x;
|
||||
this._y = y;
|
||||
this._z = z;
|
||||
}
|
||||
|
||||
get x(): number { return this._x; }
|
||||
set x(value: number) {
|
||||
if (this._x !== value) {
|
||||
this._x = value;
|
||||
this._owner.markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
get y(): number { return this._y; }
|
||||
set y(value: number) {
|
||||
if (this._y !== value) {
|
||||
this._y = value;
|
||||
this._owner.markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
get z(): number { return this._z; }
|
||||
set z(value: number) {
|
||||
if (this._z !== value) {
|
||||
this._z = value;
|
||||
this._owner.markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all components at once (more efficient than setting individually).
|
||||
* 一次性设置所有分量(比单独设置更高效)。
|
||||
*/
|
||||
set(x: number, y: number, z: number = this._z): void {
|
||||
const changed = this._x !== x || this._y !== y || this._z !== z;
|
||||
this._x = x;
|
||||
this._y = y;
|
||||
this._z = z;
|
||||
if (changed) {
|
||||
this._owner.markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy from another vector.
|
||||
* 从另一个向量复制。
|
||||
*/
|
||||
copyFrom(v: IVector3): void {
|
||||
this.set(v.x, v.y, v.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw values without triggering getters (for serialization).
|
||||
* 获取原始值而不触发 getter(用于序列化)。
|
||||
*/
|
||||
toObject(): IVector3 {
|
||||
return { x: this._x, y: this._y, z: this._z };
|
||||
}
|
||||
}
|
||||
|
||||
@ECSComponent('Transform')
|
||||
@Serializable({ version: 1, typeId: 'Transform' })
|
||||
export class TransformComponent extends Component {
|
||||
// ===== 内部响应式存储 =====
|
||||
private _position: ReactiveVector3;
|
||||
private _rotation: ReactiveVector3;
|
||||
private _scale: ReactiveVector3;
|
||||
|
||||
@Serialize()
|
||||
@Property({ type: 'vector3', label: 'Position' })
|
||||
get position(): IVector3 { return this._position; }
|
||||
set position(value: IVector3) {
|
||||
if (this._position) {
|
||||
this._position.copyFrom(value);
|
||||
} else {
|
||||
this._position = new ReactiveVector3(this, value.x, value.y, value.z);
|
||||
}
|
||||
}
|
||||
|
||||
/** 欧拉角,单位:度 | Euler angles in degrees */
|
||||
@Serialize()
|
||||
@Property({ type: 'vector3', label: 'Rotation' })
|
||||
get rotation(): IVector3 { return this._rotation; }
|
||||
set rotation(value: IVector3) {
|
||||
if (this._rotation) {
|
||||
this._rotation.copyFrom(value);
|
||||
} else {
|
||||
this._rotation = new ReactiveVector3(this, value.x, value.y, value.z);
|
||||
}
|
||||
}
|
||||
|
||||
@Serialize()
|
||||
@Property({ type: 'vector3', label: 'Scale' })
|
||||
get scale(): IVector3 { return this._scale; }
|
||||
set scale(value: IVector3) {
|
||||
if (this._scale) {
|
||||
this._scale.copyFrom(value);
|
||||
} else {
|
||||
this._scale = new ReactiveVector3(this, value.x, value.y, value.z);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 世界变换(由 TransformSystem 计算)=====
|
||||
|
||||
/** 世界位置(只读,由 TransformSystem 计算) */
|
||||
worldPosition: IVector3 = { x: 0, y: 0, z: 0 };
|
||||
|
||||
/** 世界旋转(只读,由 TransformSystem 计算) */
|
||||
worldRotation: IVector3 = { x: 0, y: 0, z: 0 };
|
||||
|
||||
/** 世界缩放(只读,由 TransformSystem 计算) */
|
||||
worldScale: IVector3 = { x: 1, y: 1, z: 1 };
|
||||
|
||||
/** 本地到世界的 2D 变换矩阵(只读,由 TransformSystem 计算) */
|
||||
localToWorldMatrix: Matrix2D = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
|
||||
|
||||
/** 变换是否需要更新 | Whether transform needs update */
|
||||
bDirty: boolean = true;
|
||||
|
||||
/**
|
||||
* Frame counter when last updated (for change detection).
|
||||
* 上次更新的帧计数器(用于变化检测)。
|
||||
* @internal
|
||||
*/
|
||||
_lastUpdateFrame: number = -1;
|
||||
|
||||
/**
|
||||
* Cached render data version (incremented when transform changes).
|
||||
* 缓存的渲染数据版本(变换改变时递增)。
|
||||
* @internal
|
||||
*/
|
||||
_version: number = 0;
|
||||
|
||||
constructor(x: number = 0, y: number = 0, z: number = 0) {
|
||||
super();
|
||||
this._position = new ReactiveVector3(this, x, y, z);
|
||||
this._rotation = new ReactiveVector3(this, 0, 0, 0);
|
||||
this._scale = new ReactiveVector3(this, 1, 1, 1);
|
||||
// 初始化世界变换为本地变换值(在 TransformSystem 更新前使用)
|
||||
this.worldPosition = { x, y, z };
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark transform as dirty and increment version.
|
||||
* 标记变换为脏并递增版本号。
|
||||
*/
|
||||
markDirty(): void {
|
||||
if (!this.bDirty) {
|
||||
this.bDirty = true;
|
||||
this._version++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear dirty flag (called by TransformSystem after update).
|
||||
* 清除脏标记(由 TransformSystem 更新后调用)。
|
||||
*/
|
||||
clearDirty(frameNumber: number): void {
|
||||
this.bDirty = false;
|
||||
this._lastUpdateFrame = frameNumber;
|
||||
}
|
||||
|
||||
setPosition(x: number, y: number, z: number = 0): this {
|
||||
this._position.set(x, y, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
setRotation(x: number, y: number, z: number): this {
|
||||
this._rotation.set(x, y, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
setScale(x: number, y: number, z: number = 1): this {
|
||||
this._scale.set(x, y, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将本地坐标转换为世界坐标
|
||||
*/
|
||||
localToWorld(localX: number, localY: number): { x: number; y: number } {
|
||||
const m = this.localToWorldMatrix;
|
||||
return {
|
||||
x: m.a * localX + m.c * localY + m.tx,
|
||||
y: m.b * localX + m.d * localY + m.ty
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将世界坐标转换为本地坐标
|
||||
*/
|
||||
worldToLocal(worldX: number, worldY: number): { x: number; y: number } {
|
||||
const m = this.localToWorldMatrix;
|
||||
const det = m.a * m.d - m.b * m.c;
|
||||
if (Math.abs(det) < 1e-10) {
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
|
||||
const invDet = 1 / det;
|
||||
const dx = worldX - m.tx;
|
||||
const dy = worldY - m.ty;
|
||||
|
||||
return {
|
||||
x: (m.d * dx - m.c * dy) * invDet,
|
||||
y: (-m.b * dx + m.a * dy) * invDet
|
||||
};
|
||||
}
|
||||
}
|
||||
146
packages/engine/engine-core/src/TransformSystem.ts
Normal file
146
packages/engine/engine-core/src/TransformSystem.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { EntitySystem, Matcher, Entity, ECSSystem, HierarchyComponent } from '@esengine/ecs-framework';
|
||||
import { TransformComponent, Matrix2D } from './TransformComponent';
|
||||
|
||||
const DEG_TO_RAD = Math.PI / 180;
|
||||
|
||||
/**
|
||||
* 变换系统
|
||||
* Transform System - Calculates world transforms based on hierarchy
|
||||
*
|
||||
* 根据实体层级关系计算世界变换矩阵。
|
||||
* 子实体的世界变换 = 父实体世界变换 * 子实体本地变换
|
||||
*/
|
||||
@ECSSystem('Transform', { updateOrder: -100 })
|
||||
export class TransformSystem extends EntitySystem {
|
||||
constructor() {
|
||||
super(Matcher.empty().all(TransformComponent));
|
||||
}
|
||||
|
||||
protected override process(entities: readonly Entity[]): void {
|
||||
// 获取所有根实体(没有父级或父级没有 TransformComponent)
|
||||
const rootEntities = entities.filter(e => {
|
||||
const hierarchy = e.getComponent(HierarchyComponent);
|
||||
if (!hierarchy || hierarchy.parentId === null) {
|
||||
return true;
|
||||
}
|
||||
const parent = this.scene?.findEntityById(hierarchy.parentId);
|
||||
return !parent || !parent.hasComponent(TransformComponent);
|
||||
});
|
||||
|
||||
// 从根实体开始递归计算世界变换
|
||||
for (const entity of rootEntities) {
|
||||
this.updateWorldTransform(entity, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归更新实体及其子实体的世界变换
|
||||
*/
|
||||
private updateWorldTransform(entity: Entity, parentMatrix: Matrix2D | null): void {
|
||||
const transform = entity.getComponent(TransformComponent);
|
||||
if (!transform) return;
|
||||
|
||||
// 计算本地变换矩阵
|
||||
const localMatrix = this.calculateLocalMatrix(transform);
|
||||
|
||||
// 计算世界变换矩阵
|
||||
if (parentMatrix) {
|
||||
// 世界矩阵 = 父矩阵 * 本地矩阵
|
||||
transform.localToWorldMatrix = this.multiplyMatrices(parentMatrix, localMatrix);
|
||||
} else {
|
||||
// 没有父级,本地矩阵就是世界矩阵
|
||||
transform.localToWorldMatrix = localMatrix;
|
||||
}
|
||||
|
||||
// 从世界矩阵提取世界位置、旋转、缩放
|
||||
this.decomposeMatrix(transform);
|
||||
|
||||
transform.bDirty = false;
|
||||
|
||||
// 递归处理子实体
|
||||
const hierarchy = entity.getComponent(HierarchyComponent);
|
||||
if (hierarchy && hierarchy.childIds.length > 0) {
|
||||
for (const childId of hierarchy.childIds) {
|
||||
const child = this.scene?.findEntityById(childId);
|
||||
if (child) {
|
||||
this.updateWorldTransform(child, transform.localToWorldMatrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算本地变换矩阵
|
||||
*/
|
||||
private calculateLocalMatrix(transform: TransformComponent): Matrix2D {
|
||||
const { position, rotation, scale } = transform;
|
||||
|
||||
// 只使用 z 轴旋转(2D)
|
||||
const rad = rotation.z * DEG_TO_RAD;
|
||||
const cos = Math.cos(rad);
|
||||
const sin = Math.sin(rad);
|
||||
|
||||
// 构建仿射变换矩阵: Scale -> Rotate -> Translate
|
||||
// 顺时针旋转 | Clockwise rotation
|
||||
// [a c tx] [sx 0 0] [cos sin 0] [1 0 tx]
|
||||
// [b d ty] = [0 sy 0] * [-sin cos 0] * [0 1 ty]
|
||||
// [0 0 1] [0 0 1] [0 0 1] [0 0 1]
|
||||
|
||||
return {
|
||||
a: scale.x * cos,
|
||||
b: -scale.x * sin,
|
||||
c: scale.y * sin,
|
||||
d: scale.y * cos,
|
||||
tx: position.x,
|
||||
ty: position.y
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 矩阵乘法: result = a * b
|
||||
*/
|
||||
private multiplyMatrices(a: Matrix2D, b: Matrix2D): Matrix2D {
|
||||
return {
|
||||
a: a.a * b.a + a.c * b.b,
|
||||
b: a.b * b.a + a.d * b.b,
|
||||
c: a.a * b.c + a.c * b.d,
|
||||
d: a.b * b.c + a.d * b.d,
|
||||
tx: a.a * b.tx + a.c * b.ty + a.tx,
|
||||
ty: a.b * b.tx + a.d * b.ty + a.ty
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 从世界矩阵分解出位置、旋转、缩放
|
||||
*/
|
||||
private decomposeMatrix(transform: TransformComponent): void {
|
||||
const m = transform.localToWorldMatrix;
|
||||
|
||||
// 位置直接从矩阵获取
|
||||
transform.worldPosition.x = m.tx;
|
||||
transform.worldPosition.y = m.ty;
|
||||
transform.worldPosition.z = transform.position.z;
|
||||
|
||||
// 计算缩放
|
||||
const scaleX = Math.sqrt(m.a * m.a + m.b * m.b);
|
||||
const scaleY = Math.sqrt(m.c * m.c + m.d * m.d);
|
||||
|
||||
// 检测负缩放(通过行列式符号)
|
||||
const det = m.a * m.d - m.b * m.c;
|
||||
const sign = det < 0 ? -1 : 1;
|
||||
|
||||
transform.worldScale.x = scaleX;
|
||||
transform.worldScale.y = scaleY * sign;
|
||||
transform.worldScale.z = transform.scale.z;
|
||||
|
||||
// 计算旋转(从归一化的矩阵)
|
||||
if (scaleX > 1e-10) {
|
||||
const rotation = Math.atan2(m.b / scaleX, m.a / scaleX);
|
||||
transform.worldRotation.z = rotation / DEG_TO_RAD;
|
||||
} else {
|
||||
transform.worldRotation.z = 0;
|
||||
}
|
||||
transform.worldRotation.x = transform.rotation.x;
|
||||
transform.worldRotation.y = transform.rotation.y;
|
||||
}
|
||||
}
|
||||
63
packages/engine/engine-core/src/index.ts
Normal file
63
packages/engine/engine-core/src/index.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
export { TransformComponent, type Matrix2D } from './TransformComponent';
|
||||
export { TransformSystem } from './TransformSystem';
|
||||
export { HierarchyComponent } from './HierarchyComponent';
|
||||
export { HierarchySystem } from './HierarchySystem';
|
||||
|
||||
export {
|
||||
EnginePlugin,
|
||||
// Type exports
|
||||
type LoadingPhase,
|
||||
type SystemContext,
|
||||
type IRuntimeModule,
|
||||
type IRuntimePlugin,
|
||||
// Engine-specific service tokens
|
||||
TransformTypeToken,
|
||||
CanvasElementToken,
|
||||
TextureServiceToken,
|
||||
DynamicAtlasServiceToken,
|
||||
CoordinateServiceToken,
|
||||
RenderConfigServiceToken,
|
||||
// Types
|
||||
type IEditorModuleBase,
|
||||
type ITextureService,
|
||||
type IDynamicAtlasService,
|
||||
type ICoordinateService,
|
||||
type IRenderConfigService
|
||||
} from './EnginePlugin';
|
||||
|
||||
// Module Manifest types (unified module/plugin configuration)
|
||||
export {
|
||||
type ModuleManifest,
|
||||
type ModuleCategory,
|
||||
type ModulePlatform,
|
||||
type ModuleExports
|
||||
} from './ModuleManifest';
|
||||
|
||||
// Input System (keyboard, mouse, touch)
|
||||
export {
|
||||
Input,
|
||||
InputManager,
|
||||
InputSystem,
|
||||
MouseButton,
|
||||
type InputSystemConfig,
|
||||
type KeyState,
|
||||
type MouseButtonState,
|
||||
type KeyboardEventInfo,
|
||||
type MouseEventInfo,
|
||||
type WheelEventInfo,
|
||||
type TouchInfo,
|
||||
type TouchEvent
|
||||
} from './Input';
|
||||
|
||||
// Sorting Layer System (render order control)
|
||||
export {
|
||||
SortingLayerManager,
|
||||
sortingLayerManager,
|
||||
SortingLayerManagerToken,
|
||||
SortingLayers,
|
||||
DEFAULT_SORTING_LAYERS,
|
||||
type ISortingLayerManager,
|
||||
type ISortable,
|
||||
type SortingLayerConfig,
|
||||
type SortingLayerName
|
||||
} from './SortingLayer';
|
||||
12
packages/engine/engine-core/tsconfig.build.json
Normal file
12
packages/engine/engine-core/tsconfig.build.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
20
packages/engine/engine-core/tsconfig.json
Normal file
20
packages/engine/engine-core/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../framework/core"
|
||||
}
|
||||
]
|
||||
}
|
||||
7
packages/engine/engine-core/tsup.config.ts
Normal file
7
packages/engine/engine-core/tsup.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'tsup';
|
||||
import { runtimeOnlyPreset } from '../../tools/build-config/src/presets/plugin-tsup';
|
||||
|
||||
export default defineConfig({
|
||||
...runtimeOnlyPreset(),
|
||||
tsconfig: 'tsconfig.build.json'
|
||||
});
|
||||
Reference in New Issue
Block a user