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,166 @@
/**
* BoxCollider2D Inspector Provider
* 2D 矩形碰撞体检视器
*/
import React from 'react';
import { Component } from '@esengine/ecs-framework';
import type { IComponentInspector, ComponentInspectorContext } from '@esengine/editor-core';
import { BoxCollider2DComponent, CollisionLayer2D } from '@esengine/physics-rapier2d';
export class BoxCollider2DInspectorProvider implements IComponentInspector<BoxCollider2DComponent> {
readonly id = 'boxcollider2d-inspector';
readonly name = 'BoxCollider2D Inspector';
readonly priority = 100;
readonly targetComponents = ['BoxCollider2D', 'BoxCollider2DComponent'];
canHandle(component: Component): component is BoxCollider2DComponent {
return component instanceof BoxCollider2DComponent ||
component.constructor.name === 'BoxCollider2DComponent';
}
render(context: ComponentInspectorContext): React.ReactElement {
const component = context.component as BoxCollider2DComponent;
const onChange = context.onChange;
const handleChange = (prop: string, value: unknown) => {
onChange?.(prop, value);
};
return (
<div className="entity-inspector">
<div className="inspector-section">
<div className="section-title">Box Collider 2D</div>
{/* Size */}
<div className="section-subtitle">Size</div>
<div className="property-row">
<label>Width</label>
<input
type="number"
value={component.width}
min={0.001}
step={0.1}
onChange={(e) => handleChange('width', parseFloat(e.target.value) || 1)}
className="property-input"
/>
</div>
<div className="property-row">
<label>Height</label>
<input
type="number"
value={component.height}
min={0.001}
step={0.1}
onChange={(e) => handleChange('height', parseFloat(e.target.value) || 1)}
className="property-input"
/>
</div>
{/* Offset */}
<div className="section-subtitle">Offset</div>
<div className="property-row">
<label>X</label>
<input
type="number"
value={component.offset.x}
step={0.1}
onChange={(e) => handleChange('offset', {
...component.offset,
x: parseFloat(e.target.value) || 0
})}
className="property-input"
/>
</div>
<div className="property-row">
<label>Y</label>
<input
type="number"
value={component.offset.y}
step={0.1}
onChange={(e) => handleChange('offset', {
...component.offset,
y: parseFloat(e.target.value) || 0
})}
className="property-input"
/>
</div>
{/* Material */}
<div className="section-subtitle">Material</div>
<div className="property-row">
<label>Friction</label>
<input
type="number"
value={component.friction}
min={0}
max={1}
step={0.01}
onChange={(e) => handleChange('friction', parseFloat(e.target.value) || 0)}
className="property-input"
/>
</div>
<div className="property-row">
<label>Restitution</label>
<input
type="number"
value={component.restitution}
min={0}
max={1}
step={0.01}
onChange={(e) => handleChange('restitution', parseFloat(e.target.value) || 0)}
className="property-input"
/>
</div>
<div className="property-row">
<label>Density</label>
<input
type="number"
value={component.density}
min={0.001}
step={0.1}
onChange={(e) => handleChange('density', parseFloat(e.target.value) || 1)}
className="property-input"
/>
</div>
{/* Collision */}
<div className="section-subtitle">Collision</div>
<div className="property-row">
<label>Is Trigger</label>
<input
type="checkbox"
checked={component.isTrigger}
onChange={(e) => handleChange('isTrigger', e.target.checked)}
className="property-checkbox"
/>
</div>
<div className="property-row">
<label>Layer</label>
<select
value={component.collisionLayer}
onChange={(e) => handleChange('collisionLayer', parseInt(e.target.value, 10))}
className="property-select"
>
<option value={CollisionLayer2D.Default}>Default</option>
<option value={CollisionLayer2D.Player}>Player</option>
<option value={CollisionLayer2D.Enemy}>Enemy</option>
<option value={CollisionLayer2D.Ground}>Ground</option>
<option value={CollisionLayer2D.Projectile}>Projectile</option>
<option value={CollisionLayer2D.Trigger}>Trigger</option>
</select>
</div>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,152 @@
/**
* CircleCollider2D Inspector Provider
* 2D 圆形碰撞体检视器
*/
import React from 'react';
import { Component } from '@esengine/ecs-framework';
import type { IComponentInspector, ComponentInspectorContext } from '@esengine/editor-core';
import { CircleCollider2DComponent, CollisionLayer2D } from '@esengine/physics-rapier2d';
export class CircleCollider2DInspectorProvider implements IComponentInspector<CircleCollider2DComponent> {
readonly id = 'circlecollider2d-inspector';
readonly name = 'CircleCollider2D Inspector';
readonly priority = 100;
readonly targetComponents = ['CircleCollider2D', 'CircleCollider2DComponent'];
canHandle(component: Component): component is CircleCollider2DComponent {
return component instanceof CircleCollider2DComponent ||
component.constructor.name === 'CircleCollider2DComponent';
}
render(context: ComponentInspectorContext): React.ReactElement {
const component = context.component as CircleCollider2DComponent;
const onChange = context.onChange;
const handleChange = (prop: string, value: unknown) => {
onChange?.(prop, value);
};
return (
<div className="entity-inspector">
<div className="inspector-section">
<div className="section-title">Circle Collider 2D</div>
{/* Radius */}
<div className="property-row">
<label>Radius</label>
<input
type="number"
value={component.radius}
min={0.001}
step={0.1}
onChange={(e) => handleChange('radius', parseFloat(e.target.value) || 0.5)}
className="property-input"
/>
</div>
{/* Offset */}
<div className="section-subtitle">Offset</div>
<div className="property-row">
<label>X</label>
<input
type="number"
value={component.offset.x}
step={0.1}
onChange={(e) => handleChange('offset', {
...component.offset,
x: parseFloat(e.target.value) || 0
})}
className="property-input"
/>
</div>
<div className="property-row">
<label>Y</label>
<input
type="number"
value={component.offset.y}
step={0.1}
onChange={(e) => handleChange('offset', {
...component.offset,
y: parseFloat(e.target.value) || 0
})}
className="property-input"
/>
</div>
{/* Material */}
<div className="section-subtitle">Material</div>
<div className="property-row">
<label>Friction</label>
<input
type="number"
value={component.friction}
min={0}
max={1}
step={0.01}
onChange={(e) => handleChange('friction', parseFloat(e.target.value) || 0)}
className="property-input"
/>
</div>
<div className="property-row">
<label>Restitution</label>
<input
type="number"
value={component.restitution}
min={0}
max={1}
step={0.01}
onChange={(e) => handleChange('restitution', parseFloat(e.target.value) || 0)}
className="property-input"
/>
</div>
<div className="property-row">
<label>Density</label>
<input
type="number"
value={component.density}
min={0.001}
step={0.1}
onChange={(e) => handleChange('density', parseFloat(e.target.value) || 1)}
className="property-input"
/>
</div>
{/* Collision */}
<div className="section-subtitle">Collision</div>
<div className="property-row">
<label>Is Trigger</label>
<input
type="checkbox"
checked={component.isTrigger}
onChange={(e) => handleChange('isTrigger', e.target.checked)}
className="property-checkbox"
/>
</div>
<div className="property-row">
<label>Layer</label>
<select
value={component.collisionLayer}
onChange={(e) => handleChange('collisionLayer', parseInt(e.target.value, 10))}
className="property-select"
>
<option value={CollisionLayer2D.Default}>Default</option>
<option value={CollisionLayer2D.Player}>Player</option>
<option value={CollisionLayer2D.Enemy}>Enemy</option>
<option value={CollisionLayer2D.Ground}>Ground</option>
<option value={CollisionLayer2D.Projectile}>Projectile</option>
<option value={CollisionLayer2D.Trigger}>Trigger</option>
</select>
</div>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,200 @@
/**
* Rigidbody2D Inspector Provider
* 2D 刚体检视器
*/
import React from 'react';
import { Component } from '@esengine/ecs-framework';
import type { IComponentInspector, ComponentInspectorContext } from '@esengine/editor-core';
import { Rigidbody2DComponent, RigidbodyType2D, CollisionDetectionMode2D } from '@esengine/physics-rapier2d';
export class Rigidbody2DInspectorProvider implements IComponentInspector<Rigidbody2DComponent> {
readonly id = 'rigidbody2d-inspector';
readonly name = 'Rigidbody2D Inspector';
readonly priority = 100;
readonly targetComponents = ['Rigidbody2D', 'Rigidbody2DComponent'];
canHandle(component: Component): component is Rigidbody2DComponent {
return component instanceof Rigidbody2DComponent ||
component.constructor.name === 'Rigidbody2DComponent';
}
render(context: ComponentInspectorContext): React.ReactElement {
const component = context.component as Rigidbody2DComponent;
const onChange = context.onChange;
const handleChange = (prop: string, value: unknown) => {
onChange?.(prop, value);
};
return (
<div className="entity-inspector">
<div className="inspector-section">
<div className="section-title">Rigidbody 2D</div>
{/* Body Type */}
<div className="property-row">
<label>Body Type</label>
<select
value={component.bodyType}
onChange={(e) => handleChange('bodyType', parseInt(e.target.value, 10) as RigidbodyType2D)}
className="property-select"
>
<option value={RigidbodyType2D.Dynamic}>Dynamic</option>
<option value={RigidbodyType2D.Kinematic}>Kinematic</option>
<option value={RigidbodyType2D.Static}>Static</option>
</select>
</div>
{/* Mass - only for Dynamic */}
{component.bodyType === RigidbodyType2D.Dynamic && (
<div className="property-row">
<label>Mass</label>
<input
type="number"
value={component.mass}
min={0.001}
step={0.1}
onChange={(e) => handleChange('mass', parseFloat(e.target.value) || 1)}
className="property-input"
/>
</div>
)}
{/* Gravity Scale */}
<div className="property-row">
<label>Gravity Scale</label>
<input
type="number"
value={component.gravityScale}
step={0.1}
onChange={(e) => handleChange('gravityScale', parseFloat(e.target.value) || 0)}
className="property-input"
/>
</div>
{/* Damping Section */}
<div className="section-subtitle">Damping</div>
<div className="property-row">
<label>Linear</label>
<input
type="number"
value={component.linearDamping}
min={0}
step={0.01}
onChange={(e) => handleChange('linearDamping', parseFloat(e.target.value) || 0)}
className="property-input"
/>
</div>
<div className="property-row">
<label>Angular</label>
<input
type="number"
value={component.angularDamping}
min={0}
step={0.01}
onChange={(e) => handleChange('angularDamping', parseFloat(e.target.value) || 0)}
className="property-input"
/>
</div>
{/* Constraints Section */}
<div className="section-subtitle">Constraints</div>
<div className="property-row">
<label>Freeze Position X</label>
<input
type="checkbox"
checked={component.constraints.freezePositionX}
onChange={(e) => handleChange('constraints', {
...component.constraints,
freezePositionX: e.target.checked
})}
className="property-checkbox"
/>
</div>
<div className="property-row">
<label>Freeze Position Y</label>
<input
type="checkbox"
checked={component.constraints.freezePositionY}
onChange={(e) => handleChange('constraints', {
...component.constraints,
freezePositionY: e.target.checked
})}
className="property-checkbox"
/>
</div>
<div className="property-row">
<label>Freeze Rotation</label>
<input
type="checkbox"
checked={component.constraints.freezeRotation}
onChange={(e) => handleChange('constraints', {
...component.constraints,
freezeRotation: e.target.checked
})}
className="property-checkbox"
/>
</div>
{/* Collision Detection */}
<div className="section-subtitle">Collision</div>
<div className="property-row">
<label>Detection</label>
<select
value={component.collisionDetection}
onChange={(e) => handleChange('collisionDetection', parseInt(e.target.value, 10) as CollisionDetectionMode2D)}
className="property-select"
>
<option value={CollisionDetectionMode2D.Discrete}>Discrete</option>
<option value={CollisionDetectionMode2D.Continuous}>Continuous</option>
</select>
</div>
{/* Sleep */}
<div className="section-subtitle">Sleep</div>
<div className="property-row">
<label>Can Sleep</label>
<input
type="checkbox"
checked={component.canSleep}
onChange={(e) => handleChange('canSleep', e.target.checked)}
className="property-checkbox"
/>
</div>
{/* Runtime Info (read-only) */}
<div className="section-subtitle">Runtime Info</div>
<div className="property-row">
<label>Velocity</label>
<span className="property-readonly">
({component.velocity.x.toFixed(2)}, {component.velocity.y.toFixed(2)})
</span>
</div>
<div className="property-row">
<label>Angular Vel</label>
<span className="property-readonly">
{component.angularVelocity.toFixed(2)} rad/s
</span>
</div>
<div className="property-row">
<label>Is Awake</label>
<span className="property-readonly">
{component.isAwake ? 'Yes' : 'No'}
</span>
</div>
</div>
</div>
);
}
}