feat: 预制体系统与架构改进 (#303)

* feat(prefab): 实现预制体系统和编辑器 UX 改进

## 预制体系统
- 新增 PrefabSerializer: 预制体序列化/反序列化
- 新增 PrefabInstanceComponent: 追踪预制体实例来源和修改
- 新增 PrefabService: 预制体核心服务
- 新增 PrefabLoader: 预制体资产加载器
- 新增预制体命令: Create/Instantiate/Apply/Revert/BreakLink

## 预制体编辑模式
- 支持双击 .prefab 文件进入编辑模式
- 预制体编辑模式工具栏 (保存/退出)
- 预制体实例指示器和操作菜单

## 编辑器 UX 改进
- SceneHierarchy 快捷键: F2 重命名, Ctrl+D 复制, ↑↓ 导航
- 支持双击实体名称内联编辑
- 删除实体时显示子节点数量警告
- 右键菜单添加重命名/复制选项及快捷键提示
- 布局持久化和重置功能

## Bug 修复
- 修复 editor-runtime 组件类重复导致的 TransformComponent 不识别问题
- 修复 .prefab-name 样式覆盖导致预制体工具栏文字不可见
- 修复 Inspector 资源字段高度不正确问题

* feat(editor): 改进编辑器 UX 交互体验

- ContentBrowser: 加载动画 spinner、搜索高亮、改进空状态设计
- SceneHierarchy: 选中项自动滚动到视图、搜索清除按钮
- PropertyInspector: 输入框本地状态管理、Enter/Escape 键处理
- EntityInspector: 组件折叠状态持久化、属性搜索清除按钮
- Viewport: 变换操作实时数值显示
- 国际化: 添加相关文本 (en/zh)

* fix(build): 修复 Web 构建资产加载和编辑器 UX 改进

构建系统修复:
- 修复 asset-catalog.json 字段名不匹配 (entries vs assets)
- 修复 BrowserFileSystemService 支持两种目录格式
- 修复 bundle 策略检测逻辑 (空对象判断)
- 修复 module.json 中 assetExtensions 声明和类型推断

行为树修复:
- 修复 BehaviorTreeExecutionSystem 使用 loadAsset 替代 loadAssetByPath
- 修复 BehaviorTreeAssetType 常量与 module.json 类型名一致 (behavior-tree)

编辑器 UX 改进:
- 构建完成对话框添加"打开文件夹"按钮
- 构建完成对话框样式优化 (圆形图标背景、按钮布局)
- SceneHierarchy 响应式布局 (窄窗口自动隐藏 Type 列)
- SceneHierarchy 隐藏滚动条

错误追踪:
- 添加全局错误处理器写入日志文件 (%TEMP%/esengine-editor-crash.log)
- 添加 append_to_log Tauri 命令

* feat(render): 修复 UI 渲染和点击特效系统

## UI 渲染修复
- 修复 GUID 验证 bug,使用统一的 isValidGUID() 函数
- 修复 UI 渲染顺序随机问题,Rust 端使用 IndexMap 替代 HashMap
- Web 运行时添加 assetPathResolver 支持 GUID 解析
- UIInteractableComponent.blockEvents 默认值改为 false

## 点击特效系统
- 新增 ClickFxComponent 和 ClickFxSystem
- 支持在点击位置播放粒子效果
- 支持多种触发模式和粒子轮换

## Camera 系统重构
- CameraSystem 从 ecs-engine-bindgen 移至 camera 包
- 新增 CameraManager 统一管理相机

## 编辑器改进
- 改进属性面板 UI 交互
- 粒子编辑器面板优化
- Transform 命令系统

* feat(render): 实现 Sorting Layer 系统和 Overlay 渲染层

- 新增 SortingLayerManager 管理排序层级 (Background, Default, Foreground, UI, Overlay)
- 实现 ISortable 接口,统一 Sprite、UI、Particle 的排序属性
- 修复粒子 Overlay 层被 UI 遮挡问题:添加独立的 Overlay Pass 在 UI 之后渲染
- 更新粒子资产格式:从 sortingOrder 改为 sortingLayer + orderInLayer
- 更新粒子编辑器面板支持新的排序属性
- 优化 UI 渲染系统使用新的排序层级

* feat(ci): 集成 SignPath 代码签名服务

- 添加 SignPath 自动签名工作流(Windows)
- 配置 release-editor.yml 支持代码签名
- 将构建改为草稿模式,等待签名完成后发布
- 添加证书文件到 .gitignore 防止泄露

* fix(asset): 修复 Web 构建资产路径解析和全局单例移除

## 资产路径修复
- 修复 Tauri 本地服务器 `/asset?path=...` 路径解析,正确与 root 目录连接
- BrowserPathResolver 支持两种模式:
  - 'proxy': 使用 /asset?path=... 格式(编辑器 Run in Browser)
  - 'direct': 使用直接路径 /assets/path.png(独立 Web 构建)
- BrowserRuntime 使用 'direct' 模式,无需 Tauri 代理

## 架构改进 - 移除全局单例
- 移除 globalAssetManager 导出,改用 AssetManagerToken 依赖注入
- 移除 globalPathResolver 导出,改用 PathResolutionService
- 移除 globalPathResolutionService 导出
- ParticleUpdateSystem/ClickFxSystem 通过 setAssetManager() 注入依赖
- EngineService 使用 new AssetManager() 替代全局实例

## 新增服务
- PathResolutionService: 统一路径解析接口
- RuntimeModeService: 运行时模式查询服务
- SerializationContext: EntityRef 序列化上下文

## 其他改进
- 完善 ServiceToken 注释说明本地定义的意图
- 导出 BrowserPathResolveMode 类型

* fix(build): 添加 world-streaming composite 设置修复类型检查

* fix(build): 移除 world-streaming 引用避免 composite 冲突

* fix(build): 将 const enum 改为 enum 兼容 isolatedModules

* fix(build): 添加缺失的 IAssetManager 导入
This commit is contained in:
YHH
2025-12-13 19:44:08 +08:00
committed by GitHub
parent a716d8006c
commit beaa1d09de
258 changed files with 17725 additions and 3030 deletions

View File

@@ -1,6 +1,7 @@
{
"id": "ui",
"name": "@esengine/ui",
"globalKey": "ui",
"displayName": "UI System",
"description": "User interface components and layout | 用户界面组件和布局",
"version": "1.0.0",
@@ -14,6 +15,7 @@
"isCore": false,
"defaultEnabled": false,
"isEngineModule": true,
"hasRuntime": true,
"canContainContent": true,
"platforms": [
"web",
@@ -22,7 +24,8 @@
],
"dependencies": [
"core",
"math"
"math",
"engine-core"
],
"exports": {
"components": [

View File

@@ -26,6 +26,9 @@
"type-check": "tsc --noEmit",
"clean": "rimraf dist"
},
"dependencies": {
"@esengine/asset-system": "workspace:*"
},
"devDependencies": {
"@esengine/ecs-framework": "workspace:*",
"@esengine/engine-core": "workspace:*",

View File

@@ -22,7 +22,8 @@ export interface UIBaseConfig {
anchor?: AnchorPreset;
visible?: boolean;
alpha?: number;
zIndex?: number;
/** 层内排序顺序 | Order within layer */
orderInLayer?: number;
}
/**
@@ -156,7 +157,7 @@ export class UIBuilder {
transform.height = config.height ?? 30;
transform.visible = config.visible ?? true;
transform.alpha = config.alpha ?? 1;
transform.zIndex = config.zIndex ?? 0;
transform.orderInLayer = config.orderInLayer ?? 0;
if (config.anchor) {
transform.setAnchorPreset(config.anchor);

View File

@@ -1,6 +1,6 @@
import type { IScene } from '@esengine/ecs-framework';
import { ComponentRegistry } from '@esengine/ecs-framework';
import type { IRuntimeModule, IPlugin, ModuleManifest, SystemContext } from '@esengine/engine-core';
import type { IRuntimeModule, IRuntimePlugin, ModuleManifest, SystemContext } from '@esengine/engine-core';
import { EngineBridgeToken } from '@esengine/ecs-engine-bindgen';
import {
@@ -122,7 +122,7 @@ const manifest: ModuleManifest = {
editorPackage: '@esengine/ui-editor'
};
export const UIPlugin: IPlugin = {
export const UIPlugin: IRuntimePlugin = {
manifest,
runtimeModule: new UIRuntimeModule()
};

View File

@@ -38,10 +38,16 @@ export class UIInteractableComponent extends Component {
/**
* 是否阻止事件冒泡
* Whether to block event propagation
*
* 默认为 false事件会传递给下层元素。
* 设置为 true 时,该元素会阻止事件传递。
*
* Default is false, events propagate to elements below.
* Set to true to prevent event propagation.
*/
@Serialize()
@Property({ type: 'boolean', label: 'Block Events' })
public blockEvents: boolean = true;
public blockEvents: boolean = false;
// ===== 状态 State (由 UIInputSystem 更新) =====

View File

@@ -1,4 +1,5 @@
import { Component, ECSComponent, Property, Serializable, Serialize } from '@esengine/ecs-framework';
import { SortingLayers, type ISortable } from '@esengine/engine-core';
/**
* 锚点预设
@@ -25,8 +26,8 @@ export enum AnchorPreset {
* Relative positioning system based on parent, supports anchors, pivots, and stretch modes
*/
@ECSComponent('UITransform')
@Serializable({ version: 1, typeId: 'UITransform' })
export class UITransformComponent extends Component {
@Serializable({ version: 2, typeId: 'UITransform' })
export class UITransformComponent extends Component implements ISortable {
// ===== 位置 Position =====
/**
@@ -184,12 +185,27 @@ export class UITransformComponent extends Component {
public visible: boolean = true;
/**
* 渲染层级,值越大越靠前
* Render order, higher values render on top
* 排序层
* Sorting layer
*
* UI 默认使用 'UI' 层,在普通精灵之上渲染。
* UI defaults to 'UI' layer, rendering above regular sprites.
*/
@Serialize()
@Property({ type: 'integer', label: 'Z Index' })
public zIndex: number = 0;
@Property({
type: 'enum',
label: 'Sorting Layer',
options: ['Background', 'Default', 'Foreground', 'WorldOverlay', 'UI', 'ScreenOverlay', 'Modal']
})
public sortingLayer: string = SortingLayers.UI;
/**
* 层内顺序,值越大越靠前
* Order within layer, higher values render on top
*/
@Serialize()
@Property({ type: 'integer', label: 'Order in Layer' })
public orderInLayer: number = 0;
/**
* 透明度 (0-1)
@@ -232,6 +248,15 @@ export class UITransformComponent extends Component {
*/
public worldAlpha: number = 1;
/**
* 计算后的世界可见性(考虑父元素可见性)
* Computed world visibility (considering parent visibility)
*
* 如果父元素不可见,子元素也不可见。
* If parent is invisible, children are also invisible.
*/
public worldVisible: boolean = true;
/**
* 计算后的世界旋转(弧度,考虑父元素旋转)
* Computed world rotation in radians (considering parent rotation)

View File

@@ -121,9 +121,9 @@ export {
// Systems - Core
export { UILayoutSystem } from './systems/UILayoutSystem';
export { UIInputSystem, MouseButton, type UIInputEvent } from './systems/UIInputSystem';
export { UIAnimationSystem, Easing, type EasingFunction, type EasingName } from './systems/UIAnimationSystem';
export { UIRenderDataProvider, type IRenderDataProvider, type IUIRenderDataProvider } from './systems/UIRenderDataProvider';
export { UIInputSystem, type UIInputEvent } from './systems/UIInputSystem';
export { UIAnimationSystem, UIEasing, type EasingFunction, type EasingName } from './systems/UIAnimationSystem';
export { UIRenderDataProvider, type IUIRenderDataProvider } from './systems/UIRenderDataProvider';
// Systems - Render (ECS-compliant render systems)
export {

View File

@@ -13,7 +13,7 @@ export type EasingFunction = (t: number) => number;
* 预定义缓动函数
* Predefined easing functions
*/
export const Easing = {
export const UIEasing = {
linear: (t: number) => t,
// Quad
@@ -126,7 +126,10 @@ export const Easing = {
* 缓动函数名称映射
* Easing function name mapping
*/
export type EasingName = keyof typeof Easing;
export type EasingName = keyof typeof UIEasing;
/** @deprecated Use UIEasing instead */
export const Easing = UIEasing;
/**
* UI 动画系统

View File

@@ -1,4 +1,5 @@
import { EntitySystem, Matcher, Entity, Time, ECSSystem } from '@esengine/ecs-framework';
import { sortingLayerManager, MouseButton } from '@esengine/engine-core';
import { UITransformComponent } from '../components/UITransformComponent';
import { UIInteractableComponent } from '../components/UIInteractableComponent';
import { UIButtonComponent } from '../components/widgets/UIButtonComponent';
@@ -6,15 +7,9 @@ import { UISliderComponent } from '../components/widgets/UISliderComponent';
import { UIScrollViewComponent } from '../components/widgets/UIScrollViewComponent';
import type { UILayoutSystem } from './UILayoutSystem';
/**
* 鼠标按钮
* Mouse buttons
*/
export enum MouseButton {
Left = 0,
Middle = 1,
Right = 2
}
// Re-export MouseButton for backward compatibility
// 为向后兼容重新导出 MouseButton
export { MouseButton };
/**
* 输入事件数据
@@ -210,11 +205,14 @@ export class UIInputSystem extends EntitySystem {
const dt = Time.deltaTime;
// 按 zIndex 从高到低排序,确保上层元素优先处理
// 按 sortKey 从高到低排序,确保上层元素优先处理
// Sort by sortKey from high to low, ensuring top elements are processed first
const sorted = [...entities].sort((a, b) => {
const ta = a.getComponent(UITransformComponent)!;
const tb = b.getComponent(UITransformComponent)!;
return tb.zIndex - ta.zIndex;
const sortKeyA = sortingLayerManager.getSortKey(ta.sortingLayer, ta.orderInLayer);
const sortKeyB = sortingLayerManager.getSortKey(tb.sortingLayer, tb.orderInLayer);
return sortKeyB - sortKeyA;
});
let consumed = false;
@@ -225,8 +223,9 @@ export class UIInputSystem extends EntitySystem {
const transform = entity.getComponent(UITransformComponent)!;
const interactable = entity.getComponent(UIInteractableComponent)!;
// 跳过不可见或禁用的元素
if (!transform.visible || !interactable.enabled) {
// 跳过不可见或禁用的元素(使用 worldVisible 考虑父级可见性)
// Skip invisible or disabled elements (use worldVisible to consider parent visibility)
if (!transform.worldVisible || !interactable.enabled) {
// 如果之前悬停,触发离开
if (interactable.hovered) {
this.handleMouseLeave(entity, interactable);

View File

@@ -111,7 +111,8 @@ export class UILayoutSystem extends EntitySystem {
parentWidth: number,
parentHeight: number,
parentAlpha: number,
parentMatrix: Matrix2D
parentMatrix: Matrix2D,
parentVisible: boolean = true
): void {
const transform = entity.getComponent(UITransformComponent);
if (!transform) return;
@@ -194,14 +195,15 @@ export class UILayoutSystem extends EntitySystem {
transform.computedHeight = height;
transform.worldAlpha = parentAlpha * transform.alpha;
// 计算世界可见性(父元素不可见则子元素也不可见)
// Calculate world visibility (if parent is invisible, children are also invisible)
transform.worldVisible = parentVisible && transform.visible;
// 使用矩阵乘法计算世界变换
this.updateWorldMatrix(transform, parentMatrix);
transform.layoutDirty = false;
// 如果元素不可见,跳过子元素
if (!transform.visible) return;
// 处理子元素布局
const children = this.getUIChildren(entity);
if (children.length === 0) return;
@@ -224,7 +226,8 @@ export class UILayoutSystem extends EntitySystem {
width,
height,
transform.worldAlpha,
transform.localToWorldMatrix
transform.localToWorldMatrix,
transform.worldVisible
);
}
}
@@ -361,6 +364,8 @@ export class UILayoutSystem extends EntitySystem {
childTransform.computedWidth = size.width;
childTransform.computedHeight = childHeight;
childTransform.worldAlpha = parentTransform.worldAlpha * childTransform.alpha;
// 传播世界可见性 | Propagate world visibility
childTransform.worldVisible = parentTransform.worldVisible && childTransform.visible;
// 使用矩阵乘法计算世界旋转和缩放
this.updateWorldMatrix(childTransform, parentTransform.localToWorldMatrix);
childTransform.layoutDirty = false;
@@ -459,6 +464,8 @@ export class UILayoutSystem extends EntitySystem {
childTransform.computedWidth = childWidth;
childTransform.computedHeight = size.height;
childTransform.worldAlpha = parentTransform.worldAlpha * childTransform.alpha;
// 传播世界可见性 | Propagate world visibility
childTransform.worldVisible = parentTransform.worldVisible && childTransform.visible;
// 使用矩阵乘法计算世界旋转和缩放
this.updateWorldMatrix(childTransform, parentTransform.localToWorldMatrix);
childTransform.layoutDirty = false;
@@ -515,6 +522,8 @@ export class UILayoutSystem extends EntitySystem {
childTransform.computedWidth = cellWidth;
childTransform.computedHeight = cellHeight;
childTransform.worldAlpha = parentTransform.worldAlpha * childTransform.alpha;
// 传播世界可见性 | Propagate world visibility
childTransform.worldVisible = parentTransform.worldVisible && childTransform.visible;
// 使用矩阵乘法计算世界旋转和缩放
this.updateWorldMatrix(childTransform, parentTransform.localToWorldMatrix);
childTransform.layoutDirty = false;
@@ -575,7 +584,8 @@ export class UILayoutSystem extends EntitySystem {
parentTransform.computedWidth,
parentTransform.computedHeight,
parentTransform.worldAlpha,
parentTransform.localToWorldMatrix
parentTransform.localToWorldMatrix,
parentTransform.worldVisible
);
}
}

View File

@@ -40,11 +40,14 @@ export class UIButtonRenderSystem extends EntitySystem {
const collector = getUIRenderCollector();
for (const entity of entities) {
const transform = entity.getComponent(UITransformComponent)!;
const button = entity.getComponent(UIButtonComponent)!;
const transform = entity.getComponent(UITransformComponent);
const button = entity.getComponent(UIButtonComponent);
const render = entity.getComponent(UIRenderComponent);
if (!transform.visible) continue;
// 空值检查 | Null check
if (!transform || !button) continue;
if (!transform.worldVisible) continue;
const x = transform.worldX ?? transform.x;
const y = transform.worldY ?? transform.y;
@@ -55,7 +58,9 @@ export class UIButtonRenderSystem extends EntitySystem {
const width = (transform.computedWidth ?? transform.width) * scaleX;
const height = (transform.computedHeight ?? transform.height) * scaleY;
const alpha = transform.worldAlpha ?? transform.alpha;
const baseOrder = 100 + transform.zIndex;
// 使用排序层和层内顺序 | Use sorting layer and order in layer
const sortingLayer = transform.sortingLayer;
const orderInLayer = transform.orderInLayer;
// 使用 transform 的 pivot 作为旋转/缩放中心
const pivotX = transform.pivotX;
const pivotY = transform.pivotY;
@@ -73,12 +78,13 @@ export class UIButtonRenderSystem extends EntitySystem {
width, height,
0xFFFFFF, // White tint for texture
alpha,
baseOrder,
sortingLayer,
orderInLayer,
{
rotation,
pivotX,
pivotY,
texturePath: textureGuid
textureGuid
}
);
}
@@ -94,7 +100,8 @@ export class UIButtonRenderSystem extends EntitySystem {
width, height,
button.currentColor,
bgAlpha * alpha,
baseOrder + (button.useTexture() ? 0.05 : 0),
sortingLayer,
orderInLayer + (button.useTexture() ? 1 : 0),
{
rotation,
pivotX,
@@ -113,7 +120,8 @@ export class UIButtonRenderSystem extends EntitySystem {
render.borderWidth,
render.borderColor,
render.borderAlpha * alpha,
baseOrder + 0.1,
sortingLayer,
orderInLayer + 2,
rotation,
pivotX,
pivotY
@@ -133,7 +141,8 @@ export class UIButtonRenderSystem extends EntitySystem {
borderWidth: number,
borderColor: number,
alpha: number,
sortOrder: number,
sortingLayer: string,
orderInLayer: number,
rotation: number,
pivotX: number,
pivotY: number
@@ -148,7 +157,7 @@ export class UIButtonRenderSystem extends EntitySystem {
collector.addRect(
(left + right) / 2, top - borderWidth / 2,
width, borderWidth,
borderColor, alpha, sortOrder,
borderColor, alpha, sortingLayer, orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
@@ -156,7 +165,7 @@ export class UIButtonRenderSystem extends EntitySystem {
collector.addRect(
(left + right) / 2, bottom + borderWidth / 2,
width, borderWidth,
borderColor, alpha, sortOrder,
borderColor, alpha, sortingLayer, orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
@@ -165,7 +174,7 @@ export class UIButtonRenderSystem extends EntitySystem {
collector.addRect(
left + borderWidth / 2, (top + bottom) / 2,
borderWidth, sideBorderHeight,
borderColor, alpha, sortOrder,
borderColor, alpha, sortingLayer, orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
@@ -173,7 +182,7 @@ export class UIButtonRenderSystem extends EntitySystem {
collector.addRect(
right - borderWidth / 2, (top + bottom) / 2,
borderWidth, sideBorderHeight,
borderColor, alpha, sortOrder,
borderColor, alpha, sortingLayer, orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
}

View File

@@ -38,10 +38,13 @@ export class UIProgressBarRenderSystem extends EntitySystem {
const collector = getUIRenderCollector();
for (const entity of entities) {
const transform = entity.getComponent(UITransformComponent)!;
const progressBar = entity.getComponent(UIProgressBarComponent)!;
const transform = entity.getComponent(UITransformComponent);
const progressBar = entity.getComponent(UIProgressBarComponent);
if (!transform.visible) continue;
// 空值检查 | Null check
if (!transform || !progressBar) continue;
if (!transform.worldVisible) continue;
const x = transform.worldX ?? transform.x;
const y = transform.worldY ?? transform.y;
@@ -52,7 +55,9 @@ export class UIProgressBarRenderSystem extends EntitySystem {
const width = (transform.computedWidth ?? transform.width) * scaleX;
const height = (transform.computedHeight ?? transform.height) * scaleY;
const alpha = transform.worldAlpha ?? transform.alpha;
const baseOrder = 100 + transform.zIndex;
// 使用排序层和层内顺序 | Use sorting layer and order in layer
const sortingLayer = transform.sortingLayer;
const orderInLayer = transform.orderInLayer;
// 使用 transform 的 pivot 作为旋转/缩放中心
const pivotX = transform.pivotX;
const pivotY = transform.pivotY;
@@ -67,7 +72,8 @@ export class UIProgressBarRenderSystem extends EntitySystem {
renderX, renderY, width, height,
progressBar.backgroundColor,
progressBar.backgroundAlpha * alpha,
baseOrder,
sortingLayer,
orderInLayer,
{
rotation,
pivotX,
@@ -84,7 +90,8 @@ export class UIProgressBarRenderSystem extends EntitySystem {
progressBar.borderWidth,
progressBar.borderColor,
alpha,
baseOrder + 0.2,
sortingLayer,
orderInLayer + 2,
transform,
pivotX,
pivotY
@@ -98,13 +105,13 @@ export class UIProgressBarRenderSystem extends EntitySystem {
if (progressBar.showSegments) {
this.renderSegmentedFill(
collector, renderX, renderY, width, height,
progress, progressBar, alpha, baseOrder + 0.1, transform,
progress, progressBar, alpha, sortingLayer, orderInLayer + 1, transform,
pivotX, pivotY
);
} else {
this.renderSolidFill(
collector, renderX, renderY, width, height,
progress, progressBar, alpha, baseOrder + 0.1, transform,
progress, progressBar, alpha, sortingLayer, orderInLayer + 1, transform,
pivotX, pivotY
);
}
@@ -125,7 +132,8 @@ export class UIProgressBarRenderSystem extends EntitySystem {
progress: number,
progressBar: UIProgressBarComponent,
alpha: number,
sortOrder: number,
sortingLayer: string,
orderInLayer: number,
transform: UITransformComponent,
pivotX: number,
pivotY: number
@@ -188,7 +196,8 @@ export class UIProgressBarRenderSystem extends EntitySystem {
fillX, fillY, fillWidth, fillHeight,
fillColor,
progressBar.fillAlpha * alpha,
sortOrder,
sortingLayer,
orderInLayer,
{
rotation,
pivotX: 0.5,
@@ -210,7 +219,8 @@ export class UIProgressBarRenderSystem extends EntitySystem {
progress: number,
progressBar: UIProgressBarComponent,
alpha: number,
sortOrder: number,
sortingLayer: string,
orderInLayer: number,
transform: UITransformComponent,
pivotX: number,
pivotY: number
@@ -292,7 +302,8 @@ export class UIProgressBarRenderSystem extends EntitySystem {
segmentHeight,
segmentColor,
progressBar.fillAlpha * alpha,
sortOrder + i * 0.001,
sortingLayer,
orderInLayer,
{
rotation,
pivotX: 0.5,
@@ -315,7 +326,8 @@ export class UIProgressBarRenderSystem extends EntitySystem {
borderWidth: number,
borderColor: number,
alpha: number,
sortOrder: number,
sortingLayer: string,
orderInLayer: number,
transform: UITransformComponent,
pivotX: number,
pivotY: number
@@ -332,7 +344,7 @@ export class UIProgressBarRenderSystem extends EntitySystem {
collector.addRect(
(left + right) / 2, top - borderWidth / 2,
width, borderWidth,
borderColor, alpha, sortOrder,
borderColor, alpha, sortingLayer, orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
@@ -340,7 +352,7 @@ export class UIProgressBarRenderSystem extends EntitySystem {
collector.addRect(
(left + right) / 2, bottom + borderWidth / 2,
width, borderWidth,
borderColor, alpha, sortOrder,
borderColor, alpha, sortingLayer, orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
@@ -349,7 +361,7 @@ export class UIProgressBarRenderSystem extends EntitySystem {
collector.addRect(
left + borderWidth / 2, (top + bottom) / 2,
borderWidth, sideBorderHeight,
borderColor, alpha, sortOrder,
borderColor, alpha, sortingLayer, orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
@@ -357,7 +369,7 @@ export class UIProgressBarRenderSystem extends EntitySystem {
collector.addRect(
right - borderWidth / 2, (top + bottom) / 2,
borderWidth, sideBorderHeight,
borderColor, alpha, sortOrder,
borderColor, alpha, sortingLayer, orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
}

View File

@@ -49,10 +49,14 @@ export class UIRectRenderSystem extends EntitySystem {
continue;
}
const transform = entity.getComponent(UITransformComponent)!;
const render = entity.getComponent(UIRenderComponent)!;
const transform = entity.getComponent(UITransformComponent);
const render = entity.getComponent(UIRenderComponent);
if (!transform.visible) continue;
// 空值检查 - 组件可能在反序列化或初始化期间尚未就绪
// Null check - component may not be ready during deserialization or initialization
if (!transform || !render) continue;
if (!transform.worldVisible) continue;
const x = transform.worldX ?? transform.x;
const y = transform.worldY ?? transform.y;
@@ -64,7 +68,9 @@ export class UIRectRenderSystem extends EntitySystem {
const alpha = transform.worldAlpha ?? transform.alpha;
// 使用世界旋转(考虑父级旋转)
const rotation = transform.worldRotation ?? transform.rotation;
const baseOrder = 100 + transform.zIndex;
// 使用排序层和层内顺序 | Use sorting layer and order in layer
const sortingLayer = transform.sortingLayer;
const orderInLayer = transform.orderInLayer;
// 使用 transform 的 pivot 作为旋转/缩放中心
const pivotX = transform.pivotX;
const pivotY = transform.pivotY;
@@ -85,7 +91,8 @@ export class UIRectRenderSystem extends EntitySystem {
height + render.shadowBlur * 2,
render.shadowColor,
render.shadowAlpha * alpha,
baseOrder - 0.1,
sortingLayer,
orderInLayer - 1, // Shadow renders below main content
{
rotation,
pivotX,
@@ -97,7 +104,7 @@ export class UIRectRenderSystem extends EntitySystem {
// Render texture if present
// 如果有纹理,渲染纹理
if (render.textureGuid) {
const texturePath = typeof render.textureGuid === 'string' ? render.textureGuid : undefined;
const textureGuid = typeof render.textureGuid === 'string' ? render.textureGuid : undefined;
const textureId = typeof render.textureGuid === 'number' ? render.textureGuid : undefined;
collector.addRect(
@@ -105,13 +112,14 @@ export class UIRectRenderSystem extends EntitySystem {
width, height,
render.textureTint,
alpha,
baseOrder,
sortingLayer,
orderInLayer,
{
rotation,
pivotX,
pivotY,
textureId,
texturePath,
textureGuid,
uv: render.textureUV
? [render.textureUV.u0, render.textureUV.v0, render.textureUV.u1, render.textureUV.v1]
: undefined
@@ -126,7 +134,8 @@ export class UIRectRenderSystem extends EntitySystem {
width, height,
render.backgroundColor,
render.backgroundAlpha * alpha,
baseOrder,
sortingLayer,
orderInLayer,
{
rotation,
pivotX,
@@ -144,7 +153,8 @@ export class UIRectRenderSystem extends EntitySystem {
render.borderWidth,
render.borderColor,
render.borderAlpha * alpha,
baseOrder + 0.1,
sortingLayer,
orderInLayer + 1, // Border renders above main content
rotation,
pivotX,
pivotY
@@ -164,7 +174,8 @@ export class UIRectRenderSystem extends EntitySystem {
borderWidth: number,
borderColor: number,
alpha: number,
sortOrder: number,
sortingLayer: string,
orderInLayer: number,
rotation: number,
pivotX: number,
pivotY: number
@@ -181,7 +192,7 @@ export class UIRectRenderSystem extends EntitySystem {
collector.addRect(
topBorderCenterX, topBorderCenterY,
width, borderWidth,
borderColor, alpha, sortOrder,
borderColor, alpha, sortingLayer, orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
@@ -190,7 +201,7 @@ export class UIRectRenderSystem extends EntitySystem {
collector.addRect(
topBorderCenterX, bottomBorderCenterY,
width, borderWidth,
borderColor, alpha, sortOrder,
borderColor, alpha, sortingLayer, orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
@@ -201,7 +212,7 @@ export class UIRectRenderSystem extends EntitySystem {
collector.addRect(
leftBorderCenterX, sideBorderCenterY,
borderWidth, sideBorderHeight,
borderColor, alpha, sortOrder,
borderColor, alpha, sortingLayer, orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
@@ -210,7 +221,7 @@ export class UIRectRenderSystem extends EntitySystem {
collector.addRect(
rightBorderCenterX, sideBorderCenterY,
borderWidth, sideBorderHeight,
borderColor, alpha, sortOrder,
borderColor, alpha, sortingLayer, orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
}

View File

@@ -14,6 +14,9 @@
* - 预览模式 (previewMode=true): UI 作为屏幕叠加层渲染
*/
import { isValidGUID } from '@esengine/asset-system';
import { sortingLayerManager, SortingLayers } from '@esengine/engine-core';
/**
* A single render primitive (rectangle with optional texture)
* 单个渲染原语(可选带纹理的矩形)
@@ -57,12 +60,14 @@ export interface UIRenderPrimitive {
pivotY: number;
/** Packed color (0xAABBGGRR) | 打包颜色 */
color: number;
/** Sort order (lower = rendered first/behind) | 排序顺序 */
sortOrder: number;
/** 排序层 | Sorting layer */
sortingLayer: string;
/** 层内排序顺序 | Order within layer */
orderInLayer: number;
/** Optional texture ID | 可选纹理 ID */
textureId?: number;
/** Optional texture path | 可选纹理路径 */
texturePath?: string;
/** Optional texture GUID | 可选纹理 GUID */
textureGuid?: string;
/** UV coordinates [u0, v0, u1, v1] | UV 坐标 */
uv?: [number, number, number, number];
}
@@ -77,8 +82,12 @@ export interface ProviderRenderData {
uvs: Float32Array;
colors: Uint32Array;
tileCount: number;
sortingOrder: number;
texturePath?: string;
/** 排序层 | Sorting layer */
sortingLayer: string;
/** 层内排序顺序 | Order within layer */
orderInLayer: number;
/** 纹理 GUID如果 textureId 为 0 则使用)| Texture GUID (used if textureId is 0) */
textureGuid?: string;
}
/**
@@ -115,13 +124,14 @@ export class UIRenderCollector {
height: number,
color: number,
alpha: number,
sortOrder: number,
sortingLayer: string,
orderInLayer: number,
options?: {
rotation?: number;
pivotX?: number;
pivotY?: number;
textureId?: number;
texturePath?: string;
textureGuid?: string;
uv?: [number, number, number, number];
}
): void {
@@ -141,9 +151,10 @@ export class UIRenderCollector {
pivotX: options?.pivotX ?? 0,
pivotY: options?.pivotY ?? 0,
color: packedColor,
sortOrder,
sortingLayer,
orderInLayer,
textureId: options?.textureId,
texturePath: options?.texturePath,
textureGuid: options?.textureGuid,
uv: options?.uv
};
@@ -182,17 +193,23 @@ export class UIRenderCollector {
return [];
}
// Sort by sortOrder
// 按 sortOrder 排序
primitives.sort((a, b) => a.sortOrder - b.sortOrder);
// Sort by sortKey (layer order * 10000 + orderInLayer)
// 按 sortKey 排序(层顺序 * 10000 + 层内顺序)
primitives.sort((a, b) => {
const sortKeyA = sortingLayerManager.getSortKey(a.sortingLayer, a.orderInLayer);
const sortKeyB = sortingLayerManager.getSortKey(b.sortingLayer, b.orderInLayer);
return sortKeyA - sortKeyB;
});
// Group by texture (primitives with same texture can be batched)
// 按纹理分组(相同纹理的原语可以批处理)
// Group by texture + sortingLayer (primitives with same texture and layer can be batched)
// 按纹理 + 排序层分组(相同纹理和层的原语可以批处理)
const groups = new Map<string, UIRenderPrimitive[]>();
for (const prim of primitives) {
// Use texture path or 'solid' for solid color rects
const key = prim.texturePath ?? (prim.textureId?.toString() ?? 'solid');
// Use texture GUID or 'solid' for solid color rects, combined with sorting layer
// 使用纹理 GUID 或 'solid' 表示纯色矩形,与排序层组合
const textureKey = prim.textureGuid ?? (prim.textureId?.toString() ?? 'solid');
const key = `${prim.sortingLayer}:${prim.orderInLayer}:${textureKey}`;
let group = groups.get(key);
if (!group) {
group = [];
@@ -212,6 +229,10 @@ export class UIRenderCollector {
const uvs = new Float32Array(count * 4);
const colors = new Uint32Array(count);
// Use the first primitive's sorting info (all in group have same layer/order)
// 使用第一个原语的排序信息(组内所有原语层/顺序相同)
const firstPrim = prims[0];
for (let i = 0; i < count; i++) {
const p = prims[i];
const tOffset = i * 7;
@@ -246,28 +267,32 @@ export class UIRenderCollector {
colors[i] = p.color;
}
// Use the minimum sortOrder from the group as the batch sortingOrder
const minSortOrder = Math.min(...prims.map(p => p.sortOrder));
const renderData: ProviderRenderData = {
transforms,
textureIds,
uvs,
colors,
tileCount: count,
sortingOrder: minSortOrder
sortingLayer: firstPrim.sortingLayer,
orderInLayer: firstPrim.orderInLayer
};
// Add texture path if not solid color
if (key !== 'solid' && isNaN(parseInt(key))) {
renderData.texturePath = key;
// Add texture GUID if it's a valid GUID (UUID format)
// 如果是有效的 GUIDUUID 格式),则添加纹理 GUID
if (firstPrim.textureGuid && isValidGUID(firstPrim.textureGuid)) {
renderData.textureGuid = firstPrim.textureGuid;
}
result.push(renderData);
}
// Sort result by sortingOrder
result.sort((a, b) => a.sortingOrder - b.sortingOrder);
// Sort result by sortKey
// 按 sortKey 排序结果
result.sort((a, b) => {
const sortKeyA = sortingLayerManager.getSortKey(a.sortingLayer, a.orderInLayer);
const sortKeyB = sortingLayerManager.getSortKey(b.sortingLayer, b.orderInLayer);
return sortKeyA - sortKeyB;
});
return result;
}

View File

@@ -39,10 +39,13 @@ export class UIScrollViewRenderSystem extends EntitySystem {
const collector = getUIRenderCollector();
for (const entity of entities) {
const transform = entity.getComponent(UITransformComponent)!;
const scrollView = entity.getComponent(UIScrollViewComponent)!;
const transform = entity.getComponent(UITransformComponent);
const scrollView = entity.getComponent(UIScrollViewComponent);
if (!transform.visible) continue;
// 空值检查 | Null check
if (!transform || !scrollView) continue;
if (!transform.worldVisible) continue;
const x = transform.worldX ?? transform.x;
const y = transform.worldY ?? transform.y;
@@ -53,7 +56,9 @@ export class UIScrollViewRenderSystem extends EntitySystem {
const width = (transform.computedWidth ?? transform.width) * scaleX;
const height = (transform.computedHeight ?? transform.height) * scaleY;
const alpha = transform.worldAlpha ?? transform.alpha;
const baseOrder = 100 + transform.zIndex;
// 使用排序层和层内顺序 | Use sorting layer and order in layer
const sortingLayer = transform.sortingLayer;
const orderInLayer = transform.orderInLayer;
// 使用 transform 的 pivot 计算位置
const pivotX = transform.pivotX;
const pivotY = transform.pivotY;
@@ -71,7 +76,7 @@ export class UIScrollViewRenderSystem extends EntitySystem {
this.renderVerticalScrollbar(
collector,
baseX, baseY, width, height,
scrollView, alpha, baseOrder, rotation
scrollView, alpha, sortingLayer, orderInLayer, rotation
);
}
@@ -81,7 +86,7 @@ export class UIScrollViewRenderSystem extends EntitySystem {
this.renderHorizontalScrollbar(
collector,
baseX, baseY, width, height,
scrollView, alpha, baseOrder, rotation
scrollView, alpha, sortingLayer, orderInLayer, rotation
);
}
}
@@ -97,7 +102,8 @@ export class UIScrollViewRenderSystem extends EntitySystem {
viewWidth: number, viewHeight: number,
scrollView: UIScrollViewComponent,
alpha: number,
baseOrder: number,
sortingLayer: string,
orderInLayer: number,
rotation: number
): void {
const scrollbarWidth = scrollView.scrollbarWidth;
@@ -117,7 +123,8 @@ export class UIScrollViewRenderSystem extends EntitySystem {
scrollbarWidth, trackHeight,
scrollView.scrollbarTrackColor,
scrollView.scrollbarTrackAlpha * alpha,
baseOrder + 0.5,
sortingLayer,
orderInLayer + 5,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
}
@@ -140,7 +147,8 @@ export class UIScrollViewRenderSystem extends EntitySystem {
scrollbarWidth - 2, metrics.size,
scrollView.scrollbarColor,
handleAlpha * alpha,
baseOrder + 0.6,
sortingLayer,
orderInLayer + 6,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
}
@@ -155,7 +163,8 @@ export class UIScrollViewRenderSystem extends EntitySystem {
viewWidth: number, viewHeight: number,
scrollView: UIScrollViewComponent,
alpha: number,
baseOrder: number,
sortingLayer: string,
orderInLayer: number,
rotation: number
): void {
const scrollbarWidth = scrollView.scrollbarWidth;
@@ -175,7 +184,8 @@ export class UIScrollViewRenderSystem extends EntitySystem {
trackWidth, scrollbarWidth,
scrollView.scrollbarTrackColor,
scrollView.scrollbarTrackAlpha * alpha,
baseOrder + 0.5,
sortingLayer,
orderInLayer + 5,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
}
@@ -198,7 +208,8 @@ export class UIScrollViewRenderSystem extends EntitySystem {
metrics.size, scrollbarWidth - 2,
scrollView.scrollbarColor,
handleAlpha * alpha,
baseOrder + 0.6,
sortingLayer,
orderInLayer + 6,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
}

View File

@@ -38,10 +38,13 @@ export class UISliderRenderSystem extends EntitySystem {
const collector = getUIRenderCollector();
for (const entity of entities) {
const transform = entity.getComponent(UITransformComponent)!;
const slider = entity.getComponent(UISliderComponent)!;
const transform = entity.getComponent(UITransformComponent);
const slider = entity.getComponent(UISliderComponent);
if (!transform.visible) continue;
// 空值检查 | Null check
if (!transform || !slider) continue;
if (!transform.worldVisible) continue;
const x = transform.worldX ?? transform.x;
const y = transform.worldY ?? transform.y;
@@ -52,7 +55,9 @@ export class UISliderRenderSystem extends EntitySystem {
const width = (transform.computedWidth ?? transform.width) * scaleX;
const height = (transform.computedHeight ?? transform.height) * scaleY;
const alpha = transform.worldAlpha ?? transform.alpha;
const baseOrder = 100 + transform.zIndex;
// 使用排序层和层内顺序 | Use sorting layer and order in layer
const sortingLayer = transform.sortingLayer;
const orderInLayer = transform.orderInLayer;
// 使用 transform 的 pivot 计算中心位置
const pivotX = transform.pivotX;
const pivotY = transform.pivotY;
@@ -82,7 +87,8 @@ export class UISliderRenderSystem extends EntitySystem {
trackLength, trackThickness,
slider.trackColor,
slider.trackAlpha * alpha,
baseOrder,
sortingLayer,
orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
} else {
@@ -91,7 +97,8 @@ export class UISliderRenderSystem extends EntitySystem {
trackThickness, trackLength,
slider.trackColor,
slider.trackAlpha * alpha,
baseOrder,
sortingLayer,
orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
}
@@ -110,7 +117,8 @@ export class UISliderRenderSystem extends EntitySystem {
fillLength, trackThickness,
slider.fillColor,
slider.fillAlpha * alpha,
baseOrder + 0.1,
sortingLayer,
orderInLayer + 1,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
} else {
@@ -121,7 +129,8 @@ export class UISliderRenderSystem extends EntitySystem {
trackThickness, fillLength,
slider.fillColor,
slider.fillAlpha * alpha,
baseOrder + 0.1,
sortingLayer,
orderInLayer + 1,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
}
@@ -133,7 +142,7 @@ export class UISliderRenderSystem extends EntitySystem {
this.renderTicks(
collector, centerX, centerY,
trackLength, trackThickness,
slider, alpha, baseOrder + 0.05,
slider, alpha, sortingLayer, orderInLayer,
isHorizontal, rotation
);
}
@@ -156,7 +165,8 @@ export class UISliderRenderSystem extends EntitySystem {
slider.handleWidth, slider.handleHeight,
0x000000,
0.3 * alpha,
baseOrder + 0.15,
sortingLayer,
orderInLayer + 2,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
}
@@ -168,7 +178,8 @@ export class UISliderRenderSystem extends EntitySystem {
slider.handleWidth, slider.handleHeight,
handleColor,
alpha,
baseOrder + 0.2,
sortingLayer,
orderInLayer + 3,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
@@ -182,7 +193,8 @@ export class UISliderRenderSystem extends EntitySystem {
slider.handleBorderWidth,
slider.handleBorderColor,
alpha,
baseOrder + 0.25,
sortingLayer,
orderInLayer + 4,
rotation
);
}
@@ -199,7 +211,8 @@ export class UISliderRenderSystem extends EntitySystem {
trackLength: number, trackThickness: number,
slider: UISliderComponent,
alpha: number,
sortOrder: number,
sortingLayer: string,
orderInLayer: number,
isHorizontal: boolean,
rotation: number
): void {
@@ -231,7 +244,8 @@ export class UISliderRenderSystem extends EntitySystem {
tickWidth, tickHeight,
slider.tickColor,
alpha,
sortOrder,
sortingLayer,
orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
}
@@ -248,7 +262,8 @@ export class UISliderRenderSystem extends EntitySystem {
borderWidth: number,
borderColor: number,
alpha: number,
sortOrder: number,
sortingLayer: string,
orderInLayer: number,
rotation: number
): void {
const halfW = width / 2;
@@ -259,7 +274,7 @@ export class UISliderRenderSystem extends EntitySystem {
collector.addRect(
x, y - halfH + halfB,
width, borderWidth,
borderColor, alpha, sortOrder,
borderColor, alpha, sortingLayer, orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
@@ -267,7 +282,7 @@ export class UISliderRenderSystem extends EntitySystem {
collector.addRect(
x, y + halfH - halfB,
width, borderWidth,
borderColor, alpha, sortOrder,
borderColor, alpha, sortingLayer, orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
@@ -275,7 +290,7 @@ export class UISliderRenderSystem extends EntitySystem {
collector.addRect(
x - halfW + halfB, y,
borderWidth, height - borderWidth * 2,
borderColor, alpha, sortOrder,
borderColor, alpha, sortingLayer, orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
@@ -283,7 +298,7 @@ export class UISliderRenderSystem extends EntitySystem {
collector.addRect(
x + halfW - halfB, y,
borderWidth, height - borderWidth * 2,
borderColor, alpha, sortOrder,
borderColor, alpha, sortingLayer, orderInLayer,
{ rotation, pivotX: 0.5, pivotY: 0.5 }
);
}

View File

@@ -94,10 +94,14 @@ export class UITextRenderSystem extends EntitySystem {
const collector = getUIRenderCollector();
for (const entity of entities) {
const transform = entity.getComponent(UITransformComponent)!;
const text = entity.getComponent(UITextComponent)!;
const transform = entity.getComponent(UITransformComponent);
const text = entity.getComponent(UITextComponent);
if (!transform.visible || !text.text) continue;
// 空值检查 - 组件可能在反序列化或初始化期间尚未就绪
// Null check - component may not be ready during deserialization or initialization
if (!transform || !text) continue;
if (!transform.worldVisible || !text.text) continue;
const x = transform.worldX ?? transform.x;
const y = transform.worldY ?? transform.y;
@@ -108,7 +112,9 @@ export class UITextRenderSystem extends EntitySystem {
const width = (transform.computedWidth ?? transform.width) * scaleX;
const height = (transform.computedHeight ?? transform.height) * scaleY;
const alpha = transform.worldAlpha ?? transform.alpha;
const baseOrder = 100 + transform.zIndex;
// 使用排序层和层内顺序 | Use sorting layer and order in layer
const sortingLayer = transform.sortingLayer;
const orderInLayer = transform.orderInLayer;
// 使用 transform 的 pivot 作为旋转/缩放中心
const pivotX = transform.pivotX;
const pivotY = transform.pivotY;
@@ -131,7 +137,8 @@ export class UITextRenderSystem extends EntitySystem {
width, height,
0xFFFFFF, // White tint (color is baked into texture)
alpha,
baseOrder + 1, // Text renders above background
sortingLayer,
orderInLayer + 1, // Text renders above background
{
rotation,
pivotX,

View File

@@ -3,7 +3,7 @@
* UI module service tokens
*/
import { createServiceToken } from '@esengine/engine-core';
import { createServiceToken } from '@esengine/ecs-framework';
import type { UILayoutSystem } from './systems/UILayoutSystem';
import type { UIInputSystem } from './systems/UIInputSystem';
import type { UIRenderDataProvider } from './systems/UIRenderDataProvider';

View File

@@ -1,22 +1,12 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "bundler",
"lib": ["ES2020", "DOM"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"composite": false,
"declaration": true,
"declarationMap": true,
"jsx": "react-jsx",
"resolveJsonModule": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
"outDir": "./dist",
"rootDir": "./src",
"jsx": "react-jsx"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]