2025-11-23 14:49:37 +08:00
|
|
|
|
import type { AssetReference } from '@esengine/asset-system';
|
2025-12-08 21:26:35 +08:00
|
|
|
|
import { Component, ECSComponent, Property, Serializable, Serialize } from '@esengine/ecs-framework';
|
2025-12-13 19:44:08 +08:00
|
|
|
|
import { SortingLayers, type ISortable } from '@esengine/engine-core';
|
refactor(ui): UI 系统架构重构 (#309)
* feat(ui): 动态图集系统与渲染调试增强
## 核心功能
### 动态图集系统 (Dynamic Atlas)
- 新增 DynamicAtlasManager:运行时纹理打包,支持 MaxRects 算法
- 新增 DynamicAtlasService:自动纹理加载与图集管理
- 新增 BinPacker:高效矩形打包算法
- 支持动态/固定两种扩展策略
- 自动 UV 重映射,实现 UI 元素合批渲染
### Frame Debugger 增强
- 新增合批分析面板,显示批次中断原因
- 新增 UI 元素层级信息(depth, worldOrderInLayer)
- 新增实体高亮功能,点击可在场景中定位
- 新增动态图集可视化面板
- 改进渲染原语详情展示
### 闪光效果 (Shiny Effect)
- 新增 UIShinyEffectComponent:UI 闪光参数配置
- 新增 UIShinyEffectSystem:材质覆盖驱动的闪光动画
- 新增 ShinyEffectComponent/System(Sprite 版本)
## 引擎层改进
### Rust 纹理管理扩展
- create_blank_texture:创建空白 GPU 纹理
- update_texture_region:局部纹理更新
- 支持动态图集的 GPU 端操作
### 材质系统
- 新增 effects/ 目录:ShinyEffect 等效果实现
- 新增 interfaces/ 目录:IMaterial 等接口定义
- 新增 mixins/ 目录:可组合的材质功能
### EngineBridge 扩展
- 新增 createBlankTexture/updateTextureRegion 方法
- 改进纹理加载回调机制
## UI 渲染改进
- UIRenderCollector:支持合批调试信息
- 稳定排序:addIndex 保证渲染顺序一致性
- 九宫格渲染优化
- 材质覆盖支持
## 其他改进
- 国际化:新增 Frame Debugger 相关翻译
- 编辑器:新增渲染调试入口
- 文档:新增架构设计文档目录
* refactor(ui): 引入新基础组件架构与渲染工具函数
Phase 1 重构 - 组件职责分离与代码复用:
新增基础组件层:
- UIGraphicComponent: 所有可视 UI 元素的基类(颜色、透明度、raycast)
- UIImageComponent: 纹理显示组件(支持简单、切片、平铺、填充模式)
- UISelectableComponent: 可交互元素的基类(状态管理、颜色过渡)
新增渲染工具:
- UIRenderUtils: 提取共享的坐标计算、边框渲染、阴影渲染等工具函数
- getUIRenderTransform: 统一的变换数据提取
- renderBorder/renderShadow: 复用的边框和阴影渲染逻辑
新增渲染系统:
- UIGraphicRenderSystem: 处理新基础组件的统一渲染器
重构现有系统:
- UIRectRenderSystem: 使用新工具函数,移除重复代码
- UIButtonRenderSystem: 使用新工具函数,移除重复代码
这些改动为后续统一渲染系统奠定基础。
* refactor(ui): UIProgressBarRenderSystem 使用渲染工具函数
- 使用 getUIRenderTransform 替代手动变换计算
- 使用 renderBorder 工具函数替代重复的边框渲染
- 使用 lerpColor 工具函数替代重复的颜色插值
- 简化方法签名,使用 UIRenderTransform 类型
- 移除约 135 行重复代码
* refactor(ui): Slider 和 ScrollView 渲染系统使用工具函数
- UISliderRenderSystem: 使用 getUIRenderTransform,简化方法签名
- UIScrollViewRenderSystem: 使用 getUIRenderTransform,简化方法签名
- 统一使用 UIRenderTransform 类型减少参数传递
- 消除重复的变换计算代码
* refactor(ui): 使用 UIWidgetMarker 消除硬编码组件依赖
- 新增 UIWidgetMarker 标记组件
- UIRectRenderSystem 改为检查标记而非硬编码4种组件类型
- 各 Widget 渲染系统自动添加标记组件
- 减少模块间耦合,提高可扩展性
* feat(ui): 实现 Canvas 隔离机制
- 新增 UICanvasComponent 定义 Canvas 渲染组
- UITransformComponent 添加 Canvas 相关字段:canvasEntityId, worldSortingLayer, pixelPerfect
- UILayoutSystem 传播 Canvas 设置给子元素
- UIRenderUtils 使用 Canvas 继承的排序层
- 支持嵌套 Canvas 和不同渲染模式
* refactor(ui): 统一纹理管理工具函数
Phase 4: 纹理管理统一
新增:
- UITextureUtils.ts: 统一的纹理描述符接口和验证函数
- UITextureDescriptor: 支持 GUID/textureId/path 多种纹理源
- isValidTextureGuid: GUID 验证
- getTextureKey: 获取用于合批的纹理键
- normalizeTextureDescriptor: 规范化各种输入格式
- utils/index.ts: 工具函数导出
修改:
- UIGraphicRenderSystem: 使用新的纹理工具函数
- index.ts: 导出纹理工具类型和函数
* refactor(ui): 实现统一的脏标记机制
Phase 5: Dirty 标记机制
新增:
- UIDirtyFlags.ts: 位标记枚举和追踪工具
- UIDirtyFlags: Visual/Layout/Transform/Material/Text 标记
- IDirtyTrackable: 脏追踪接口
- DirtyTracker: 辅助工具类
- 帧级别脏状态追踪 (markFrameDirty, isFrameDirty)
修改:
- UIGraphicComponent: 实现 IDirtyTrackable
- 属性 setter 自动设置脏标记
- 保留 setDirty/clearDirty 向后兼容
- UIImageComponent: 所有属性支持脏追踪
- textureGuid/imageType/fillAmount 等变化自动标记
- UIGraphicRenderSystem: 使用 clearDirtyFlags()
导出:
- UIDirtyFlags, IDirtyTrackable, DirtyTracker
- markFrameDirty, isFrameDirty, clearFrameDirty
* refactor(ui): 移除过时的 dirty flag API
移除 UIGraphicComponent 中的兼容性 API:
- 移除 _isDirty getter/setter
- 移除 setDirty() 方法
- 移除 clearDirty() 方法
现在统一使用新的 dirty flag 系统:
- isDirty() / hasDirtyFlag(flags)
- markDirty(flags) / clearDirtyFlags()
* fix(ui): 修复两个 TODO 功能
1. 滑块手柄命中测试 (UIInputSystem)
- UISliderComponent 添加 getHandleBounds() 计算手柄边界
- UISliderComponent 添加 isPointInHandle() 精确命中测试
- UIInputSystem.handleSlider() 使用精确测试更新悬停状态
2. 径向填充渲染 (UIGraphicRenderSystem)
- 实现 renderRadialFill() 方法
- 支持 radial90/radial180/radial360 三种模式
- 支持 fillOrigin (top/right/bottom/left) 和 fillClockwise
- 使用多段矩形近似饼形填充效果
* feat(ui): 完善 UI 系统架构和九宫格渲染
* fix(ui): 修复文本渲染层级问题并清理调试代码
- 修复纹理就绪后调用 invalidateUIRenderCaches() 导致的无限循环
- 移除 UITextRenderSystem、UIButtonRenderSystem、UIRectRenderSystem 中的首帧调试输出
- 移除 UILayoutSystem 中的布局调试日志
- 清理所有 __UI_RENDER_DEBUG__ 条件日志
* refactor(ui): 优化渲染批处理和输入框组件
渲染系统:
- 修复 RenderBatcher 保持渲染顺序
- 优化 Rust SpriteBatch 避免合并非连续精灵
- 增强 EngineRenderSystem 纹理就绪检测
输入框组件:
- 增强 UIInputFieldComponent 功能
- 改进 UIInputSystem 输入处理
- 新增 TextMeasureService 文本测量服务
* fix(ui): 修复九宫格首帧渲染和InputField输入问题
- 修复九宫格首帧 size=0x0 问题:
- Viewport.tsx: 预览模式读取图片尺寸存储到 importSettings
- AssetDatabase: ISpriteSettings 添加 width/height 字段
- AssetMetadataService: getTextureSpriteInfo 使用元数据尺寸作为后备
- UIRectRenderSystem: 当 atlasEntry 不存在时使用 spriteInfo 尺寸
- WebBuildPipeline: 构建时包含 importSettings
- AssetManager: 从 catalog 初始化时复制 importSettings
- AssetTypes: IAssetCatalogEntry 添加 importSettings 字段
- 修复 InputField 无法输入问题:
- UIRuntimeModule: manifest 添加 pluginExport: 'UIPlugin'
- 确保预览模式正确加载 UI 插件并绑定 UIInputSystem
- 添加调试日志用于排查纹理加载问题
* fix(sprite): 修复类型导出错误
MaterialPropertyOverride 和 MaterialOverrides 应从 @esengine/material-system 导出
* fix(ui-editor): 补充 AnchorPreset 拉伸预设的映射
添加 StretchTop, StretchMiddle, StretchBottom, StretchLeft, StretchCenter, StretchRight 的位置和锚点值映射
2025-12-19 15:33:36 +08:00
|
|
|
|
import type {
|
|
|
|
|
|
IMaterialOverridable,
|
|
|
|
|
|
MaterialPropertyOverride,
|
|
|
|
|
|
MaterialOverrides
|
|
|
|
|
|
} from '@esengine/material-system';
|
2025-11-23 14:49:37 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 精灵组件 - 管理2D图像渲染
|
|
|
|
|
|
* Sprite component - manages 2D image rendering
|
2025-12-13 19:44:08 +08:00
|
|
|
|
*
|
|
|
|
|
|
* 需要 TransformComponent 才能被 EngineRenderSystem 处理
|
|
|
|
|
|
* Requires TransformComponent to be processed by EngineRenderSystem
|
2025-11-23 14:49:37 +08:00
|
|
|
|
*/
|
2025-12-13 19:44:08 +08:00
|
|
|
|
@ECSComponent('Sprite', { requires: ['Transform'] })
|
|
|
|
|
|
@Serializable({ version: 5, typeId: 'Sprite' })
|
refactor(ui): UI 系统架构重构 (#309)
* feat(ui): 动态图集系统与渲染调试增强
## 核心功能
### 动态图集系统 (Dynamic Atlas)
- 新增 DynamicAtlasManager:运行时纹理打包,支持 MaxRects 算法
- 新增 DynamicAtlasService:自动纹理加载与图集管理
- 新增 BinPacker:高效矩形打包算法
- 支持动态/固定两种扩展策略
- 自动 UV 重映射,实现 UI 元素合批渲染
### Frame Debugger 增强
- 新增合批分析面板,显示批次中断原因
- 新增 UI 元素层级信息(depth, worldOrderInLayer)
- 新增实体高亮功能,点击可在场景中定位
- 新增动态图集可视化面板
- 改进渲染原语详情展示
### 闪光效果 (Shiny Effect)
- 新增 UIShinyEffectComponent:UI 闪光参数配置
- 新增 UIShinyEffectSystem:材质覆盖驱动的闪光动画
- 新增 ShinyEffectComponent/System(Sprite 版本)
## 引擎层改进
### Rust 纹理管理扩展
- create_blank_texture:创建空白 GPU 纹理
- update_texture_region:局部纹理更新
- 支持动态图集的 GPU 端操作
### 材质系统
- 新增 effects/ 目录:ShinyEffect 等效果实现
- 新增 interfaces/ 目录:IMaterial 等接口定义
- 新增 mixins/ 目录:可组合的材质功能
### EngineBridge 扩展
- 新增 createBlankTexture/updateTextureRegion 方法
- 改进纹理加载回调机制
## UI 渲染改进
- UIRenderCollector:支持合批调试信息
- 稳定排序:addIndex 保证渲染顺序一致性
- 九宫格渲染优化
- 材质覆盖支持
## 其他改进
- 国际化:新增 Frame Debugger 相关翻译
- 编辑器:新增渲染调试入口
- 文档:新增架构设计文档目录
* refactor(ui): 引入新基础组件架构与渲染工具函数
Phase 1 重构 - 组件职责分离与代码复用:
新增基础组件层:
- UIGraphicComponent: 所有可视 UI 元素的基类(颜色、透明度、raycast)
- UIImageComponent: 纹理显示组件(支持简单、切片、平铺、填充模式)
- UISelectableComponent: 可交互元素的基类(状态管理、颜色过渡)
新增渲染工具:
- UIRenderUtils: 提取共享的坐标计算、边框渲染、阴影渲染等工具函数
- getUIRenderTransform: 统一的变换数据提取
- renderBorder/renderShadow: 复用的边框和阴影渲染逻辑
新增渲染系统:
- UIGraphicRenderSystem: 处理新基础组件的统一渲染器
重构现有系统:
- UIRectRenderSystem: 使用新工具函数,移除重复代码
- UIButtonRenderSystem: 使用新工具函数,移除重复代码
这些改动为后续统一渲染系统奠定基础。
* refactor(ui): UIProgressBarRenderSystem 使用渲染工具函数
- 使用 getUIRenderTransform 替代手动变换计算
- 使用 renderBorder 工具函数替代重复的边框渲染
- 使用 lerpColor 工具函数替代重复的颜色插值
- 简化方法签名,使用 UIRenderTransform 类型
- 移除约 135 行重复代码
* refactor(ui): Slider 和 ScrollView 渲染系统使用工具函数
- UISliderRenderSystem: 使用 getUIRenderTransform,简化方法签名
- UIScrollViewRenderSystem: 使用 getUIRenderTransform,简化方法签名
- 统一使用 UIRenderTransform 类型减少参数传递
- 消除重复的变换计算代码
* refactor(ui): 使用 UIWidgetMarker 消除硬编码组件依赖
- 新增 UIWidgetMarker 标记组件
- UIRectRenderSystem 改为检查标记而非硬编码4种组件类型
- 各 Widget 渲染系统自动添加标记组件
- 减少模块间耦合,提高可扩展性
* feat(ui): 实现 Canvas 隔离机制
- 新增 UICanvasComponent 定义 Canvas 渲染组
- UITransformComponent 添加 Canvas 相关字段:canvasEntityId, worldSortingLayer, pixelPerfect
- UILayoutSystem 传播 Canvas 设置给子元素
- UIRenderUtils 使用 Canvas 继承的排序层
- 支持嵌套 Canvas 和不同渲染模式
* refactor(ui): 统一纹理管理工具函数
Phase 4: 纹理管理统一
新增:
- UITextureUtils.ts: 统一的纹理描述符接口和验证函数
- UITextureDescriptor: 支持 GUID/textureId/path 多种纹理源
- isValidTextureGuid: GUID 验证
- getTextureKey: 获取用于合批的纹理键
- normalizeTextureDescriptor: 规范化各种输入格式
- utils/index.ts: 工具函数导出
修改:
- UIGraphicRenderSystem: 使用新的纹理工具函数
- index.ts: 导出纹理工具类型和函数
* refactor(ui): 实现统一的脏标记机制
Phase 5: Dirty 标记机制
新增:
- UIDirtyFlags.ts: 位标记枚举和追踪工具
- UIDirtyFlags: Visual/Layout/Transform/Material/Text 标记
- IDirtyTrackable: 脏追踪接口
- DirtyTracker: 辅助工具类
- 帧级别脏状态追踪 (markFrameDirty, isFrameDirty)
修改:
- UIGraphicComponent: 实现 IDirtyTrackable
- 属性 setter 自动设置脏标记
- 保留 setDirty/clearDirty 向后兼容
- UIImageComponent: 所有属性支持脏追踪
- textureGuid/imageType/fillAmount 等变化自动标记
- UIGraphicRenderSystem: 使用 clearDirtyFlags()
导出:
- UIDirtyFlags, IDirtyTrackable, DirtyTracker
- markFrameDirty, isFrameDirty, clearFrameDirty
* refactor(ui): 移除过时的 dirty flag API
移除 UIGraphicComponent 中的兼容性 API:
- 移除 _isDirty getter/setter
- 移除 setDirty() 方法
- 移除 clearDirty() 方法
现在统一使用新的 dirty flag 系统:
- isDirty() / hasDirtyFlag(flags)
- markDirty(flags) / clearDirtyFlags()
* fix(ui): 修复两个 TODO 功能
1. 滑块手柄命中测试 (UIInputSystem)
- UISliderComponent 添加 getHandleBounds() 计算手柄边界
- UISliderComponent 添加 isPointInHandle() 精确命中测试
- UIInputSystem.handleSlider() 使用精确测试更新悬停状态
2. 径向填充渲染 (UIGraphicRenderSystem)
- 实现 renderRadialFill() 方法
- 支持 radial90/radial180/radial360 三种模式
- 支持 fillOrigin (top/right/bottom/left) 和 fillClockwise
- 使用多段矩形近似饼形填充效果
* feat(ui): 完善 UI 系统架构和九宫格渲染
* fix(ui): 修复文本渲染层级问题并清理调试代码
- 修复纹理就绪后调用 invalidateUIRenderCaches() 导致的无限循环
- 移除 UITextRenderSystem、UIButtonRenderSystem、UIRectRenderSystem 中的首帧调试输出
- 移除 UILayoutSystem 中的布局调试日志
- 清理所有 __UI_RENDER_DEBUG__ 条件日志
* refactor(ui): 优化渲染批处理和输入框组件
渲染系统:
- 修复 RenderBatcher 保持渲染顺序
- 优化 Rust SpriteBatch 避免合并非连续精灵
- 增强 EngineRenderSystem 纹理就绪检测
输入框组件:
- 增强 UIInputFieldComponent 功能
- 改进 UIInputSystem 输入处理
- 新增 TextMeasureService 文本测量服务
* fix(ui): 修复九宫格首帧渲染和InputField输入问题
- 修复九宫格首帧 size=0x0 问题:
- Viewport.tsx: 预览模式读取图片尺寸存储到 importSettings
- AssetDatabase: ISpriteSettings 添加 width/height 字段
- AssetMetadataService: getTextureSpriteInfo 使用元数据尺寸作为后备
- UIRectRenderSystem: 当 atlasEntry 不存在时使用 spriteInfo 尺寸
- WebBuildPipeline: 构建时包含 importSettings
- AssetManager: 从 catalog 初始化时复制 importSettings
- AssetTypes: IAssetCatalogEntry 添加 importSettings 字段
- 修复 InputField 无法输入问题:
- UIRuntimeModule: manifest 添加 pluginExport: 'UIPlugin'
- 确保预览模式正确加载 UI 插件并绑定 UIInputSystem
- 添加调试日志用于排查纹理加载问题
* fix(sprite): 修复类型导出错误
MaterialPropertyOverride 和 MaterialOverrides 应从 @esengine/material-system 导出
* fix(ui-editor): 补充 AnchorPreset 拉伸预设的映射
添加 StretchTop, StretchMiddle, StretchBottom, StretchLeft, StretchCenter, StretchRight 的位置和锚点值映射
2025-12-19 15:33:36 +08:00
|
|
|
|
export class SpriteComponent extends Component implements ISortable, IMaterialOverridable {
|
2025-11-23 14:49:37 +08:00
|
|
|
|
/**
|
2025-12-06 14:08:48 +08:00
|
|
|
|
* 纹理资产 GUID
|
|
|
|
|
|
* Texture asset GUID
|
|
|
|
|
|
*
|
|
|
|
|
|
* Stores the unique identifier of the texture asset.
|
|
|
|
|
|
* The actual file path is resolved at runtime via AssetDatabase.
|
|
|
|
|
|
* 存储纹理资产的唯一标识符。
|
|
|
|
|
|
* 实际文件路径在运行时通过 AssetDatabase 解析。
|
2025-11-23 14:49:37 +08:00
|
|
|
|
*/
|
|
|
|
|
|
@Serialize()
|
2025-12-06 14:08:48 +08:00
|
|
|
|
@Property({ type: 'asset', label: 'Texture', assetType: 'texture' })
|
|
|
|
|
|
public textureGuid: string = '';
|
2025-11-23 14:49:37 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 纹理ID(运行时使用)
|
|
|
|
|
|
* Texture ID for runtime rendering
|
|
|
|
|
|
*/
|
|
|
|
|
|
public textureId: number = 0;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 资产引用(运行时,不序列化)
|
|
|
|
|
|
* Asset reference (runtime only, not serialized)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private _assetReference?: AssetReference<HTMLImageElement>;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 精灵宽度(像素)
|
|
|
|
|
|
* Sprite width in pixels
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Serialize()
|
|
|
|
|
|
@Property({
|
|
|
|
|
|
type: 'number',
|
|
|
|
|
|
label: 'Width',
|
|
|
|
|
|
min: 0,
|
|
|
|
|
|
actions: [{
|
|
|
|
|
|
id: 'nativeSize',
|
|
|
|
|
|
label: 'Native',
|
|
|
|
|
|
tooltip: 'Set to texture native size',
|
|
|
|
|
|
icon: 'Maximize2'
|
|
|
|
|
|
}]
|
|
|
|
|
|
})
|
|
|
|
|
|
public width: number = 64;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 精灵高度(像素)
|
|
|
|
|
|
* Sprite height in pixels
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Serialize()
|
|
|
|
|
|
@Property({
|
|
|
|
|
|
type: 'number',
|
|
|
|
|
|
label: 'Height',
|
|
|
|
|
|
min: 0,
|
|
|
|
|
|
actions: [{
|
|
|
|
|
|
id: 'nativeSize',
|
|
|
|
|
|
label: 'Native',
|
|
|
|
|
|
tooltip: 'Set to texture native size',
|
|
|
|
|
|
icon: 'Maximize2'
|
|
|
|
|
|
}]
|
|
|
|
|
|
})
|
|
|
|
|
|
public height: number = 64;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* UV坐标 [u0, v0, u1, v1]
|
|
|
|
|
|
* UV coordinates [u0, v0, u1, v1]
|
|
|
|
|
|
* 默认为完整纹理 [0, 0, 1, 1]
|
|
|
|
|
|
* Default is full texture [0, 0, 1, 1]
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Serialize()
|
|
|
|
|
|
public uv: [number, number, number, number] = [0, 0, 1, 1];
|
|
|
|
|
|
|
|
|
|
|
|
/** 颜色(十六进制)| Color (hex string) */
|
|
|
|
|
|
@Serialize()
|
|
|
|
|
|
@Property({ type: 'color', label: 'Color' })
|
|
|
|
|
|
public color: string = '#ffffff';
|
|
|
|
|
|
|
|
|
|
|
|
/** 透明度 (0-1) | Alpha (0-1) */
|
|
|
|
|
|
@Serialize()
|
|
|
|
|
|
@Property({ type: 'number', label: 'Alpha', min: 0, max: 1, step: 0.01 })
|
|
|
|
|
|
public alpha: number = 1;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 原点X (0-1, 0.5为中心)
|
|
|
|
|
|
* Origin point X (0-1, where 0.5 is center)
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Serialize()
|
|
|
|
|
|
@Property({ type: 'number', label: 'Origin X', min: 0, max: 1, step: 0.01 })
|
|
|
|
|
|
public originX: number = 0.5;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 原点Y (0-1, 0.5为中心)
|
|
|
|
|
|
* Origin point Y (0-1, where 0.5 is center)
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Serialize()
|
|
|
|
|
|
@Property({ type: 'number', label: 'Origin Y', min: 0, max: 1, step: 0.01 })
|
|
|
|
|
|
public originY: number = 0.5;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 精灵是否可见
|
|
|
|
|
|
* Whether sprite is visible
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Serialize()
|
|
|
|
|
|
@Property({ type: 'boolean', label: 'Visible' })
|
|
|
|
|
|
public visible: boolean = true;
|
|
|
|
|
|
|
|
|
|
|
|
/** 是否水平翻转 | Flip sprite horizontally */
|
|
|
|
|
|
@Serialize()
|
|
|
|
|
|
@Property({ type: 'boolean', label: 'Flip X' })
|
|
|
|
|
|
public flipX: boolean = false;
|
|
|
|
|
|
|
|
|
|
|
|
/** 是否垂直翻转 | Flip sprite vertically */
|
|
|
|
|
|
@Serialize()
|
|
|
|
|
|
@Property({ type: 'boolean', label: 'Flip Y' })
|
|
|
|
|
|
public flipY: boolean = false;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-12-13 19:44:08 +08:00
|
|
|
|
* 排序层
|
|
|
|
|
|
* Sorting layer
|
|
|
|
|
|
*
|
|
|
|
|
|
* 决定渲染的大类顺序,如 Background, Default, UI, Overlay 等。
|
|
|
|
|
|
* Determines the major render order category.
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Serialize()
|
|
|
|
|
|
@Property({
|
|
|
|
|
|
type: 'enum',
|
|
|
|
|
|
label: 'Sorting Layer',
|
|
|
|
|
|
options: ['Background', 'Default', 'Foreground', 'WorldOverlay', 'UI', 'ScreenOverlay', 'Modal']
|
|
|
|
|
|
})
|
|
|
|
|
|
public sortingLayer: string = SortingLayers.Default;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 层内顺序(越高越在上面)
|
|
|
|
|
|
* Order within layer (higher = rendered on top)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 同一排序层内的细分顺序。
|
|
|
|
|
|
* Fine-grained order within the same sorting layer.
|
2025-11-23 14:49:37 +08:00
|
|
|
|
*/
|
|
|
|
|
|
@Serialize()
|
2025-12-13 19:44:08 +08:00
|
|
|
|
@Property({ type: 'integer', label: 'Order in Layer' })
|
|
|
|
|
|
public orderInLayer: number = 0;
|
2025-11-23 14:49:37 +08:00
|
|
|
|
|
2025-12-03 22:15:22 +08:00
|
|
|
|
/**
|
2025-12-06 14:08:48 +08:00
|
|
|
|
* 材质资产 GUID(共享材质)
|
|
|
|
|
|
* Material asset GUID (shared material)
|
2025-12-03 22:15:22 +08:00
|
|
|
|
*
|
|
|
|
|
|
* Multiple sprites can reference the same material file.
|
|
|
|
|
|
* 多个精灵可以引用同一个材质文件。
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Serialize()
|
|
|
|
|
|
@Property({ type: 'asset', label: 'Material', extensions: ['.mat'] })
|
2025-12-06 14:08:48 +08:00
|
|
|
|
public materialGuid: string = '';
|
2025-12-03 22:15:22 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 材质属性覆盖(实例级别)
|
|
|
|
|
|
* Material property overrides (instance level)
|
|
|
|
|
|
*
|
|
|
|
|
|
* Override specific uniform parameters without creating a new material.
|
|
|
|
|
|
* 覆盖特定的 uniform 参数,无需创建新材质。
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Serialize()
|
|
|
|
|
|
public materialOverrides: MaterialOverrides = {};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 是否使用独立材质实例
|
|
|
|
|
|
* Whether to use an independent material instance
|
|
|
|
|
|
*
|
|
|
|
|
|
* When true, a copy of the shared material is created for this sprite.
|
|
|
|
|
|
* Changes to this material won't affect other sprites using the same source.
|
|
|
|
|
|
* 当为 true 时,会为此精灵创建共享材质的副本。
|
|
|
|
|
|
* 对此材质的更改不会影响使用相同源的其他精灵。
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Serialize()
|
|
|
|
|
|
@Property({ type: 'boolean', label: 'Use Instance Material' })
|
|
|
|
|
|
public useInstanceMaterial: boolean = false;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 运行时材质ID(缓存)
|
|
|
|
|
|
* Runtime material ID (cached)
|
|
|
|
|
|
*
|
|
|
|
|
|
* Cached material ID for rendering. Updated when material path changes.
|
|
|
|
|
|
* 用于渲染的缓存材质ID。当材质路径更改时更新。
|
|
|
|
|
|
*/
|
|
|
|
|
|
private _materialId: number = 0;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 独立材质实例(如果 useInstanceMaterial 为 true)
|
|
|
|
|
|
* Independent material instance (if useInstanceMaterial is true)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private _instanceMaterial: unknown = null;
|
|
|
|
|
|
|
2025-11-23 14:49:37 +08:00
|
|
|
|
/** 锚点X (0-1) - 别名为originX | Anchor X (0-1) - alias for originX */
|
|
|
|
|
|
get anchorX(): number {
|
|
|
|
|
|
return this.originX;
|
|
|
|
|
|
}
|
|
|
|
|
|
set anchorX(value: number) {
|
|
|
|
|
|
this.originX = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 锚点Y (0-1) - 别名为originY | Anchor Y (0-1) - alias for originY */
|
|
|
|
|
|
get anchorY(): number {
|
|
|
|
|
|
return this.originY;
|
|
|
|
|
|
}
|
|
|
|
|
|
set anchorY(value: number) {
|
|
|
|
|
|
this.originY = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-06 14:08:48 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @param textureGuidOrPath - Texture GUID or path (for backward compatibility)
|
|
|
|
|
|
*/
|
|
|
|
|
|
constructor(textureGuidOrPath: string = '') {
|
2025-11-23 14:49:37 +08:00
|
|
|
|
super();
|
2025-12-06 14:08:48 +08:00
|
|
|
|
// Support both GUID and path for backward compatibility
|
|
|
|
|
|
this.textureGuid = textureGuidOrPath;
|
2025-11-23 14:49:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 从精灵图集区域设置UV
|
|
|
|
|
|
* Set UV from a sprite atlas region
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param x - 区域X(像素)| Region X in pixels
|
|
|
|
|
|
* @param y - 区域Y(像素)| Region Y in pixels
|
|
|
|
|
|
* @param w - 区域宽度(像素)| Region width in pixels
|
|
|
|
|
|
* @param h - 区域高度(像素)| Region height in pixels
|
|
|
|
|
|
* @param atlasWidth - 图集总宽度 | Atlas total width
|
|
|
|
|
|
* @param atlasHeight - 图集总高度 | Atlas total height
|
|
|
|
|
|
*/
|
|
|
|
|
|
setAtlasRegion(
|
|
|
|
|
|
x: number,
|
|
|
|
|
|
y: number,
|
|
|
|
|
|
w: number,
|
|
|
|
|
|
h: number,
|
|
|
|
|
|
atlasWidth: number,
|
|
|
|
|
|
atlasHeight: number
|
|
|
|
|
|
): void {
|
|
|
|
|
|
this.uv = [
|
|
|
|
|
|
x / atlasWidth,
|
|
|
|
|
|
y / atlasHeight,
|
|
|
|
|
|
(x + w) / atlasWidth,
|
|
|
|
|
|
(y + h) / atlasHeight
|
|
|
|
|
|
];
|
|
|
|
|
|
this.width = w;
|
|
|
|
|
|
this.height = h;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置资产引用
|
|
|
|
|
|
* Set asset reference
|
|
|
|
|
|
*/
|
|
|
|
|
|
setAssetReference(reference: AssetReference<HTMLImageElement>): void {
|
|
|
|
|
|
// 释放旧引用 / Release old reference
|
|
|
|
|
|
if (this._assetReference) {
|
|
|
|
|
|
this._assetReference.release();
|
|
|
|
|
|
}
|
|
|
|
|
|
this._assetReference = reference;
|
|
|
|
|
|
if (reference) {
|
2025-12-06 14:08:48 +08:00
|
|
|
|
this.textureGuid = reference.guid;
|
2025-11-23 14:49:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取资产引用
|
|
|
|
|
|
* Get asset reference
|
|
|
|
|
|
*/
|
|
|
|
|
|
getAssetReference(): AssetReference<HTMLImageElement> | undefined {
|
|
|
|
|
|
return this._assetReference;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 异步加载纹理
|
|
|
|
|
|
* Load texture asynchronously
|
|
|
|
|
|
*/
|
|
|
|
|
|
async loadTextureAsync(): Promise<void> {
|
|
|
|
|
|
if (this._assetReference) {
|
|
|
|
|
|
try {
|
2025-12-08 21:10:57 +08:00
|
|
|
|
const result = await this._assetReference.loadAsync();
|
|
|
|
|
|
// 检查返回值是否包含 textureId 属性(ITextureAsset 类型)
|
|
|
|
|
|
// Check if result has textureId property (ITextureAsset type)
|
|
|
|
|
|
if (result && typeof result === 'object' && 'textureId' in result) {
|
|
|
|
|
|
this.textureId = (result as { textureId: number }).textureId;
|
2025-11-23 14:49:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to load texture:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-12-06 14:08:48 +08:00
|
|
|
|
* 获取纹理 GUID
|
|
|
|
|
|
* Get texture GUID
|
2025-11-23 14:49:37 +08:00
|
|
|
|
*/
|
|
|
|
|
|
getTextureSource(): string {
|
2025-12-06 14:08:48 +08:00
|
|
|
|
return this.textureGuid;
|
2025-11-23 14:49:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-03 22:15:22 +08:00
|
|
|
|
// ============= Material Override Methods =============
|
|
|
|
|
|
// ============= 材质覆盖方法 =============
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取材质ID
|
|
|
|
|
|
* Get material ID
|
|
|
|
|
|
*
|
|
|
|
|
|
* # Returns | 返回
|
|
|
|
|
|
* The cached material ID for rendering.
|
|
|
|
|
|
* 用于渲染的缓存材质ID。
|
|
|
|
|
|
*/
|
|
|
|
|
|
getMaterialId(): number {
|
|
|
|
|
|
return this._materialId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置材质ID
|
|
|
|
|
|
* Set material ID
|
|
|
|
|
|
*
|
|
|
|
|
|
* # Arguments | 参数
|
|
|
|
|
|
* * `id` - Material ID from MaterialManager. | 来自 MaterialManager 的材质ID。
|
|
|
|
|
|
*/
|
|
|
|
|
|
setMaterialId(id: number): void {
|
|
|
|
|
|
this._materialId = id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置浮点覆盖值
|
|
|
|
|
|
* Set float override value
|
|
|
|
|
|
*
|
|
|
|
|
|
* # Arguments | 参数
|
|
|
|
|
|
* * `name` - Uniform name. | Uniform 名称。
|
|
|
|
|
|
* * `value` - Float value. | 浮点值。
|
|
|
|
|
|
*/
|
|
|
|
|
|
setOverrideFloat(name: string, value: number): this {
|
|
|
|
|
|
this.materialOverrides[name] = { type: 'float', value };
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置 vec2 覆盖值
|
|
|
|
|
|
* Set vec2 override value
|
|
|
|
|
|
*
|
|
|
|
|
|
* # Arguments | 参数
|
|
|
|
|
|
* * `name` - Uniform name. | Uniform 名称。
|
|
|
|
|
|
* * `x` - X component. | X 分量。
|
|
|
|
|
|
* * `y` - Y component. | Y 分量。
|
|
|
|
|
|
*/
|
|
|
|
|
|
setOverrideVec2(name: string, x: number, y: number): this {
|
|
|
|
|
|
this.materialOverrides[name] = { type: 'vec2', value: [x, y] };
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置 vec3 覆盖值
|
|
|
|
|
|
* Set vec3 override value
|
|
|
|
|
|
*
|
|
|
|
|
|
* # Arguments | 参数
|
|
|
|
|
|
* * `name` - Uniform name. | Uniform 名称。
|
|
|
|
|
|
* * `x` - X component. | X 分量。
|
|
|
|
|
|
* * `y` - Y component. | Y 分量。
|
|
|
|
|
|
* * `z` - Z component. | Z 分量。
|
|
|
|
|
|
*/
|
|
|
|
|
|
setOverrideVec3(name: string, x: number, y: number, z: number): this {
|
|
|
|
|
|
this.materialOverrides[name] = { type: 'vec3', value: [x, y, z] };
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置 vec4 覆盖值
|
|
|
|
|
|
* Set vec4 override value
|
|
|
|
|
|
*
|
|
|
|
|
|
* # Arguments | 参数
|
|
|
|
|
|
* * `name` - Uniform name. | Uniform 名称。
|
|
|
|
|
|
* * `x` - X component. | X 分量。
|
|
|
|
|
|
* * `y` - Y component. | Y 分量。
|
|
|
|
|
|
* * `z` - Z component. | Z 分量。
|
|
|
|
|
|
* * `w` - W component. | W 分量。
|
|
|
|
|
|
*/
|
|
|
|
|
|
setOverrideVec4(name: string, x: number, y: number, z: number, w: number): this {
|
|
|
|
|
|
this.materialOverrides[name] = { type: 'vec4', value: [x, y, z, w] };
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置颜色覆盖值
|
|
|
|
|
|
* Set color override value
|
|
|
|
|
|
*
|
|
|
|
|
|
* # Arguments | 参数
|
|
|
|
|
|
* * `name` - Uniform name. | Uniform 名称。
|
|
|
|
|
|
* * `r` - Red component (0-1). | 红色分量 (0-1)。
|
|
|
|
|
|
* * `g` - Green component (0-1). | 绿色分量 (0-1)。
|
|
|
|
|
|
* * `b` - Blue component (0-1). | 蓝色分量 (0-1)。
|
|
|
|
|
|
* * `a` - Alpha component (0-1). | 透明度分量 (0-1)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
setOverrideColor(name: string, r: number, g: number, b: number, a: number = 1.0): this {
|
|
|
|
|
|
this.materialOverrides[name] = { type: 'color', value: [r, g, b, a] };
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置整数覆盖值
|
|
|
|
|
|
* Set integer override value
|
|
|
|
|
|
*
|
|
|
|
|
|
* # Arguments | 参数
|
|
|
|
|
|
* * `name` - Uniform name. | Uniform 名称。
|
|
|
|
|
|
* * `value` - Integer value. | 整数值。
|
|
|
|
|
|
*/
|
|
|
|
|
|
setOverrideInt(name: string, value: number): this {
|
|
|
|
|
|
this.materialOverrides[name] = { type: 'int', value: Math.floor(value) };
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取覆盖值
|
|
|
|
|
|
* Get override value
|
|
|
|
|
|
*
|
|
|
|
|
|
* # Arguments | 参数
|
|
|
|
|
|
* * `name` - Uniform name. | Uniform 名称。
|
|
|
|
|
|
*
|
|
|
|
|
|
* # Returns | 返回
|
|
|
|
|
|
* Override value or undefined if not set.
|
|
|
|
|
|
* 覆盖值,如果未设置则返回 undefined。
|
|
|
|
|
|
*/
|
|
|
|
|
|
getOverride(name: string): MaterialPropertyOverride | undefined {
|
|
|
|
|
|
return this.materialOverrides[name];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 移除覆盖值
|
|
|
|
|
|
* Remove override value
|
|
|
|
|
|
*
|
|
|
|
|
|
* # Arguments | 参数
|
|
|
|
|
|
* * `name` - Uniform name to remove. | 要移除的 Uniform 名称。
|
|
|
|
|
|
*/
|
|
|
|
|
|
removeOverride(name: string): this {
|
|
|
|
|
|
delete this.materialOverrides[name];
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 清除所有覆盖值
|
|
|
|
|
|
* Clear all override values
|
|
|
|
|
|
*/
|
|
|
|
|
|
clearOverrides(): this {
|
|
|
|
|
|
this.materialOverrides = {};
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查是否有覆盖值
|
|
|
|
|
|
* Check if there are any overrides
|
|
|
|
|
|
*
|
|
|
|
|
|
* # Returns | 返回
|
|
|
|
|
|
* True if there are any material overrides.
|
|
|
|
|
|
* 如果有任何材质覆盖则返回 true。
|
|
|
|
|
|
*/
|
|
|
|
|
|
hasOverrides(): boolean {
|
|
|
|
|
|
return Object.keys(this.materialOverrides).length > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-23 14:49:37 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 组件销毁时调用
|
|
|
|
|
|
* Called when component is destroyed
|
|
|
|
|
|
*/
|
|
|
|
|
|
onDestroy(): void {
|
|
|
|
|
|
// 释放资产引用 / Release asset reference
|
|
|
|
|
|
if (this._assetReference) {
|
|
|
|
|
|
this._assetReference.release();
|
|
|
|
|
|
this._assetReference = undefined;
|
|
|
|
|
|
}
|
2025-12-03 22:15:22 +08:00
|
|
|
|
// 清理材质覆盖 / Clear material overrides
|
|
|
|
|
|
this.materialOverrides = {};
|
|
|
|
|
|
this._instanceMaterial = null;
|
2025-11-23 14:49:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|