Files
esengine/packages/tilemap/src/TilemapComponent.ts

1258 lines
41 KiB
TypeScript
Raw Normal View History

import { Component, ECSComponent, Serializable, Serialize, Property } from '@esengine/ecs-framework';
import type { IResourceComponent, ResourceReference } from '@esengine/asset-system';
import { UVHelper } from '@esengine/asset-system';
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 导入
2025-12-13 19:44:08 +08:00
import { SortingLayers, type ISortable } from '@esengine/engine-core';
/**
* Resize anchor point for tilemap expansion
*
*/
export type ResizeAnchor =
| 'top-left' | 'top-center' | 'top-right'
| 'middle-left' | 'center' | 'middle-right'
| 'bottom-left' | 'bottom-center' | 'bottom-right';
feat: 添加跨平台运行时、资产系统和UI适配功能 (#256) * feat(platform-common): 添加WASM加载器和环境检测API * feat(rapier2d): 新增Rapier2D WASM绑定包 * feat(physics-rapier2d): 添加跨平台WASM加载器 * feat(asset-system): 添加运行时资产目录和bundle格式 * feat(asset-system-editor): 新增编辑器资产管理包 * feat(editor-core): 添加构建系统和模块管理 * feat(editor-app): 重构浏览器预览使用import maps * feat(platform-web): 添加BrowserRuntime和资产读取 * feat(engine): 添加材质系统和着色器管理 * feat(material): 新增材质系统和着色器编辑器 * feat(tilemap): 增强tilemap编辑器和动画系统 * feat(modules): 添加module.json配置 * feat(core): 添加module.json和类型定义更新 * chore: 更新依赖和构建配置 * refactor(plugins): 更新插件模板使用ModuleManifest * chore: 添加第三方依赖库 * chore: 移除BehaviourTree-ai和ecs-astar子模块 * docs: 更新README和文档主题样式 * fix: 修复Rust文档测试和添加rapier2d WASM绑定 * fix(tilemap-editor): 修复画布高DPI屏幕分辨率适配问题 * feat(ui): 添加UI屏幕适配系统(CanvasScaler/SafeArea) * fix(ecs-engine-bindgen): 添加缺失的ecs-framework-math依赖 * fix: 添加缺失的包依赖修复CI构建 * fix: 修复CodeQL检测到的代码问题 * fix: 修复构建错误和缺失依赖 * fix: 修复类型检查错误 * fix(material-system): 修复tsconfig配置支持TypeScript项目引用 * fix(editor-core): 修复Rollup构建配置添加tauri external * fix: 修复CodeQL检测到的代码问题 * fix: 修复CodeQL检测到的代码问题
2025-12-03 22:15:22 +08:00
/**
* Animation frame definition
*
*/
export interface ITileAnimationFrame {
/** Tile ID to display for this frame (local ID within tileset) | 此帧显示的瓦片ID图块集内的本地ID */
tileId: number;
/** Frame duration in milliseconds | 帧持续时间(毫秒) */
duration: number;
}
/**
* Tile animation definition
*
*/
export interface ITileAnimation {
/** Animation frame sequence | 动画帧序列 */
frames: ITileAnimationFrame[];
}
/**
* Individual tile metadata
*
*/
export interface ITileMetadata {
/** Tile ID (local ID within tileset) | 瓦片ID图块集内的本地ID */
id: number;
/** Tile class/type | 瓦片类型 */
type?: string;
/** Custom properties | 自定义属性 */
properties?: Record<string, unknown>;
/** Tile animation (if any) | 瓦片动画(如果有) */
animation?: ITileAnimation;
}
/**
* Tileset data interface
*
*/
export interface ITilesetData {
/** Tileset name | 图块集名称 */
name: string;
/** Data format version | 数据格式版本 */
version: number;
/** Image file path | 图片文件路径 */
image: string;
/** Image width in pixels | 图片宽度(像素) */
imageWidth: number;
/** Image height in pixels | 图片高度(像素) */
imageHeight: number;
/** Single tile width in pixels | 单个图块宽度(像素) */
tileWidth: number;
/** Single tile height in pixels | 单个图块高度(像素) */
tileHeight: number;
/** Total number of tiles | 图块总数 */
tileCount: number;
/** Number of tile columns | 图块列数 */
columns: number;
/** Number of tile rows | 图块行数 */
rows: number;
/** Margin around tileset in pixels | 图块集边距(像素) */
margin?: number;
/** Spacing between tiles in pixels | 图块间距(像素) */
spacing?: number;
/** Individual tile metadata | 单个图块元数据 */
feat: 添加跨平台运行时、资产系统和UI适配功能 (#256) * feat(platform-common): 添加WASM加载器和环境检测API * feat(rapier2d): 新增Rapier2D WASM绑定包 * feat(physics-rapier2d): 添加跨平台WASM加载器 * feat(asset-system): 添加运行时资产目录和bundle格式 * feat(asset-system-editor): 新增编辑器资产管理包 * feat(editor-core): 添加构建系统和模块管理 * feat(editor-app): 重构浏览器预览使用import maps * feat(platform-web): 添加BrowserRuntime和资产读取 * feat(engine): 添加材质系统和着色器管理 * feat(material): 新增材质系统和着色器编辑器 * feat(tilemap): 增强tilemap编辑器和动画系统 * feat(modules): 添加module.json配置 * feat(core): 添加module.json和类型定义更新 * chore: 更新依赖和构建配置 * refactor(plugins): 更新插件模板使用ModuleManifest * chore: 添加第三方依赖库 * chore: 移除BehaviourTree-ai和ecs-astar子模块 * docs: 更新README和文档主题样式 * fix: 修复Rust文档测试和添加rapier2d WASM绑定 * fix(tilemap-editor): 修复画布高DPI屏幕分辨率适配问题 * feat(ui): 添加UI屏幕适配系统(CanvasScaler/SafeArea) * fix(ecs-engine-bindgen): 添加缺失的ecs-framework-math依赖 * fix: 添加缺失的包依赖修复CI构建 * fix: 修复CodeQL检测到的代码问题 * fix: 修复构建错误和缺失依赖 * fix: 修复类型检查错误 * fix(material-system): 修复tsconfig配置支持TypeScript项目引用 * fix(editor-core): 修复Rollup构建配置添加tauri external * fix: 修复CodeQL检测到的代码问题 * fix: 修复CodeQL检测到的代码问题
2025-12-03 22:15:22 +08:00
tiles?: ITileMetadata[];
}
/**
* Layer data interface
*
*/
export interface ITilemapLayerData {
/** Unique layer identifier | 图层唯一标识符 */
id: string;
/** Layer display name | 图层显示名称 */
name: string;
/** Layer visibility | 图层可见性 */
visible: boolean;
/** Layer opacity (0-1) | 图层不透明度0-1 */
opacity: number;
/** Tile index data array (row-major order) | 图块索引数据数组(行优先顺序) */
data: number[];
/** Default tileset index for this layer | 此图层的默认图块集索引 */
tilesetIndex?: number;
/** Layer X offset in pixels | 图层X偏移像素 */
offsetX?: number;
/** Layer Y offset in pixels | 图层Y偏移像素 */
offsetY?: number;
feat: 添加跨平台运行时、资产系统和UI适配功能 (#256) * feat(platform-common): 添加WASM加载器和环境检测API * feat(rapier2d): 新增Rapier2D WASM绑定包 * feat(physics-rapier2d): 添加跨平台WASM加载器 * feat(asset-system): 添加运行时资产目录和bundle格式 * feat(asset-system-editor): 新增编辑器资产管理包 * feat(editor-core): 添加构建系统和模块管理 * feat(editor-app): 重构浏览器预览使用import maps * feat(platform-web): 添加BrowserRuntime和资产读取 * feat(engine): 添加材质系统和着色器管理 * feat(material): 新增材质系统和着色器编辑器 * feat(tilemap): 增强tilemap编辑器和动画系统 * feat(modules): 添加module.json配置 * feat(core): 添加module.json和类型定义更新 * chore: 更新依赖和构建配置 * refactor(plugins): 更新插件模板使用ModuleManifest * chore: 添加第三方依赖库 * chore: 移除BehaviourTree-ai和ecs-astar子模块 * docs: 更新README和文档主题样式 * fix: 修复Rust文档测试和添加rapier2d WASM绑定 * fix(tilemap-editor): 修复画布高DPI屏幕分辨率适配问题 * feat(ui): 添加UI屏幕适配系统(CanvasScaler/SafeArea) * fix(ecs-engine-bindgen): 添加缺失的ecs-framework-math依赖 * fix: 添加缺失的包依赖修复CI构建 * fix: 修复CodeQL检测到的代码问题 * fix: 修复构建错误和缺失依赖 * fix: 修复类型检查错误 * fix(material-system): 修复tsconfig配置支持TypeScript项目引用 * fix(editor-core): 修复Rollup构建配置添加tauri external * fix: 修复CodeQL检测到的代码问题 * fix: 修复CodeQL检测到的代码问题
2025-12-03 22:15:22 +08:00
/** Material asset path for this layer (.mat file) | 此图层的材质资源路径(.mat 文件) */
materialPath?: string;
/** Runtime material ID (set after loading) | 运行时材质ID加载后设置 */
materialId?: number;
/** Tint color in hex format | 着色颜色(十六进制格式) */
color?: string;
/** Hidden in game (visible only in editor) | 游戏中隐藏(仅在编辑器中可见) */
hiddenInGame?: boolean;
/** Custom layer properties | 自定义图层属性 */
properties?: Record<string, unknown>;
}
/**
* Tileset reference info
*
*/
export interface ITilesetRef {
/** Tileset image source path | 图块集图片源路径 */
source: string;
/** First global tile ID for this tileset | 此图块集的第一个全局图块ID */
firstGid: number;
/** Loaded tileset data | 已加载的图块集数据 */
data?: ITilesetData;
/** GPU texture ID for rendering | 用于渲染的GPU纹理ID */
textureId?: number;
}
/**
* Tilemap data interface
*
*/
export interface ITilemapData {
/** Tilemap name | 瓦片地图名称 */
name: string;
/** Data format version | 数据格式版本 */
version: number;
/** Map width in tiles | 地图宽度(图块数) */
width: number;
/** Map height in tiles | 地图高度(图块数) */
height: number;
/** Single tile width in pixels | 单个图块宽度(像素) */
tileWidth: number;
/** Single tile height in pixels | 单个图块高度(像素) */
tileHeight: number;
/** Array of tileset references | 图块集引用数组 */
tilesets: ITilesetRef[];
/** Array of layer data | 图层数据数组 */
layers: ITilemapLayerData[];
/** Collision data array | 碰撞数据数组 */
collisionData?: number[];
/** Custom tilemap properties | 自定义瓦片地图属性 */
properties?: Record<string, unknown>;
}
/**
* Tilemap Component - Manages tile-based 2D map rendering
* - 2D地图渲染
*/
@ECSComponent('Tilemap')
@Serializable({ version: 2, typeId: 'Tilemap' })
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 导入
2025-12-13 19:44:08 +08:00
export class TilemapComponent extends Component implements IResourceComponent, ISortable {
/** Tilemap asset GUID reference | 瓦片地图资源GUID引用 */
@Serialize()
@Property({ type: 'asset', label: 'Tilemap', extensions: ['.tilemap', '.tilemap.json'] })
public tilemapAssetGuid: string = '';
@Serialize()
private _width: number = 10;
@Serialize()
private _height: number = 10;
/** Map width in tiles | 地图宽度(图块数) */
@Property({ type: 'integer', label: 'Width (Tiles)', min: 1 })
public get width(): number {
return this._width;
}
public set width(value: number) {
if (value !== this._width && value > 0) {
this.resize(value, this._height);
}
}
/** Map height in tiles | 地图高度(图块数) */
@Property({ type: 'integer', label: 'Height (Tiles)', min: 1 })
public get height(): number {
return this._height;
}
public set height(value: number) {
if (value !== this._height && value > 0) {
this.resize(this._width, value);
}
}
/** Single tile width in pixels | 单个图块宽度(像素) */
@Serialize()
@Property({ type: 'integer', label: 'Tile Width', min: 1 })
public tileWidth: number = 32;
/** Single tile height in pixels | 单个图块高度(像素) */
@Serialize()
@Property({ type: 'integer', label: 'Tile Height', min: 1 })
public tileHeight: number = 32;
/** Component visibility | 组件可见性 */
@Serialize()
@Property({ type: 'boolean', label: 'Visible' })
public visible: boolean = true;
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 导入
2025-12-13 19:44:08 +08:00
/** Rendering sort order (deprecated, use sortingLayer + orderInLayer) | 渲染排序顺序(已弃用,使用 sortingLayer + orderInLayer */
@Serialize()
@Property({ type: 'integer', label: 'Sorting Order' })
public sortingOrder: number = 0;
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 导入
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.
*/
@Serialize()
@Property({ type: 'integer', label: 'Order in Layer' })
public orderInLayer: number = 0;
/** Tint color in hex format | 着色颜色(十六进制格式) */
@Serialize()
@Property({ type: 'color', label: 'Color' })
public color: string = '#ffffff';
/** Opacity value (0-1) | 不透明度0-1 */
@Serialize()
@Property({ type: 'number', label: 'Alpha', min: 0, max: 1, step: 0.01 })
public alpha: number = 1;
/** Flag indicating render data needs update | 标记渲染数据需要更新 */
public renderDirty: boolean = true;
// ===== 多Tileset =====
@Serialize()
private _tilesets: ITilesetRef[] = [];
private _tilesetsData: Map<number, ITilesetData> = new Map();
// ===== 多图层 =====
@Serialize()
private _layers: ITilemapLayerData[] = [];
private _layersData: Map<string, Uint32Array> = new Map();
@Serialize()
private _activeLayerIndex: number = 0;
// ===== 碰撞数据 =====
@Serialize()
private _collisionDataArray: number[] = [];
private _collisionData: Uint32Array = new Uint32Array(0);
// ===== Getters =====
/** All tileset references | 所有图块集引用 */
get tilesets(): readonly ITilesetRef[] {
return this._tilesets;
}
/** All layer data | 所有图层数据 */
get layers(): readonly ITilemapLayerData[] {
return this._layers;
}
/** Current active layer index | 当前活动图层索引 */
get activeLayerIndex(): number {
return this._activeLayerIndex;
}
set activeLayerIndex(value: number) {
if (value >= 0 && value < this._layers.length) {
this._activeLayerIndex = value;
}
}
/** Current active layer data | 当前活动图层数据 */
get activeLayer(): ITilemapLayerData | undefined {
return this._layers[this._activeLayerIndex];
}
/** Total map width in pixels | 地图总宽度(像素) */
get pixelWidth(): number {
return this._width * this.tileWidth;
}
/** Total map height in pixels | 地图总高度(像素) */
get pixelHeight(): number {
return this._height * this.tileHeight;
}
/** Raw collision data array | 原始碰撞数据数组 */
get collisionData(): Uint32Array {
return this._collisionData;
}
// ===== Initialization | 初始化 =====
/**
* Initialize an empty tilemap with default layer
*
* @param width Map width in tiles |
* @param height Map height in tiles |
*/
initializeEmpty(width: number, height: number): void {
this._width = width;
this._height = height;
const defaultLayer: ITilemapLayerData = {
id: 'default',
name: 'Layer 0',
visible: true,
opacity: 1,
data: new Array(width * height).fill(0)
};
this._layers = [defaultLayer];
this._layersData.set('default', new Uint32Array(width * height));
this._activeLayerIndex = 0;
this.renderDirty = true;
}
/**
* Apply tilemap data from external source
*
* @param data Tilemap data to apply |
*/
applyTilemapData(data: ITilemapData): void {
this._width = data.width;
this._height = data.height;
this.tileWidth = data.tileWidth;
this.tileHeight = data.tileHeight;
// 加载Tilesets
this._tilesets = data.tilesets.map((ts) => ({ ...ts }));
this._tilesetsData.clear();
// 加载图层
this._layers = data.layers.map((layer) => ({
...layer,
data: [...layer.data]
}));
this._layersData.clear();
for (const layer of this._layers) {
this._layersData.set(layer.id, new Uint32Array(layer.data));
}
// 加载碰撞数据
if (data.collisionData) {
this._collisionData = new Uint32Array(data.collisionData);
this._collisionDataArray = [...data.collisionData];
} else {
this._collisionData = new Uint32Array(0);
this._collisionDataArray = [];
}
this.renderDirty = true;
}
// ===== Tileset Methods | 图块集方法 =====
/**
* Add a new tileset reference
*
* @param source Tileset image source path |
* @param firstGid Optional first global tile ID | ID
* @returns Index of the added tileset |
*/
addTileset(source: string, firstGid?: number): number {
const gid = firstGid ?? this.calculateNextFirstGid();
this._tilesets.push({ source, firstGid: gid });
this.renderDirty = true;
return this._tilesets.length - 1;
}
/**
* Remove a tileset by index
*
* @param index Tileset index to remove |
*/
removeTileset(index: number): void {
if (index >= 0 && index < this._tilesets.length) {
this._tilesets.splice(index, 1);
this._tilesetsData.delete(index);
this.renderDirty = true;
}
}
/**
* Set tileset data for a specific index
*
* @param index Tileset index |
* @param data Tileset data to set |
*/
setTilesetData(index: number, data: ITilesetData): void {
if (index >= 0 && index < this._tilesets.length) {
this._tilesets[index].data = data;
this._tilesetsData.set(index, data);
this.renderDirty = true;
}
}
/**
* Get tileset data by index
*
* @param index Tileset index |
* @returns Tileset data or undefined | undefined
*/
getTilesetData(index: number): ITilesetData | undefined {
return this._tilesetsData.get(index) || this._tilesets[index]?.data;
}
/**
* Find tileset for a global tile ID
* ID查找图块集
* @param gid Global tile ID | ID
* @returns Tileset info with local ID, or null if not found | ID的图块集信息null
*/
getTilesetForGid(gid: number): { tileset: ITilesetRef; localId: number; index: number } | null {
if (gid <= 0) return null;
for (let i = this._tilesets.length - 1; i >= 0; i--) {
const tileset = this._tilesets[i];
if (gid >= tileset.firstGid) {
return {
tileset,
localId: gid - tileset.firstGid + 1,
index: i
};
}
}
return null;
}
private calculateNextFirstGid(): number {
if (this._tilesets.length === 0) return 1;
let maxGid = 1;
for (const tileset of this._tilesets) {
const tileCount = tileset.data?.tileCount || 256;
const nextGid = tileset.firstGid + tileCount;
if (nextGid > maxGid) maxGid = nextGid;
}
return maxGid;
}
// ===== Layer Methods | 图层方法 =====
/**
* Add a new layer to the tilemap
*
* @param name Optional layer name |
* @param index Optional insertion index |
* @returns The created layer data |
*/
addLayer(name?: string, index?: number): ITilemapLayerData {
const id = `layer_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
const layerName = name || `Layer ${this._layers.length}`;
const data = new Array(this._width * this._height).fill(0);
const layer: ITilemapLayerData = {
id,
name: layerName,
visible: true,
opacity: 1,
data
};
if (index !== undefined && index >= 0 && index <= this._layers.length) {
this._layers.splice(index, 0, layer);
} else {
this._layers.push(layer);
}
this._layersData.set(id, new Uint32Array(data));
this.renderDirty = true;
return layer;
}
feat: 添加跨平台运行时、资产系统和UI适配功能 (#256) * feat(platform-common): 添加WASM加载器和环境检测API * feat(rapier2d): 新增Rapier2D WASM绑定包 * feat(physics-rapier2d): 添加跨平台WASM加载器 * feat(asset-system): 添加运行时资产目录和bundle格式 * feat(asset-system-editor): 新增编辑器资产管理包 * feat(editor-core): 添加构建系统和模块管理 * feat(editor-app): 重构浏览器预览使用import maps * feat(platform-web): 添加BrowserRuntime和资产读取 * feat(engine): 添加材质系统和着色器管理 * feat(material): 新增材质系统和着色器编辑器 * feat(tilemap): 增强tilemap编辑器和动画系统 * feat(modules): 添加module.json配置 * feat(core): 添加module.json和类型定义更新 * chore: 更新依赖和构建配置 * refactor(plugins): 更新插件模板使用ModuleManifest * chore: 添加第三方依赖库 * chore: 移除BehaviourTree-ai和ecs-astar子模块 * docs: 更新README和文档主题样式 * fix: 修复Rust文档测试和添加rapier2d WASM绑定 * fix(tilemap-editor): 修复画布高DPI屏幕分辨率适配问题 * feat(ui): 添加UI屏幕适配系统(CanvasScaler/SafeArea) * fix(ecs-engine-bindgen): 添加缺失的ecs-framework-math依赖 * fix: 添加缺失的包依赖修复CI构建 * fix: 修复CodeQL检测到的代码问题 * fix: 修复构建错误和缺失依赖 * fix: 修复类型检查错误 * fix(material-system): 修复tsconfig配置支持TypeScript项目引用 * fix(editor-core): 修复Rollup构建配置添加tauri external * fix: 修复CodeQL检测到的代码问题 * fix: 修复CodeQL检测到的代码问题
2025-12-03 22:15:22 +08:00
/**
* Duplicate a layer
*
* @param index Layer index to duplicate |
* @returns The duplicated layer data, or null if index is invalid | null
*/
duplicateLayer(index: number): ITilemapLayerData | null {
if (index < 0 || index >= this._layers.length) {
return null;
}
const sourceLayer = this._layers[index];
const sourceData = this._layersData.get(sourceLayer.id);
if (!sourceData) {
return null;
}
const id = `layer_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
const newLayer: ITilemapLayerData = {
id,
name: `${sourceLayer.name} (副本)`,
visible: sourceLayer.visible,
opacity: sourceLayer.opacity,
data: Array.from(sourceData)
};
// Insert after the source layer
this._layers.splice(index + 1, 0, newLayer);
this._layersData.set(id, new Uint32Array(sourceData));
this.renderDirty = true;
return newLayer;
}
/**
* Remove a layer by index (cannot remove last layer)
*
* @param index Layer index to remove |
*/
removeLayer(index: number): void {
if (index >= 0 && index < this._layers.length && this._layers.length > 1) {
const layer = this._layers[index];
this._layers.splice(index, 1);
this._layersData.delete(layer.id);
if (this._activeLayerIndex >= this._layers.length) {
this._activeLayerIndex = this._layers.length - 1;
}
this.renderDirty = true;
}
}
/**
* Move a layer from one position to another
*
* @param fromIndex Source index |
* @param toIndex Target index |
*/
moveLayer(fromIndex: number, toIndex: number): void {
if (
fromIndex >= 0 &&
fromIndex < this._layers.length &&
toIndex >= 0 &&
toIndex < this._layers.length &&
fromIndex !== toIndex
) {
const [layer] = this._layers.splice(fromIndex, 1);
this._layers.splice(toIndex, 0, layer);
this.renderDirty = true;
}
}
/**
* Get layer data by index
*
*/
getLayer(index: number): ITilemapLayerData | undefined {
return this._layers[index];
}
/**
* Get layer data by ID
* ID获取图层数据
*/
getLayerById(id: string): ITilemapLayerData | undefined {
return this._layers.find((l) => l.id === id);
}
/**
* Set layer visibility
*
*/
setLayerVisible(index: number, visible: boolean): void {
if (index >= 0 && index < this._layers.length) {
this._layers[index].visible = visible;
this.renderDirty = true;
}
}
/**
* Set layer opacity
*
*/
setLayerOpacity(index: number, opacity: number): void {
if (index >= 0 && index < this._layers.length) {
this._layers[index].opacity = Math.max(0, Math.min(1, opacity));
this.renderDirty = true;
}
}
/**
* Rename a layer
*
*/
renameLayer(index: number, name: string): void {
if (index >= 0 && index < this._layers.length) {
this._layers[index].name = name;
}
}
feat: 添加跨平台运行时、资产系统和UI适配功能 (#256) * feat(platform-common): 添加WASM加载器和环境检测API * feat(rapier2d): 新增Rapier2D WASM绑定包 * feat(physics-rapier2d): 添加跨平台WASM加载器 * feat(asset-system): 添加运行时资产目录和bundle格式 * feat(asset-system-editor): 新增编辑器资产管理包 * feat(editor-core): 添加构建系统和模块管理 * feat(editor-app): 重构浏览器预览使用import maps * feat(platform-web): 添加BrowserRuntime和资产读取 * feat(engine): 添加材质系统和着色器管理 * feat(material): 新增材质系统和着色器编辑器 * feat(tilemap): 增强tilemap编辑器和动画系统 * feat(modules): 添加module.json配置 * feat(core): 添加module.json和类型定义更新 * chore: 更新依赖和构建配置 * refactor(plugins): 更新插件模板使用ModuleManifest * chore: 添加第三方依赖库 * chore: 移除BehaviourTree-ai和ecs-astar子模块 * docs: 更新README和文档主题样式 * fix: 修复Rust文档测试和添加rapier2d WASM绑定 * fix(tilemap-editor): 修复画布高DPI屏幕分辨率适配问题 * feat(ui): 添加UI屏幕适配系统(CanvasScaler/SafeArea) * fix(ecs-engine-bindgen): 添加缺失的ecs-framework-math依赖 * fix: 添加缺失的包依赖修复CI构建 * fix: 修复CodeQL检测到的代码问题 * fix: 修复构建错误和缺失依赖 * fix: 修复类型检查错误 * fix(material-system): 修复tsconfig配置支持TypeScript项目引用 * fix(editor-core): 修复Rollup构建配置添加tauri external * fix: 修复CodeQL检测到的代码问题 * fix: 修复CodeQL检测到的代码问题
2025-12-03 22:15:22 +08:00
/**
* Set layer color (tint)
*
*/
setLayerColor(index: number, color: string): void {
if (index >= 0 && index < this._layers.length) {
this._layers[index].color = color;
this.renderDirty = true;
}
}
/**
* Get layer color
*
*/
getLayerColor(index: number): string {
if (index >= 0 && index < this._layers.length) {
return this._layers[index].color ?? '#ffffff';
}
return '#ffffff';
}
/**
* Set layer hidden in game
*
*/
setLayerHiddenInGame(index: number, hidden: boolean): void {
if (index >= 0 && index < this._layers.length) {
this._layers[index].hiddenInGame = hidden;
}
}
/**
* Get layer hidden in game
*
*/
getLayerHiddenInGame(index: number): boolean {
if (index >= 0 && index < this._layers.length) {
return this._layers[index].hiddenInGame ?? false;
}
return false;
}
/**
* Set layer material path
*
* @param index Layer index |
* @param materialPath Material asset path (.mat file) | .mat
*/
setLayerMaterial(index: number, materialPath: string): void {
if (index >= 0 && index < this._layers.length) {
this._layers[index].materialPath = materialPath;
this._layers[index].materialId = undefined;
this.renderDirty = true;
}
}
/**
* Get layer material path
*
* @param index Layer index |
* @returns Material path or undefined | undefined
*/
getLayerMaterial(index: number): string | undefined {
return this._layers[index]?.materialPath;
}
/**
* Set layer material ID (runtime)
* ID
* @param index Layer index |
* @param materialId Runtime material ID | ID
*/
setLayerMaterialId(index: number, materialId: number): void {
if (index >= 0 && index < this._layers.length) {
this._layers[index].materialId = materialId;
this.renderDirty = true;
}
}
/**
* Get layer material ID
* ID
* @param index Layer index |
* @returns Material ID or 0 (default) | ID 0
*/
getLayerMaterialId(index: number): number {
return this._layers[index]?.materialId ?? 0;
}
// ===== Tile Operations | 瓦片操作 =====
/**
* Get tile index at position
*
* @param layerIndex Layer index |
* @param col Column (X) | X
* @param row Row (Y) | Y
* @returns Tile index (0 = empty) | 0
*/
getTile(layerIndex: number, col: number, row: number): number {
if (col < 0 || col >= this._width || row < 0 || row >= this._height) {
return 0;
}
const layer = this._layers[layerIndex];
if (!layer) return 0;
const layerData = this._layersData.get(layer.id);
if (layerData) {
return layerData[row * this._width + col];
}
return layer.data[row * this._width + col] || 0;
}
/**
* Set tile index at position
*
* @param layerIndex Layer index |
* @param col Column (X) | X
* @param row Row (Y) | Y
* @param tileIndex Tile index to set (0 = clear) | 0
*/
setTile(layerIndex: number, col: number, row: number, tileIndex: number): void {
if (col < 0 || col >= this._width || row < 0 || row >= this._height) {
return;
}
const layer = this._layers[layerIndex];
if (!layer) return;
const index = row * this._width + col;
layer.data[index] = tileIndex;
let layerData = this._layersData.get(layer.id);
if (!layerData) {
layerData = new Uint32Array(layer.data);
this._layersData.set(layer.id, layerData);
}
layerData[index] = tileIndex;
this.renderDirty = true;
}
/**
* Get raw tile data array for a layer
*
* @param layerIndex Layer index |
* @returns Uint32Array of tile indices | Uint32Array
*/
getLayerData(layerIndex: number): Uint32Array | undefined {
const layer = this._layers[layerIndex];
if (!layer) return undefined;
return this._layersData.get(layer.id);
}
feat: 添加跨平台运行时、资产系统和UI适配功能 (#256) * feat(platform-common): 添加WASM加载器和环境检测API * feat(rapier2d): 新增Rapier2D WASM绑定包 * feat(physics-rapier2d): 添加跨平台WASM加载器 * feat(asset-system): 添加运行时资产目录和bundle格式 * feat(asset-system-editor): 新增编辑器资产管理包 * feat(editor-core): 添加构建系统和模块管理 * feat(editor-app): 重构浏览器预览使用import maps * feat(platform-web): 添加BrowserRuntime和资产读取 * feat(engine): 添加材质系统和着色器管理 * feat(material): 新增材质系统和着色器编辑器 * feat(tilemap): 增强tilemap编辑器和动画系统 * feat(modules): 添加module.json配置 * feat(core): 添加module.json和类型定义更新 * chore: 更新依赖和构建配置 * refactor(plugins): 更新插件模板使用ModuleManifest * chore: 添加第三方依赖库 * chore: 移除BehaviourTree-ai和ecs-astar子模块 * docs: 更新README和文档主题样式 * fix: 修复Rust文档测试和添加rapier2d WASM绑定 * fix(tilemap-editor): 修复画布高DPI屏幕分辨率适配问题 * feat(ui): 添加UI屏幕适配系统(CanvasScaler/SafeArea) * fix(ecs-engine-bindgen): 添加缺失的ecs-framework-math依赖 * fix: 添加缺失的包依赖修复CI构建 * fix: 修复CodeQL检测到的代码问题 * fix: 修复构建错误和缺失依赖 * fix: 修复类型检查错误 * fix(material-system): 修复tsconfig配置支持TypeScript项目引用 * fix(editor-core): 修复Rollup构建配置添加tauri external * fix: 修复CodeQL检测到的代码问题 * fix: 修复CodeQL检测到的代码问题
2025-12-03 22:15:22 +08:00
/**
* Set raw tile data array for a layer
*
* @param layerIndex Layer index |
* @param data Uint32Array of tile indices | Uint32Array
*/
setLayerData(layerIndex: number, data: Uint32Array): void {
const layer = this._layers[layerIndex];
if (!layer) return;
// Copy data to both the layer object and the internal map
layer.data = Array.from(data);
this._layersData.set(layer.id, new Uint32Array(data));
this.renderDirty = true;
}
/**
* Get merged tile data from all visible layers
*
* @returns Merged tile data array |
*/
getMergedTileData(): Uint32Array {
const merged = new Uint32Array(this._width * this._height);
for (const layer of this._layers) {
if (!layer.visible) continue;
const layerData = this._layersData.get(layer.id);
if (!layerData) continue;
for (let i = 0; i < merged.length; i++) {
if (layerData[i] > 0) {
merged[i] = layerData[i];
}
}
}
return merged;
}
// ===== Collision | 碰撞 =====
/**
* Check if tile has collision
*
* @param col Column (X) | X
* @param row Row (Y) | Y
* @returns True if has collision | true
*/
hasCollision(col: number, row: number): boolean {
if (col < 0 || col >= this._width || row < 0 || row >= this._height) {
return true;
}
if (this._collisionData.length === 0) {
return false;
}
return this._collisionData[row * this._width + col] > 0;
}
/**
* Get collision type at tile position
*
*/
getCollisionType(col: number, row: number): number {
if (col < 0 || col >= this._width || row < 0 || row >= this._height) {
return 0;
}
if (this._collisionData.length === 0) {
return 0;
}
return this._collisionData[row * this._width + col];
}
/**
* Set collision type at tile position
*
* @param col Column (X) | X
* @param row Row (Y) | Y
* @param collisionType Collision type (0 = none) | 0
*/
setCollision(col: number, row: number, collisionType: number): void {
if (col < 0 || col >= this._width || row < 0 || row >= this._height) {
return;
}
if (this._collisionData.length === 0) {
this._collisionData = new Uint32Array(this._width * this._height);
this._collisionDataArray = new Array(this._width * this._height).fill(0);
}
const index = row * this._width + col;
this._collisionData[index] = collisionType;
this._collisionDataArray[index] = collisionType;
}
/**
* Check collision at world coordinates
*
*/
hasCollisionAt(worldX: number, worldY: number): boolean {
const [col, row] = this.worldToTile(worldX, worldY);
return this.hasCollision(col, row);
}
/**
* Get all collision tiles within bounds
*
* @returns Array of [col, row, type] | [, , ]
*/
getCollisionTilesInBounds(
left: number,
bottom: number,
right: number,
top: number
): Array<[number, number, number]> {
const result: Array<[number, number, number]> = [];
if (this._collisionData.length === 0) {
return result;
}
const startCol = Math.max(0, Math.floor(left / this.tileWidth));
const endCol = Math.min(this._width, Math.ceil(right / this.tileWidth));
const startRow = Math.max(0, Math.floor(bottom / this.tileHeight));
const endRow = Math.min(this._height, Math.ceil(top / this.tileHeight));
for (let row = startRow; row < endRow; row++) {
for (let col = startCol; col < endCol; col++) {
const type = this._collisionData[row * this._width + col];
if (type > 0) {
result.push([col, row, type]);
}
}
}
return result;
}
/**
* Generate collision rectangles for physics
*
* @returns Array of collision rectangles |
*/
generateCollisionRects(): Array<{
x: number;
y: number;
width: number;
height: number;
type: number;
}> {
const rects: Array<{
x: number;
y: number;
width: number;
height: number;
type: number;
}> = [];
if (this._collisionData.length === 0) {
return rects;
}
for (let row = 0; row < this._height; row++) {
for (let col = 0; col < this._width; col++) {
const type = this._collisionData[row * this._width + col];
if (type > 0) {
rects.push({
x: col * this.tileWidth,
y: row * this.tileHeight,
width: this.tileWidth,
height: this.tileHeight,
type
});
}
}
}
return rects;
}
// ===== Coordinate Conversion | 坐标转换 =====
/**
* Convert world coordinates to tile coordinates
*
* @returns [col, row] | [, ]
*/
worldToTile(worldX: number, worldY: number): [number, number] {
const col = Math.floor(worldX / this.tileWidth);
const row = Math.floor(worldY / this.tileHeight);
return [col, row];
}
/**
* Convert tile coordinates to world coordinates (center of tile)
*
* @returns [worldX, worldY] | [X, Y]
*/
tileToWorld(col: number, row: number): [number, number] {
const worldX = col * this.tileWidth + this.tileWidth / 2;
const worldY = row * this.tileHeight + this.tileHeight / 2;
return [worldX, worldY];
}
// ===== UV Calculation | UV计算 =====
/**
* Get UV coordinates for a tile
* tile UV
*
* 使 UVHelper OpenGL
* Uses UVHelper to calculate OpenGL texture coordinates.
*
* @see UVHelper.calculateTileUV for coordinate system documentation
* @param tilesetIndex Tileset index | Tileset
* @param localTileId Local tile ID (1-based) | tile ID1
* @returns [u0, v0, u1, v1] OpenGL UV coordinates, or null if invalid
*/
getTileUV(tilesetIndex: number, localTileId: number): [number, number, number, number] | null {
if (localTileId <= 0) return null;
const tilesetData = this.getTilesetData(tilesetIndex);
if (!tilesetData) {
console.warn('[TilemapComponent] getTileUV: No tileset data for index', tilesetIndex);
return null;
}
// Use UVHelper for coordinate calculation
// 使用 UVHelper 计算坐标
return UVHelper.calculateTileUV(localTileId - 1, {
columns: tilesetData.columns,
tileWidth: tilesetData.tileWidth,
tileHeight: tilesetData.tileHeight,
imageWidth: tilesetData.imageWidth,
imageHeight: tilesetData.imageHeight,
margin: tilesetData.margin,
spacing: tilesetData.spacing
});
}
// ===== Resize | 大小调整 =====
/**
* Calculate offset based on anchor point
*
*/
private calculateAnchorOffset(
oldSize: number,
newSize: number,
anchor: 'start' | 'center' | 'end'
): number {
const delta = newSize - oldSize;
switch (anchor) {
case 'start': return 0;
case 'center': return Math.floor(delta / 2);
case 'end': return delta;
}
}
/**
* Resize the tilemap, preserving existing data at the specified anchor position
*
* @param newWidth New width in tiles |
* @param newHeight New height in tiles |
* @param anchor Anchor point for preserving data (default: 'bottom-left' for Y-up coordinate system) | 'bottom-left'Y轴向上的坐标系
*/
resize(newWidth: number, newHeight: number, anchor: ResizeAnchor = 'bottom-left'): void {
if (newWidth === this._width && newHeight === this._height) {
return;
}
// Parse anchor to get X and Y alignment
// 解析锚点获取X和Y方向的对齐方式
feat: 添加跨平台运行时、资产系统和UI适配功能 (#256) * feat(platform-common): 添加WASM加载器和环境检测API * feat(rapier2d): 新增Rapier2D WASM绑定包 * feat(physics-rapier2d): 添加跨平台WASM加载器 * feat(asset-system): 添加运行时资产目录和bundle格式 * feat(asset-system-editor): 新增编辑器资产管理包 * feat(editor-core): 添加构建系统和模块管理 * feat(editor-app): 重构浏览器预览使用import maps * feat(platform-web): 添加BrowserRuntime和资产读取 * feat(engine): 添加材质系统和着色器管理 * feat(material): 新增材质系统和着色器编辑器 * feat(tilemap): 增强tilemap编辑器和动画系统 * feat(modules): 添加module.json配置 * feat(core): 添加module.json和类型定义更新 * chore: 更新依赖和构建配置 * refactor(plugins): 更新插件模板使用ModuleManifest * chore: 添加第三方依赖库 * chore: 移除BehaviourTree-ai和ecs-astar子模块 * docs: 更新README和文档主题样式 * fix: 修复Rust文档测试和添加rapier2d WASM绑定 * fix(tilemap-editor): 修复画布高DPI屏幕分辨率适配问题 * feat(ui): 添加UI屏幕适配系统(CanvasScaler/SafeArea) * fix(ecs-engine-bindgen): 添加缺失的ecs-framework-math依赖 * fix: 添加缺失的包依赖修复CI构建 * fix: 修复CodeQL检测到的代码问题 * fix: 修复构建错误和缺失依赖 * fix: 修复类型检查错误 * fix(material-system): 修复tsconfig配置支持TypeScript项目引用 * fix(editor-core): 修复Rollup构建配置添加tauri external * fix: 修复CodeQL检测到的代码问题 * fix: 修复CodeQL检测到的代码问题
2025-12-03 22:15:22 +08:00
let xAnchor: 'start' | 'center' | 'end';
let yAnchor: 'start' | 'center' | 'end';
if (anchor.includes('left')) xAnchor = 'start';
else if (anchor.includes('right')) xAnchor = 'end';
else xAnchor = 'center';
if (anchor.includes('bottom')) yAnchor = 'end';
else if (anchor.includes('top')) yAnchor = 'start';
else yAnchor = 'center';
// Calculate offsets for placing old data in new array
// 计算将旧数据放入新数组的偏移量
const offsetX = this.calculateAnchorOffset(this._width, newWidth, xAnchor);
const offsetY = this.calculateAnchorOffset(this._height, newHeight, yAnchor);
// 调整所有图层
for (const layer of this._layers) {
const oldLayerData = this._layersData.get(layer.id);
const newLayerData = new Uint32Array(newWidth * newHeight);
const newDataArray = new Array(newWidth * newHeight).fill(0);
if (oldLayerData) {
for (let y = 0; y < this._height; y++) {
for (let x = 0; x < this._width; x++) {
const newX = x + offsetX;
const newY = y + offsetY;
// Check bounds
if (newX >= 0 && newX < newWidth && newY >= 0 && newY < newHeight) {
const value = oldLayerData[y * this._width + x];
newLayerData[newY * newWidth + newX] = value;
newDataArray[newY * newWidth + newX] = value;
}
}
}
}
this._layersData.set(layer.id, newLayerData);
layer.data = newDataArray;
}
// 调整碰撞数据
if (this._collisionData.length > 0) {
const newCollisionData = new Uint32Array(newWidth * newHeight);
const newCollisionArray = new Array(newWidth * newHeight).fill(0);
for (let y = 0; y < this._height; y++) {
for (let x = 0; x < this._width; x++) {
const newX = x + offsetX;
const newY = y + offsetY;
if (newX >= 0 && newX < newWidth && newY >= 0 && newY < newHeight) {
const value = this._collisionData[y * this._width + x];
newCollisionData[newY * newWidth + newX] = value;
newCollisionArray[newY * newWidth + newX] = value;
}
}
}
this._collisionData = newCollisionData;
this._collisionDataArray = newCollisionArray;
}
this._width = newWidth;
this._height = newHeight;
this.renderDirty = true;
}
// ===== Serialization | 序列化 =====
/**
* Called after deserialization to restore runtime data
*
*/
override onDeserialized(): void {
// 恢复图层运行时数据
for (const layer of this._layers) {
if (layer.data && layer.data.length > 0) {
this._layersData.set(layer.id, new Uint32Array(layer.data));
}
}
// 恢复Tileset缓存数据
for (let i = 0; i < this._tilesets.length; i++) {
const tileset = this._tilesets[i];
if (tileset.data) {
this._tilesetsData.set(i, tileset.data);
}
}
// 恢复碰撞数据
if (this._collisionDataArray.length > 0) {
this._collisionData = new Uint32Array(this._collisionDataArray);
}
this.renderDirty = true;
}
/**
* Cleanup when component is destroyed
*
*/
onDestroy(): void {
this._tilesets = [];
this._tilesetsData.clear();
this._layers = [];
this._layersData.clear();
this._collisionData = new Uint32Array(0);
this._collisionDataArray = [];
}
/**
* Export tilemap to data format
*
* @returns Tilemap data object |
*/
exportToData(): ITilemapData {
return {
name: 'Tilemap',
version: 2,
width: this._width,
height: this._height,
tileWidth: this.tileWidth,
tileHeight: this.tileHeight,
tilesets: this._tilesets.map((ts) => ({ ...ts })),
layers: this._layers.map((layer) => ({
...layer,
data: [...layer.data]
})),
collisionData: this._collisionData.length > 0 ? Array.from(this._collisionData) : undefined
};
}
// ===== IResourceComponent 实现 =====
/**
*
* Get all resource references needed by this component
*/
getResourceReferences(): ResourceReference[] {
const refs: ResourceReference[] = [];
// 收集所有 tileset 纹理引用
// Collect all tileset texture references
for (const tileset of this._tilesets) {
if (tileset.source) {
refs.push({
path: tileset.source,
type: 'texture',
runtimeId: tileset.textureId
});
}
}
feat: 添加跨平台运行时、资产系统和UI适配功能 (#256) * feat(platform-common): 添加WASM加载器和环境检测API * feat(rapier2d): 新增Rapier2D WASM绑定包 * feat(physics-rapier2d): 添加跨平台WASM加载器 * feat(asset-system): 添加运行时资产目录和bundle格式 * feat(asset-system-editor): 新增编辑器资产管理包 * feat(editor-core): 添加构建系统和模块管理 * feat(editor-app): 重构浏览器预览使用import maps * feat(platform-web): 添加BrowserRuntime和资产读取 * feat(engine): 添加材质系统和着色器管理 * feat(material): 新增材质系统和着色器编辑器 * feat(tilemap): 增强tilemap编辑器和动画系统 * feat(modules): 添加module.json配置 * feat(core): 添加module.json和类型定义更新 * chore: 更新依赖和构建配置 * refactor(plugins): 更新插件模板使用ModuleManifest * chore: 添加第三方依赖库 * chore: 移除BehaviourTree-ai和ecs-astar子模块 * docs: 更新README和文档主题样式 * fix: 修复Rust文档测试和添加rapier2d WASM绑定 * fix(tilemap-editor): 修复画布高DPI屏幕分辨率适配问题 * feat(ui): 添加UI屏幕适配系统(CanvasScaler/SafeArea) * fix(ecs-engine-bindgen): 添加缺失的ecs-framework-math依赖 * fix: 添加缺失的包依赖修复CI构建 * fix: 修复CodeQL检测到的代码问题 * fix: 修复构建错误和缺失依赖 * fix: 修复类型检查错误 * fix(material-system): 修复tsconfig配置支持TypeScript项目引用 * fix(editor-core): 修复Rollup构建配置添加tauri external * fix: 修复CodeQL检测到的代码问题 * fix: 修复CodeQL检测到的代码问题
2025-12-03 22:15:22 +08:00
// 收集所有图层材质引用
// Collect all layer material references
for (const layer of this._layers) {
if (layer.materialPath) {
refs.push({
path: layer.materialPath,
type: 'data',
runtimeId: layer.materialId
});
}
}
return refs;
}
/**
* ID
* Set runtime IDs for loaded resources
*/
setResourceIds(pathToId: Map<string, number>): void {
// 为每个 tileset 设置纹理 ID
// Set texture ID for each tileset
for (const tileset of this._tilesets) {
if (tileset.source) {
const textureId = pathToId.get(tileset.source);
if (textureId !== undefined) {
tileset.textureId = textureId;
}
}
}
feat: 添加跨平台运行时、资产系统和UI适配功能 (#256) * feat(platform-common): 添加WASM加载器和环境检测API * feat(rapier2d): 新增Rapier2D WASM绑定包 * feat(physics-rapier2d): 添加跨平台WASM加载器 * feat(asset-system): 添加运行时资产目录和bundle格式 * feat(asset-system-editor): 新增编辑器资产管理包 * feat(editor-core): 添加构建系统和模块管理 * feat(editor-app): 重构浏览器预览使用import maps * feat(platform-web): 添加BrowserRuntime和资产读取 * feat(engine): 添加材质系统和着色器管理 * feat(material): 新增材质系统和着色器编辑器 * feat(tilemap): 增强tilemap编辑器和动画系统 * feat(modules): 添加module.json配置 * feat(core): 添加module.json和类型定义更新 * chore: 更新依赖和构建配置 * refactor(plugins): 更新插件模板使用ModuleManifest * chore: 添加第三方依赖库 * chore: 移除BehaviourTree-ai和ecs-astar子模块 * docs: 更新README和文档主题样式 * fix: 修复Rust文档测试和添加rapier2d WASM绑定 * fix(tilemap-editor): 修复画布高DPI屏幕分辨率适配问题 * feat(ui): 添加UI屏幕适配系统(CanvasScaler/SafeArea) * fix(ecs-engine-bindgen): 添加缺失的ecs-framework-math依赖 * fix: 添加缺失的包依赖修复CI构建 * fix: 修复CodeQL检测到的代码问题 * fix: 修复构建错误和缺失依赖 * fix: 修复类型检查错误 * fix(material-system): 修复tsconfig配置支持TypeScript项目引用 * fix(editor-core): 修复Rollup构建配置添加tauri external * fix: 修复CodeQL检测到的代码问题 * fix: 修复CodeQL检测到的代码问题
2025-12-03 22:15:22 +08:00
// 为每个图层设置材质 ID
// Set material ID for each layer
for (const layer of this._layers) {
if (layer.materialPath) {
const materialId = pathToId.get(layer.materialPath);
if (materialId !== undefined) {
layer.materialId = materialId;
}
}
}
// 标记渲染数据为脏,需要重新构建
// Mark render data as dirty, needs rebuild
this.renderDirty = true;
}
}