Feature/editor optimization (#251)

* refactor: 编辑器/运行时架构拆分与构建系统升级

* feat(core): 层级系统重构与UI变换矩阵修复

* refactor: 移除 ecs-components 聚合包并修复跨包组件查找问题

* fix(physics): 修复跨包组件类引用问题

* feat: 统一运行时架构与浏览器运行支持

* feat(asset): 实现浏览器运行时资产加载系统

* fix: 修复文档、CodeQL安全问题和CI类型检查错误

* fix: 修复文档、CodeQL安全问题和CI类型检查错误

* fix: 修复文档、CodeQL安全问题、CI类型检查和测试错误

* test: 补齐核心模块测试用例,修复CI构建配置

* fix: 修复测试用例中的类型错误和断言问题

* fix: 修复 turbo build:npm 任务的依赖顺序问题

* fix: 修复 CI 构建错误并优化构建性能
This commit is contained in:
YHH
2025-12-01 22:28:51 +08:00
committed by GitHub
parent 189714c727
commit b42a7b4e43
468 changed files with 18301 additions and 9075 deletions

View File

@@ -0,0 +1,352 @@
/**
* Tilemap Gizmo Implementation
* Tilemap Gizmo 实现
*
* Registers gizmo provider for TilemapComponent using the GizmoRegistry.
* Rendered via Rust WebGL engine for optimal performance.
* 使用 GizmoRegistry 为 TilemapComponent 注册 gizmo 提供者。
* 通过 Rust WebGL 引擎渲染以获得最佳性能。
*/
import type { Entity } from '@esengine/ecs-framework';
import type { IGizmoRenderData, IRectGizmoData, IGridGizmoData, GizmoColor } from '@esengine/editor-core';
import { GizmoColors, GizmoRegistry } from '@esengine/editor-core';
import { TransformComponent } from '@esengine/engine-core';
import { TilemapComponent } from '@esengine/tilemap';
import { TilemapCollider2DComponent, TilemapColliderMode } from '@esengine/tilemap';
/**
* Tilemap Collider Gizmo 颜色配置
*/
const TilemapColliderGizmoColors = {
/** 碰撞体边框 - 青色 */
collider: { r: 0.0, g: 0.8, b: 0.8, a: 0.8 } as GizmoColor,
/** 碰撞体填充 - 半透明青色 */
colliderFill: { r: 0.0, g: 0.8, b: 0.8, a: 0.2 } as GizmoColor,
/** 选中时的碰撞体 - 亮青色 */
colliderSelected: { r: 0.0, g: 1.0, b: 1.0, a: 1.0 } as GizmoColor,
/** 触发器 - 橙色 */
trigger: { r: 1.0, g: 0.6, b: 0.0, a: 0.8 } as GizmoColor,
};
/**
* Gizmo provider function for TilemapComponent.
* TilemapComponent 的 gizmo 提供者函数。
*
* Provides gizmo data including:
* - Outer boundary rectangle
* - Tile grid overlay (when selected)
*
* 提供的 gizmo 数据包括:
* - 外部边界矩形
* - 瓦片网格覆盖层(选中时)
*/
function tilemapGizmoProvider(
tilemap: TilemapComponent,
entity: Entity,
isSelected: boolean
): IGizmoRenderData[] {
const transform = entity.getComponent(TransformComponent);
if (!transform) {
return [];
}
const gizmos: IGizmoRenderData[] = [];
// Calculate tilemap world bounds
// 计算 tilemap 世界边界
const width = tilemap.width * tilemap.tileWidth * transform.scale.x;
const height = tilemap.height * tilemap.tileHeight * transform.scale.y;
// Get rotation (handle both number and Vector3)
// 获取旋转(处理数字和 Vector3 两种情况)
const rotation = typeof transform.rotation === 'number'
? transform.rotation
: transform.rotation.z;
// Center position (tilemap origin is at bottom-left)
// 中心位置tilemap 原点在左下角)
const centerX = transform.position.x + width / 2;
const centerY = transform.position.y + height / 2;
// Use predefined colors based on selection state
// 根据选择状态使用预定义颜色
const boundaryColor: GizmoColor = isSelected
? GizmoColors.selected
: GizmoColors.unselected;
// Outer boundary rectangle
// 外部边界矩形
const boundaryGizmo: IRectGizmoData = {
type: 'rect',
x: centerX,
y: centerY,
width,
height,
rotation,
originX: 0.5,
originY: 0.5,
color: boundaryColor,
showHandles: false
};
gizmos.push(boundaryGizmo);
// Grid overlay (only when selected for performance)
// 网格覆盖层(仅选中时显示以保证性能)
if (isSelected) {
const gridColor: GizmoColor = { ...GizmoColors.grid, a: 0.3 };
const gridGizmo: IGridGizmoData = {
type: 'grid',
x: transform.position.x,
y: transform.position.y,
width,
height,
cols: tilemap.width,
rows: tilemap.height,
color: gridColor
};
gizmos.push(gridGizmo);
}
return gizmos;
}
/**
* Gizmo provider function for TilemapCollider2DComponent.
* TilemapCollider2DComponent 的 gizmo 提供者函数。
*
* Provides gizmo data for collision visualization:
* - Shows collision rectangles (per-tile or merged)
* - Different colors for trigger vs collider
*
* 提供碰撞可视化的 gizmo 数据:
* - 显示碰撞矩形(每格或合并)
* - 触发器和碰撞体使用不同颜色
*/
function tilemapCollider2DGizmoProvider(
collider: TilemapCollider2DComponent,
entity: Entity,
isSelected: boolean
): IGizmoRenderData[] {
const transform = entity.getComponent(TransformComponent);
const tilemap = entity.getComponent(TilemapComponent);
if (!transform || !tilemap) {
return [];
}
const gizmos: IGizmoRenderData[] = [];
// 获取碰撞颜色
const color = isSelected
? TilemapColliderGizmoColors.colliderSelected
: (collider.isTrigger ? TilemapColliderGizmoColors.trigger : TilemapColliderGizmoColors.collider);
// 获取实体位置偏移
const offsetX = transform.position.x;
const offsetY = transform.position.y;
const scaleX = transform.scale.x;
const scaleY = transform.scale.y;
// 获取旋转
const rotation = typeof transform.rotation === 'number'
? transform.rotation
: transform.rotation.z;
// 计算地图总高度(像素),用于 Y 轴翻转
// Calculate total map height (pixels) for Y-axis flip
const mapPixelHeight = tilemap.height * tilemap.tileHeight;
// 如果已有碰撞矩形缓存,直接使用
if (collider._collisionRects.length > 0) {
// 使用已生成的碰撞矩形
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 worldX = offsetX + (rect.x + rect.width / 2) * scaleX;
const worldY = offsetY + (flippedY + rect.height / 2) * scaleY;
const worldWidth = rect.width * scaleX;
const worldHeight = rect.height * scaleY;
const rectGizmo: IRectGizmoData = {
type: 'rect',
x: worldX,
y: worldY,
width: worldWidth,
height: worldHeight,
rotation,
originX: 0.5,
originY: 0.5,
color,
showHandles: false
};
gizmos.push(rectGizmo);
}
} else {
// 如果没有缓存,根据模式生成预览
const collisionData = tilemap.collisionData;
const width = tilemap.width;
const height = tilemap.height;
const tileWidth = tilemap.tileWidth;
const tileHeight = tilemap.tileHeight;
if (collider.colliderMode === TilemapColliderMode.PerTile) {
// PerTile 模式:每个碰撞格子单独显示
for (let row = 0; row < height; row++) {
for (let col = 0; col < width; col++) {
if (collisionData[row * width + col] > 0) {
// Y 轴翻转:数据存储 row 0 在顶部,渲染时 Y-up 需要翻转
// Y-axis flip: data stores row 0 at top, rendering needs Y-up flip
const flippedRow = height - 1 - row;
const worldX = offsetX + (col * tileWidth + tileWidth / 2) * scaleX;
const worldY = offsetY + (flippedRow * tileHeight + tileHeight / 2) * scaleY;
const worldWidth = tileWidth * scaleX;
const worldHeight = tileHeight * scaleY;
const rectGizmo: IRectGizmoData = {
type: 'rect',
x: worldX,
y: worldY,
width: worldWidth,
height: worldHeight,
rotation,
originX: 0.5,
originY: 0.5,
color,
showHandles: false
};
gizmos.push(rectGizmo);
}
}
}
} else {
// Merged 模式:使用贪心算法合并相邻格子
const rects = generateMergedRects(collisionData, width, height, tileWidth, tileHeight);
for (const rect of rects) {
// Y 轴翻转rect.y 是从顶部计算的,需要翻转到底部
// Y-axis flip: rect.y is calculated from top, needs flip to bottom
const flippedY = mapPixelHeight - rect.y - rect.height;
const worldX = offsetX + (rect.x + rect.width / 2) * scaleX;
const worldY = offsetY + (flippedY + rect.height / 2) * scaleY;
const worldWidth = rect.width * scaleX;
const worldHeight = rect.height * scaleY;
const rectGizmo: IRectGizmoData = {
type: 'rect',
x: worldX,
y: worldY,
width: worldWidth,
height: worldHeight,
rotation,
originX: 0.5,
originY: 0.5,
color,
showHandles: false
};
gizmos.push(rectGizmo);
}
}
}
return gizmos;
}
/**
* 生成合并的碰撞矩形(贪心算法)
* 用于 Gizmo 预览,与 TilemapCollider2DComponent 中的算法相同
*/
function generateMergedRects(
collisionData: Uint32Array,
width: number,
height: number,
tileWidth: number,
tileHeight: number
): Array<{ x: number; y: number; width: number; height: number }> {
if (collisionData.length === 0) {
return [];
}
const processed = new Array(width * height).fill(false);
const rects: Array<{ x: number; y: number; width: number; height: number }> = [];
for (let row = 0; row < height; row++) {
for (let col = 0; col < width; col++) {
const index = row * width + col;
if (processed[index] || collisionData[index] === 0) {
continue;
}
// 找到水平方向最大范围
let endCol = col;
while (
endCol < width &&
collisionData[row * width + endCol] > 0 &&
!processed[row * width + endCol]
) {
endCol++;
}
const rectWidth = endCol - col;
// 找到垂直方向最大范围
let endRow = row;
let canExtend = true;
while (canExtend && endRow < height) {
for (let c = col; c < endCol; c++) {
const idx = endRow * width + c;
if (collisionData[idx] === 0 || processed[idx]) {
canExtend = false;
break;
}
}
if (canExtend) {
endRow++;
}
}
const rectHeight = endRow - row;
// 标记所有包含的格子为已处理
for (let r = row; r < endRow; r++) {
for (let c = col; c < endCol; c++) {
processed[r * width + c] = true;
}
}
// 添加合并后的矩形
rects.push({
x: col * tileWidth,
y: row * tileHeight,
width: rectWidth * tileWidth,
height: rectHeight * tileHeight,
});
}
}
return rects;
}
/**
* Register gizmo provider for TilemapComponent.
* 为 TilemapComponent 注册 gizmo 提供者。
*
* Uses the GizmoRegistry pattern for clean separation between
* game components and editor functionality.
* 使用 GizmoRegistry 模式实现游戏组件和编辑器功能的清晰分离。
*/
export function registerTilemapGizmo(): void {
GizmoRegistry.register(TilemapComponent, tilemapGizmoProvider);
GizmoRegistry.register(TilemapCollider2DComponent, tilemapCollider2DGizmoProvider);
}
/**
* Unregister gizmo provider for TilemapComponent.
* 取消注册 TilemapComponent 的 gizmo 提供者。
*/
export function unregisterTilemapGizmo(): void {
GizmoRegistry.unregister(TilemapComponent);
GizmoRegistry.unregister(TilemapCollider2DComponent);
}