Files
esengine/packages/tilemap/src/physics/TilemapPhysicsSystem.ts

174 lines
5.5 KiB
TypeScript
Raw Normal View History

/**
* TilemapPhysicsSystem
* Tilemap
*
* TilemapComponent
* Physics2DSystem 使
*/
import { EntitySystem, Matcher, ECSSystem, type Entity, type Scene } from '@esengine/esengine';
import { TransformComponent } from '@esengine/engine-core';
import { TilemapComponent } from '../TilemapComponent';
import { TilemapCollider2DComponent, type CollisionRect } from './TilemapCollider2DComponent';
refactor(plugin): 重构插件系统架构,统一类型导入路径 (#296) * refactor(plugin): 重构插件系统架构,统一类型导入路径 ## 主要更改 ### 新增 @esengine/plugin-types 包 - 提供打破循环依赖的最小类型定义 - 包含 ServiceToken, createServiceToken, PluginServiceRegistry, IEditorModuleBase ### engine-core 类型统一 - IPlugin<T> 泛型参数改为 T = unknown - 所有运行时类型(IRuntimeModule, ModuleManifest, SystemContext)在此定义 - 新增 PluginServiceRegistry.ts 导出服务令牌相关类型 ### editor-core 类型优化 - 重命名 IPluginLoader.ts 为 EditorModule.ts - 新增 IEditorPlugin = IPlugin<IEditorModuleLoader> 类型别名 - 移除弃用别名 IPluginLoader, IRuntimeModuleLoader ### 编辑器插件更新 - 所有 9 个编辑器插件使用 IEditorPlugin 类型 - 统一从 editor-core 导入编辑器类型 ### 服务令牌规范 - 各模块在 tokens.ts 中定义自己的服务接口和令牌 - 遵循"谁定义接口,谁导出 Token"原则 ## 导入规范 | 场景 | 导入来源 | |------|---------| | 运行时模块 | @esengine/engine-core | | 编辑器插件 | @esengine/editor-core | | 服务令牌 | @esengine/engine-core | * refactor(plugin): 完善服务令牌规范,统一运行时模块 ## 更改内容 ### 运行时模块优化 - particle: 使用 PluginServiceRegistry 获取依赖服务 - physics-rapier2d: 通过服务令牌注册/获取物理查询接口 - tilemap: 使用服务令牌获取物理系统依赖 - sprite: 导出服务令牌 - ui: 导出服务令牌 - behavior-tree: 使用服务令牌系统 ### 资产系统增强 - IAssetManager 接口扩展 - 加载器使用服务令牌获取依赖 ### 运行时核心 - GameRuntime 使用 PluginServiceRegistry - 导出服务令牌相关类型 ### 编辑器服务 - EngineService 适配新的服务令牌系统 - AssetRegistryService 优化 * fix: 修复 editor-app 和 behavior-tree-editor 中的类型引用 - editor-app/PluginLoader.ts: 使用 IPlugin 替代 IPluginLoader - behavior-tree-editor: 使用 IEditorPlugin 替代 IPluginLoader * fix(ui): 添加缺失的 ecs-engine-bindgen 依赖 UIRuntimeModule 使用 EngineBridgeToken,需要声明对 ecs-engine-bindgen 的依赖 * fix(type): 解决 ServiceToken 跨包类型兼容性问题 - 在 engine-core 中直接定义 ServiceToken 和 PluginServiceRegistry 而不是从 plugin-types 重新导出,确保 tsup 生成的类型声明 以 engine-core 作为类型来源 - 移除 RuntimeResolver.ts 中的硬编码模块 ID 检查, 改用 module.json 中的 name 配置 - 修复 pnpm-lock.yaml 中的依赖记录 * refactor(arch): 改进架构设计,移除硬编码 - 统一类型导出:editor-core 从 engine-core 导入 IEditorModuleBase - RuntimeResolver: 将硬编码路径改为配置常量和搜索路径列表 - 添加跨平台安装路径支持(Windows/macOS/Linux) - 使用 ENGINE_WASM_CONFIG 配置引擎 WASM 文件信息 - IBundlePackOptions 添加 preloadBundles 配置项 * fix(particle): 添加缺失的 ecs-engine-bindgen 依赖 ParticleRuntimeModule 导入了 @esengine/ecs-engine-bindgen 的 tokens, 但 package.json 中未声明该依赖,导致 CI 构建失败。 * fix(physics-rapier2d): 移除不存在的 PhysicsSystemContext 导出 PhysicsRuntimeModule 中不存在该类型,导致 type-check 失败
2025-12-08 21:10:57 +08:00
import type { IPhysics2DWorld } from '@esengine/physics-rapier2d';
refactor(plugin): 重构插件系统架构,统一类型导入路径 (#296) * refactor(plugin): 重构插件系统架构,统一类型导入路径 ## 主要更改 ### 新增 @esengine/plugin-types 包 - 提供打破循环依赖的最小类型定义 - 包含 ServiceToken, createServiceToken, PluginServiceRegistry, IEditorModuleBase ### engine-core 类型统一 - IPlugin<T> 泛型参数改为 T = unknown - 所有运行时类型(IRuntimeModule, ModuleManifest, SystemContext)在此定义 - 新增 PluginServiceRegistry.ts 导出服务令牌相关类型 ### editor-core 类型优化 - 重命名 IPluginLoader.ts 为 EditorModule.ts - 新增 IEditorPlugin = IPlugin<IEditorModuleLoader> 类型别名 - 移除弃用别名 IPluginLoader, IRuntimeModuleLoader ### 编辑器插件更新 - 所有 9 个编辑器插件使用 IEditorPlugin 类型 - 统一从 editor-core 导入编辑器类型 ### 服务令牌规范 - 各模块在 tokens.ts 中定义自己的服务接口和令牌 - 遵循"谁定义接口,谁导出 Token"原则 ## 导入规范 | 场景 | 导入来源 | |------|---------| | 运行时模块 | @esengine/engine-core | | 编辑器插件 | @esengine/editor-core | | 服务令牌 | @esengine/engine-core | * refactor(plugin): 完善服务令牌规范,统一运行时模块 ## 更改内容 ### 运行时模块优化 - particle: 使用 PluginServiceRegistry 获取依赖服务 - physics-rapier2d: 通过服务令牌注册/获取物理查询接口 - tilemap: 使用服务令牌获取物理系统依赖 - sprite: 导出服务令牌 - ui: 导出服务令牌 - behavior-tree: 使用服务令牌系统 ### 资产系统增强 - IAssetManager 接口扩展 - 加载器使用服务令牌获取依赖 ### 运行时核心 - GameRuntime 使用 PluginServiceRegistry - 导出服务令牌相关类型 ### 编辑器服务 - EngineService 适配新的服务令牌系统 - AssetRegistryService 优化 * fix: 修复 editor-app 和 behavior-tree-editor 中的类型引用 - editor-app/PluginLoader.ts: 使用 IPlugin 替代 IPluginLoader - behavior-tree-editor: 使用 IEditorPlugin 替代 IPluginLoader * fix(ui): 添加缺失的 ecs-engine-bindgen 依赖 UIRuntimeModule 使用 EngineBridgeToken,需要声明对 ecs-engine-bindgen 的依赖 * fix(type): 解决 ServiceToken 跨包类型兼容性问题 - 在 engine-core 中直接定义 ServiceToken 和 PluginServiceRegistry 而不是从 plugin-types 重新导出,确保 tsup 生成的类型声明 以 engine-core 作为类型来源 - 移除 RuntimeResolver.ts 中的硬编码模块 ID 检查, 改用 module.json 中的 name 配置 - 修复 pnpm-lock.yaml 中的依赖记录 * refactor(arch): 改进架构设计,移除硬编码 - 统一类型导出:editor-core 从 engine-core 导入 IEditorModuleBase - RuntimeResolver: 将硬编码路径改为配置常量和搜索路径列表 - 添加跨平台安装路径支持(Windows/macOS/Linux) - 使用 ENGINE_WASM_CONFIG 配置引擎 WASM 文件信息 - IBundlePackOptions 添加 preloadBundles 配置项 * fix(particle): 添加缺失的 ecs-engine-bindgen 依赖 ParticleRuntimeModule 导入了 @esengine/ecs-engine-bindgen 的 tokens, 但 package.json 中未声明该依赖,导致 CI 构建失败。 * fix(physics-rapier2d): 移除不存在的 PhysicsSystemContext 导出 PhysicsRuntimeModule 中不存在该类型,导致 type-check 失败
2025-12-08 21:10:57 +08:00
// 重新导出类型以保持向后兼容 | Re-export types for backward compatibility
export type IPhysicsWorld = IPhysics2DWorld;
/**
*
*/
export interface IPhysics2DSystem {
world: IPhysicsWorld;
}
/**
* Tilemap
*
* TilemapComponent TilemapCollider2DComponent
*
*/
@ECSSystem('TilemapPhysics', { updateOrder: 50 })
export class TilemapPhysicsSystem extends EntitySystem {
private _physicsWorld: IPhysicsWorld | null = null;
private _pendingEntities: Entity[] = [];
constructor() {
super(Matcher.empty().all(TilemapComponent, TilemapCollider2DComponent));
}
/**
*
*/
public setPhysicsWorld(world: IPhysicsWorld): void {
this._physicsWorld = world;
// 处理待处理的实体
for (const entity of this._pendingEntities) {
this._createColliders(entity);
}
this._pendingEntities = [];
}
protected override onAdded(entity: Entity): void {
if (!this._physicsWorld) {
this._pendingEntities.push(entity);
return;
}
this._createColliders(entity);
}
protected override onRemoved(entity: Entity): void {
this._removeColliders(entity);
const idx = this._pendingEntities.indexOf(entity);
if (idx >= 0) {
this._pendingEntities.splice(idx, 1);
}
}
protected override process(entities: readonly Entity[]): void {
if (!this._physicsWorld) return;
for (const entity of entities) {
const tilemap = entity.getComponent(TilemapComponent);
const collider = entity.getComponent(TilemapCollider2DComponent);
if (!tilemap || !collider) continue;
// 检查碰撞数据是否变化
const currentVersion = tilemap.renderDirty ? Date.now() : collider._lastCollisionVersion;
if (collider._needsRebuild || currentVersion !== collider._lastCollisionVersion) {
this._rebuildColliders(entity);
collider._lastCollisionVersion = currentVersion;
}
}
}
/**
*
*/
private _createColliders(entity: Entity): void {
const tilemap = entity.getComponent(TilemapComponent);
const collider = entity.getComponent(TilemapCollider2DComponent);
const transform = entity.getComponent(TransformComponent);
if (!tilemap || !collider || !this._physicsWorld) return;
// 生成碰撞矩形
const collisionData = tilemap.collisionData;
collider.generateCollisionRects(
collisionData,
tilemap.width,
tilemap.height,
tilemap.tileWidth,
tilemap.tileHeight
);
// 获取实体位置偏移
const offsetX = transform?.position.x ?? 0;
const offsetY = transform?.position.y ?? 0;
// 计算地图总高度(像素),用于 Y 轴翻转
// Calculate total map height (pixels) for Y-axis flip
const mapPixelHeight = tilemap.height * tilemap.tileHeight;
// 为每个碰撞矩形创建物理碰撞体
for (const rect of collider._collisionRects) {
// Y 轴翻转rect.y 是从顶部计算的,需要翻转到底部
// Y-axis flip: rect.y is calculated from top, needs flip to bottom
const flippedY = mapPixelHeight - rect.y - rect.height;
const handle = this._physicsWorld.createStaticCollider(
entity.id,
{
x: offsetX + rect.x + rect.width / 2,
y: offsetY + flippedY + rect.height / 2,
},
{
x: rect.width / 2,
y: rect.height / 2,
},
collider.collisionLayer,
collider.collisionMask,
collider.friction,
collider.restitution,
collider.isTrigger
);
if (handle !== null) {
collider._colliderHandles.push(handle);
}
}
collider._needsRebuild = false;
this.logger.debug(`Created ${collider._colliderHandles.length} colliders for tilemap entity ${entity.name}`);
}
/**
*
*/
private _removeColliders(entity: Entity): void {
const collider = entity.getComponent(TilemapCollider2DComponent);
if (!collider || !this._physicsWorld) return;
for (const handle of collider._colliderHandles) {
this._physicsWorld.removeCollider(handle);
}
collider._colliderHandles = [];
}
/**
*
*/
private _rebuildColliders(entity: Entity): void {
this._removeColliders(entity);
this._createColliders(entity);
}
protected override onDestroy(): void {
this._physicsWorld = null;
this._pendingEntities = [];
}
}