feat: 纹理路径稳定 ID 与架构改进 (#305)
* feat(asset-system): 实现路径稳定 ID 生成器 使用 FNV-1a hash 算法为纹理生成稳定的运行时 ID: - 新增 _pathIdCache 静态缓存,跨 Play/Stop 循环保持稳定 - 新增 getStableIdForPath() 方法,相同路径永远返回相同 ID - 修改 loadTextureForComponent/loadTextureByGuid 使用稳定 ID - clearTextureMappings() 不再清除 _pathIdCache 这解决了 Play/Stop 后纹理 ID 失效的根本问题。 * fix(runtime-core): 移除 Play/Stop 循环中的 clearTextureMappings 调用 使用路径稳定 ID 后,不再需要在快照保存/恢复时清除纹理缓存: - saveSceneSnapshot() 移除 clearTextureMappings() 调用 - restoreSceneSnapshot() 移除 clearTextureMappings() 调用 - 组件保存的 textureId 在 Play/Stop 后仍然有效 * fix(editor-core): 修复场景切换时的资源泄漏 在 openScene() 加载新场景前先卸载旧场景资源: - 调用 sceneResourceManager.unloadSceneResources() 释放旧资源 - 使用引用计数机制,仅卸载不再被引用的资源 - 路径稳定 ID 缓存不受影响,保持 ID 稳定性 * fix(runtime-core): 修复 PluginManager 组件注册类型错误 将 ComponentRegistry 类改为 GlobalComponentRegistry 实例: - registerComponents() 期望 IComponentRegistry 接口实例 - GlobalComponentRegistry 是 ComponentRegistry 的全局实例 * refactor(core): 提取 IComponentRegistry 接口 将组件注册表抽象为接口,支持场景级组件注册: - 新增 IComponentRegistry 接口定义 - Scene 持有独立的 componentRegistry 实例 - 支持从 GlobalComponentRegistry 克隆 - 各系统支持传入自定义注册表 * refactor(engine-core): 改进插件服务注册机制 - 更新 IComponentRegistry 类型引用 - 优化 PluginServiceRegistry 服务管理 * refactor(modules): 适配新的组件注册接口 更新各模块 RuntimeModule 使用 IComponentRegistry 接口: - audio, behavior-tree, camera - sprite, tilemap, world-streaming * fix(physics-rapier2d): 修复物理插件组件注册 - PhysicsEditorPlugin 添加 runtimeModule 引用 - 适配 IComponentRegistry 接口 - 修复物理组件在场景加载时未注册的问题 * feat(editor-core): 添加 UserCodeService 就绪信号机制 - 新增 waitForReady()/signalReady() API - 支持等待用户脚本编译完成 - 解决场景加载时组件未注册的时序问题 * fix(editor-app): 在编译完成后调用 signalReady() 确保用户脚本编译完成后发出就绪信号: - 编译成功后调用 userCodeService.signalReady() - 编译失败也要发出信号,避免阻塞场景加载 * feat(editor-core): 改进编辑器核心服务 - EntityStoreService 添加调试日志 - AssetRegistryService 优化资产注册 - PluginManager 改进插件管理 - IFileAPI 添加 getFileMtime 接口 * feat(engine): 改进 Rust 纹理管理器 - 支持任意 ID 的纹理加载(非递增) - 添加纹理状态追踪 API - 优化纹理缓存清理机制 - 更新 TypeScript 绑定 * feat(ui): 添加场景切换和文本闪烁组件 新增组件: - SceneLoadTriggerComponent: 场景切换触发器 - TextBlinkComponent: 文本闪烁效果 新增系统: - SceneLoadTriggerSystem: 处理场景切换逻辑 - TextBlinkSystem: 处理文本闪烁动画 其他改进: - UIRuntimeModule 适配新组件注册接口 - UI 渲染系统优化 * feat(editor-app): 添加外部文件修改检测 - 新增 ExternalModificationDialog 组件 - TauriFileAPI 支持 getFileMtime - 场景文件被外部修改时提示用户 * feat(editor-app): 添加渲染调试面板 - 新增 RenderDebugService 和调试面板 UI - App/ContentBrowser 添加调试日志 - TitleBar/Viewport 优化 - DialogManager 改进 * refactor(editor-app): 编辑器服务和组件优化 - EngineService 改进引擎集成 - EditorEngineSync 同步优化 - AssetFileInspector 改进 - VectorFieldEditors 优化 - InstantiatePrefabCommand 改进 * feat(i18n): 更新国际化翻译 - 添加新功能相关翻译 - 更新中文、英文、西班牙文 * feat(tauri): 添加文件修改时间查询命令 - 新增 get_file_mtime 命令 - 支持检测文件外部修改 * refactor(particle): 粒子系统改进 - 适配新的组件注册接口 - ParticleSystem 优化 - 添加单元测试 * refactor(platform): 平台适配层优化 - BrowserRuntime 改进 - 新增 RuntimeSceneManager 服务 - 导出优化 * refactor(asset-system-editor): 资产元数据改进 - AssetMetaFile 优化 - 导出调整 * fix(asset-system): 移除未使用的 TextureLoader 导入 * fix(tests): 更新测试以使用 GlobalComponentRegistry 实例 修复多个测试文件以适配 ComponentRegistry 从静态类变为实例类的变更: - ComponentStorage.test.ts: 使用 GlobalComponentRegistry.reset() - EntitySerializer.test.ts: 使用 GlobalComponentRegistry 实例 - IncrementalSerialization.test.ts: 使用 GlobalComponentRegistry 实例 - SceneSerializer.test.ts: 使用 GlobalComponentRegistry 实例 - ComponentRegistry.extended.test.ts: 使用 GlobalComponentRegistry,同时注册到 scene.componentRegistry - SystemTypes.test.ts: 在 Scene 创建前注册组件 - QuerySystem.test.ts: mockScene 添加 componentRegistry
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import type { IScene } from '@esengine/ecs-framework';
|
||||
import { ComponentRegistry } from '@esengine/ecs-framework';
|
||||
import type { IScene, IComponentRegistry } from '@esengine/ecs-framework';
|
||||
import type { IRuntimeModule, IRuntimePlugin, ModuleManifest, SystemContext } from '@esengine/engine-core';
|
||||
import { EngineBridgeToken } from '@esengine/ecs-engine-bindgen';
|
||||
|
||||
@@ -14,10 +13,14 @@ import {
|
||||
UISliderComponent,
|
||||
UIScrollViewComponent
|
||||
} from './components';
|
||||
import { TextBlinkComponent } from './components/TextBlinkComponent';
|
||||
import { SceneLoadTriggerComponent } from './components/SceneLoadTriggerComponent';
|
||||
import { UILayoutSystem } from './systems/UILayoutSystem';
|
||||
import { UIInputSystem } from './systems/UIInputSystem';
|
||||
import { UIAnimationSystem } from './systems/UIAnimationSystem';
|
||||
import { UIRenderDataProvider } from './systems/UIRenderDataProvider';
|
||||
import { TextBlinkSystem } from './systems/TextBlinkSystem';
|
||||
import { SceneLoadTriggerSystem } from './systems/SceneLoadTriggerSystem';
|
||||
import {
|
||||
UIRenderBeginSystem,
|
||||
UIRectRenderSystem,
|
||||
@@ -43,7 +46,7 @@ export {
|
||||
} from './tokens';
|
||||
|
||||
class UIRuntimeModule implements IRuntimeModule {
|
||||
registerComponents(registry: typeof ComponentRegistry): void {
|
||||
registerComponents(registry: IComponentRegistry): void {
|
||||
registry.register(UITransformComponent);
|
||||
registry.register(UIRenderComponent);
|
||||
registry.register(UIInteractableComponent);
|
||||
@@ -53,6 +56,8 @@ class UIRuntimeModule implements IRuntimeModule {
|
||||
registry.register(UIProgressBarComponent);
|
||||
registry.register(UISliderComponent);
|
||||
registry.register(UIScrollViewComponent);
|
||||
registry.register(TextBlinkComponent);
|
||||
registry.register(SceneLoadTriggerComponent);
|
||||
}
|
||||
|
||||
createSystems(scene: IScene, context: SystemContext): void {
|
||||
@@ -65,6 +70,14 @@ class UIRuntimeModule implements IRuntimeModule {
|
||||
const animationSystem = new UIAnimationSystem();
|
||||
scene.addSystem(animationSystem);
|
||||
|
||||
// 文本闪烁系统 | Text blink system
|
||||
const textBlinkSystem = new TextBlinkSystem();
|
||||
scene.addSystem(textBlinkSystem);
|
||||
|
||||
// 场景加载触发系统 | Scene load trigger system
|
||||
const sceneLoadTriggerSystem = new SceneLoadTriggerSystem();
|
||||
scene.addSystem(sceneLoadTriggerSystem);
|
||||
|
||||
const renderBeginSystem = new UIRenderBeginSystem();
|
||||
scene.addSystem(renderBeginSystem);
|
||||
|
||||
|
||||
61
packages/ui/src/components/SceneLoadTriggerComponent.ts
Normal file
61
packages/ui/src/components/SceneLoadTriggerComponent.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 场景加载触发组件
|
||||
* Scene Load Trigger Component
|
||||
*
|
||||
* 配合 UIInteractable 使用,点击时自动加载指定场景。
|
||||
* Works with UIInteractable to automatically load scene on click.
|
||||
*/
|
||||
|
||||
import { Component, ECSComponent, Property, Serializable, Serialize } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* 场景加载触发组件
|
||||
* Scene Load Trigger Component
|
||||
*
|
||||
* 添加到带有 UIInteractable 的实体上,点击时会加载 targetScene 指定的场景。
|
||||
* Add to entity with UIInteractable, loads targetScene on click.
|
||||
*
|
||||
* @example
|
||||
* ```json
|
||||
* {
|
||||
* "type": "SceneLoadTrigger",
|
||||
* "data": {
|
||||
* "targetScene": "GameScene",
|
||||
* "enabled": true
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@ECSComponent('SceneLoadTrigger')
|
||||
@Serializable({ version: 1, typeId: 'SceneLoadTrigger' })
|
||||
export class SceneLoadTriggerComponent extends Component {
|
||||
/**
|
||||
* 目标场景名称
|
||||
* Target scene name to load on click
|
||||
*/
|
||||
@Serialize()
|
||||
@Property({ type: 'string', label: 'Target Scene' })
|
||||
public targetScene: string = '';
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
* Whether the trigger is enabled
|
||||
*/
|
||||
@Serialize()
|
||||
@Property({ type: 'boolean', label: 'Enabled' })
|
||||
public enabled: boolean = true;
|
||||
|
||||
/**
|
||||
* 点击后是否禁用(防止重复点击)
|
||||
* Disable after click (prevent double clicks)
|
||||
*/
|
||||
@Serialize()
|
||||
@Property({ type: 'boolean', label: 'Disable On Click' })
|
||||
public disableOnClick: boolean = true;
|
||||
|
||||
/**
|
||||
* 内部标记:回调是否已绑定
|
||||
* Internal flag: whether callback is bound
|
||||
*/
|
||||
public _callbackBound: boolean = false;
|
||||
}
|
||||
101
packages/ui/src/components/TextBlinkComponent.ts
Normal file
101
packages/ui/src/components/TextBlinkComponent.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* 文本闪烁组件
|
||||
* Text Blink Component
|
||||
*
|
||||
* 让文本产生闪烁效果,类似 Unity 的 Animation 实现
|
||||
* Creates a blinking effect for text, similar to Unity's Animation implementation
|
||||
*/
|
||||
|
||||
import { Component, ECSComponent, Property, Serializable, Serialize } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* 文本闪烁组件
|
||||
* Text Blink Component
|
||||
*/
|
||||
@ECSComponent('TextBlink')
|
||||
@Serializable({ version: 1, typeId: 'TextBlink' })
|
||||
export class TextBlinkComponent extends Component {
|
||||
/**
|
||||
* 闪烁速度(周期/秒)
|
||||
* Blink speed (cycles per second)
|
||||
*/
|
||||
@Serialize()
|
||||
@Property({ type: 'number', label: 'Speed', min: 0.1, max: 10, step: 0.1 })
|
||||
public speed: number = 1.5;
|
||||
|
||||
/**
|
||||
* 最小透明度
|
||||
* Minimum alpha
|
||||
*/
|
||||
@Serialize()
|
||||
@Property({ type: 'number', label: 'Min Alpha', min: 0, max: 1, step: 0.05 })
|
||||
public minAlpha: number = 0.3;
|
||||
|
||||
/**
|
||||
* 最大透明度
|
||||
* Maximum alpha
|
||||
*/
|
||||
@Serialize()
|
||||
@Property({ type: 'number', label: 'Max Alpha', min: 0, max: 1, step: 0.05 })
|
||||
public maxAlpha: number = 1.0;
|
||||
|
||||
/**
|
||||
* 是否启用闪烁
|
||||
* Whether blinking is enabled
|
||||
*/
|
||||
@Serialize()
|
||||
@Property({ type: 'boolean', label: 'Enabled' })
|
||||
public blinkEnabled: boolean = true;
|
||||
|
||||
// ============= 运行时状态(不序列化)| Runtime state (not serialized) =============
|
||||
|
||||
/** 当前时间 | Current time */
|
||||
private _time: number = 0;
|
||||
|
||||
/**
|
||||
* 获取当前时间
|
||||
* Get current time
|
||||
*/
|
||||
public get time(): number {
|
||||
return this._time;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
* Update time
|
||||
*/
|
||||
public addTime(deltaTime: number): void {
|
||||
this._time += deltaTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当前 alpha 值
|
||||
* Calculate current alpha value
|
||||
*
|
||||
* 使用正弦波实现平滑的闪烁效果
|
||||
* Uses sine wave for smooth blinking effect
|
||||
*/
|
||||
public calculateAlpha(): number {
|
||||
if (!this.blinkEnabled) {
|
||||
return this.maxAlpha;
|
||||
}
|
||||
|
||||
// 使用正弦波:sin 从 -1 到 1,映射到 minAlpha 到 maxAlpha
|
||||
// Using sine wave: sin from -1 to 1, mapped to minAlpha to maxAlpha
|
||||
const t = Math.sin(this._time * this.speed * Math.PI * 2);
|
||||
const normalized = (t + 1) / 2; // 0 到 1
|
||||
return this.minAlpha + normalized * (this.maxAlpha - this.minAlpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置状态
|
||||
* Reset state
|
||||
*/
|
||||
public reset(): void {
|
||||
this._time = 0;
|
||||
}
|
||||
|
||||
override onRemovedFromEntity(): void {
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
@@ -120,9 +120,36 @@ export class UIRenderComponent extends Component {
|
||||
/**
|
||||
* 九宫格边距 [top, right, bottom, left]
|
||||
* Nine-patch margins
|
||||
*
|
||||
* Defines the non-stretchable borders for nine-patch rendering.
|
||||
* 定义九宫格渲染时不可拉伸的边框区域。
|
||||
*/
|
||||
@Serialize()
|
||||
@Property({ type: 'vector4', label: 'Nine-Patch Margins' })
|
||||
public ninePatchMargins: [number, number, number, number] = [0, 0, 0, 0];
|
||||
|
||||
/**
|
||||
* 源纹理宽度(像素)
|
||||
* Source texture width in pixels
|
||||
*
|
||||
* Required for nine-patch UV calculations.
|
||||
* 九宫格 UV 计算所需。
|
||||
*/
|
||||
@Serialize()
|
||||
@Property({ type: 'number', label: 'Texture Width', min: 1 })
|
||||
public textureWidth: number = 0;
|
||||
|
||||
/**
|
||||
* 源纹理高度(像素)
|
||||
* Source texture height in pixels
|
||||
*
|
||||
* Required for nine-patch UV calculations.
|
||||
* 九宫格 UV 计算所需。
|
||||
*/
|
||||
@Serialize()
|
||||
@Property({ type: 'number', label: 'Texture Height', min: 1 })
|
||||
public textureHeight: number = 0;
|
||||
|
||||
// ===== 边框 Border =====
|
||||
|
||||
/**
|
||||
|
||||
@@ -275,6 +275,15 @@ export class UITransformComponent extends Component implements ISortable {
|
||||
*/
|
||||
public worldScaleY: number = 1;
|
||||
|
||||
/**
|
||||
* 计算后的世界层内顺序(考虑父元素和层级深度)
|
||||
* Computed world order in layer (considering parent and hierarchy depth)
|
||||
*
|
||||
* 子元素总是渲染在父元素之上:worldOrderInLayer = parentWorldOrder + depth * 1000 + localOrder
|
||||
* Children always render on top of parents: worldOrderInLayer = parentWorldOrder + depth * 1000 + localOrder
|
||||
*/
|
||||
public worldOrderInLayer: number = 0;
|
||||
|
||||
/**
|
||||
* 本地到世界的 2D 变换矩阵(只读,由 UILayoutSystem 计算)
|
||||
* Local to world 2D transformation matrix (readonly, computed by UILayoutSystem)
|
||||
|
||||
@@ -88,6 +88,9 @@ export {
|
||||
type UIFontWeight
|
||||
} from './components/UITextComponent';
|
||||
|
||||
export { TextBlinkComponent } from './components/TextBlinkComponent';
|
||||
export { SceneLoadTriggerComponent } from './components/SceneLoadTriggerComponent';
|
||||
|
||||
export {
|
||||
UILayoutComponent,
|
||||
UILayoutType,
|
||||
@@ -124,6 +127,8 @@ export { UILayoutSystem } from './systems/UILayoutSystem';
|
||||
export { UIInputSystem, type UIInputEvent } from './systems/UIInputSystem';
|
||||
export { UIAnimationSystem, UIEasing, type EasingFunction, type EasingName } from './systems/UIAnimationSystem';
|
||||
export { UIRenderDataProvider, type IUIRenderDataProvider } from './systems/UIRenderDataProvider';
|
||||
export { TextBlinkSystem } from './systems/TextBlinkSystem';
|
||||
export { SceneLoadTriggerSystem } from './systems/SceneLoadTriggerSystem';
|
||||
|
||||
// Systems - Render (ECS-compliant render systems)
|
||||
export {
|
||||
|
||||
162
packages/ui/src/systems/SceneLoadTriggerSystem.ts
Normal file
162
packages/ui/src/systems/SceneLoadTriggerSystem.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 场景加载触发系统
|
||||
* Scene Load Trigger System
|
||||
*
|
||||
* 处理 SceneLoadTriggerComponent,绑定 UIInteractable 点击事件到场景加载。
|
||||
* Processes SceneLoadTriggerComponent, binds UIInteractable click to scene loading.
|
||||
*/
|
||||
|
||||
import { Entity, EntitySystem, Matcher, ECSSystem, Core } from '@esengine/ecs-framework';
|
||||
import { SceneLoadTriggerComponent } from '../components/SceneLoadTriggerComponent';
|
||||
import { UIInteractableComponent } from '../components/UIInteractableComponent';
|
||||
|
||||
/**
|
||||
* 场景加载函数类型(与 RuntimeSceneManager.loadScene 兼容)
|
||||
* Scene load function type (compatible with RuntimeSceneManager.loadScene)
|
||||
*/
|
||||
type SceneLoadFunction = (sceneName: string) => Promise<void>;
|
||||
|
||||
/**
|
||||
* 场景管理器接口(最小化,避免循环依赖)
|
||||
* Scene manager interface (minimal, avoids circular dependency)
|
||||
*
|
||||
* 包含 IService 的 dispose 方法以兼容 ServiceContainer。
|
||||
* Includes IService's dispose method for ServiceContainer compatibility.
|
||||
*/
|
||||
interface ISceneManager {
|
||||
loadScene(sceneName: string): Promise<void>;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局场景管理器服务键
|
||||
* Global scene manager service key
|
||||
*
|
||||
* 使用 Symbol.for 确保与 BrowserRuntime 中注册的键一致。
|
||||
* Uses Symbol.for to match the key registered in BrowserRuntime.
|
||||
*/
|
||||
const GlobalSceneManagerKey = Symbol.for('@esengine/service:runtimeSceneManager');
|
||||
|
||||
/**
|
||||
* 场景加载触发系统
|
||||
* Scene Load Trigger System
|
||||
*
|
||||
* 自动将 SceneLoadTriggerComponent 的配置连接到 UIInteractable 的点击事件。
|
||||
* Automatically connects SceneLoadTriggerComponent config to UIInteractable click events.
|
||||
*/
|
||||
@ECSSystem('SceneLoadTrigger')
|
||||
export class SceneLoadTriggerSystem extends EntitySystem {
|
||||
private _sceneLoader: SceneLoadFunction | null = null;
|
||||
|
||||
constructor() {
|
||||
super(Matcher.empty().all(SceneLoadTriggerComponent, UIInteractableComponent));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置场景加载函数
|
||||
* Set scene load function
|
||||
*
|
||||
* 可以直接设置函数,或者系统会尝试从服务注册表获取 RuntimeSceneManager。
|
||||
* Can set function directly, or system will try to get RuntimeSceneManager from service registry.
|
||||
*/
|
||||
public setSceneLoader(loader: SceneLoadFunction): void {
|
||||
this._sceneLoader = loader;
|
||||
}
|
||||
|
||||
protected override process(entities: readonly Entity[]): void {
|
||||
// 如果没有设置场景加载器,尝试从服务注册表获取
|
||||
// If no scene loader set, try to get from service registry
|
||||
if (!this._sceneLoader) {
|
||||
this._tryGetSceneManager();
|
||||
}
|
||||
|
||||
for (const entity of entities) {
|
||||
const trigger = entity.getComponent(SceneLoadTriggerComponent);
|
||||
const interactable = entity.getComponent(UIInteractableComponent);
|
||||
|
||||
if (!trigger || !interactable) continue;
|
||||
if (!trigger.enabled || !trigger.targetScene) continue;
|
||||
|
||||
// 只绑定一次回调
|
||||
// Only bind callback once
|
||||
if (trigger._callbackBound) continue;
|
||||
|
||||
this._bindClickHandler(entity, trigger, interactable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试从全局服务获取场景管理器
|
||||
* Try to get scene manager from global services
|
||||
*/
|
||||
private _tryGetSceneManager(): void {
|
||||
try {
|
||||
// 从 Core.services 获取场景管理器
|
||||
// Get scene manager from Core.services
|
||||
// RuntimeSceneManager 实现了 IService 接口
|
||||
// RuntimeSceneManager implements IService interface
|
||||
const sceneManager = Core.services.tryResolve<ISceneManager>(GlobalSceneManagerKey);
|
||||
if (sceneManager?.loadScene) {
|
||||
this._sceneLoader = (sceneName: string) => sceneManager.loadScene(sceneName);
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略错误,保持 _sceneLoader 为 null
|
||||
// Ignore error, keep _sceneLoader as null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定点击处理器
|
||||
* Bind click handler
|
||||
*/
|
||||
private _bindClickHandler(
|
||||
entity: Entity,
|
||||
trigger: SceneLoadTriggerComponent,
|
||||
interactable: UIInteractableComponent
|
||||
): void {
|
||||
const targetScene = trigger.targetScene;
|
||||
|
||||
// 保存原有的 onClick(如果有)
|
||||
// Save original onClick (if any)
|
||||
const originalOnClick = interactable.onClick;
|
||||
|
||||
interactable.onClick = () => {
|
||||
// 调用原有回调
|
||||
// Call original callback
|
||||
originalOnClick?.();
|
||||
|
||||
// 检查是否启用
|
||||
// Check if enabled
|
||||
if (!trigger.enabled) return;
|
||||
|
||||
// 禁用(防止重复点击)
|
||||
// Disable (prevent double clicks)
|
||||
if (trigger.disableOnClick) {
|
||||
trigger.enabled = false;
|
||||
}
|
||||
|
||||
// 尝试获取场景加载器(可能在回调绑定后才注册)
|
||||
// Try to get scene loader (may be registered after callback binding)
|
||||
if (!this._sceneLoader) {
|
||||
this._tryGetSceneManager();
|
||||
}
|
||||
|
||||
// 加载场景
|
||||
// Load scene
|
||||
if (this._sceneLoader) {
|
||||
this._sceneLoader(targetScene).catch((error) => {
|
||||
console.error(`[SceneLoadTriggerSystem] Failed to load scene "${targetScene}":`, error);
|
||||
// 恢复启用状态
|
||||
// Restore enabled state
|
||||
if (trigger.disableOnClick) {
|
||||
trigger.enabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
// 静默处理:编辑器预览模式下场景切换不可用
|
||||
// Silent handling: scene switching not available in editor preview mode
|
||||
};
|
||||
|
||||
trigger._callbackBound = true;
|
||||
}
|
||||
}
|
||||
37
packages/ui/src/systems/TextBlinkSystem.ts
Normal file
37
packages/ui/src/systems/TextBlinkSystem.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 文本闪烁系统 - 实现 UI 元素的透明度脉冲动画
|
||||
*
|
||||
* Text Blink System - Implements alpha pulse animation for UI elements
|
||||
*/
|
||||
|
||||
import { Entity, EntitySystem, Matcher, Time } from '@esengine/ecs-framework';
|
||||
import { TextBlinkComponent } from '../components/TextBlinkComponent';
|
||||
import { UITransformComponent } from '../components/UITransformComponent';
|
||||
|
||||
/**
|
||||
* 处理 TextBlinkComponent,驱动 UI 元素的透明度动画。
|
||||
* 常用于 "TAP TO START" 等需要吸引注意力的文本效果。
|
||||
*
|
||||
* Processes TextBlinkComponent to drive UI element alpha animation.
|
||||
* Commonly used for attention-grabbing text effects like "TAP TO START".
|
||||
*/
|
||||
export class TextBlinkSystem extends EntitySystem {
|
||||
constructor() {
|
||||
super(Matcher.empty().all(TextBlinkComponent, UITransformComponent));
|
||||
}
|
||||
|
||||
protected override process(entities: readonly Entity[]): void {
|
||||
const deltaTime = Time.deltaTime;
|
||||
|
||||
for (const entity of entities) {
|
||||
if (!entity.enabled) continue;
|
||||
|
||||
const blink = entity.getComponent(TextBlinkComponent);
|
||||
const uiTransform = entity.getComponent(UITransformComponent);
|
||||
if (!blink || !uiTransform) continue;
|
||||
|
||||
blink.addTime(deltaTime);
|
||||
uiTransform.alpha = blink.calculateAlpha();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ export class UILayoutSystem extends EntitySystem {
|
||||
const identityMatrix: Matrix2D = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
|
||||
|
||||
for (const entity of rootEntities) {
|
||||
this.layoutEntity(entity, parentX, parentY, this.canvasWidth, this.canvasHeight, 1, identityMatrix);
|
||||
this.layoutEntity(entity, parentX, parentY, this.canvasWidth, this.canvasHeight, 1, identityMatrix, true, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,8 @@ export class UILayoutSystem extends EntitySystem {
|
||||
parentHeight: number,
|
||||
parentAlpha: number,
|
||||
parentMatrix: Matrix2D,
|
||||
parentVisible: boolean = true
|
||||
parentVisible: boolean = true,
|
||||
depth: number = 0
|
||||
): void {
|
||||
const transform = entity.getComponent(UITransformComponent);
|
||||
if (!transform) return;
|
||||
@@ -199,6 +200,12 @@ export class UILayoutSystem extends EntitySystem {
|
||||
// Calculate world visibility (if parent is invisible, children are also invisible)
|
||||
transform.worldVisible = parentVisible && transform.visible;
|
||||
|
||||
// 计算世界层内顺序(子元素总是渲染在父元素之上)
|
||||
// Calculate world order in layer (children always render on top of parents)
|
||||
// 公式:depth * 1000 + localOrderInLayer
|
||||
// Formula: depth * 1000 + localOrderInLayer
|
||||
transform.worldOrderInLayer = depth * 1000 + transform.orderInLayer;
|
||||
|
||||
// 使用矩阵乘法计算世界变换
|
||||
this.updateWorldMatrix(transform, parentMatrix);
|
||||
|
||||
@@ -215,7 +222,7 @@ export class UILayoutSystem extends EntitySystem {
|
||||
// 检查是否有布局组件
|
||||
const layout = entity.getComponent(UILayoutComponent);
|
||||
if (layout && layout.type !== UILayoutType.None) {
|
||||
this.layoutChildren(layout, transform, children);
|
||||
this.layoutChildren(layout, transform, children, depth + 1);
|
||||
} else {
|
||||
// 无布局组件,直接递归处理子元素
|
||||
for (const child of children) {
|
||||
@@ -227,7 +234,8 @@ export class UILayoutSystem extends EntitySystem {
|
||||
height,
|
||||
transform.worldAlpha,
|
||||
transform.localToWorldMatrix,
|
||||
transform.worldVisible
|
||||
transform.worldVisible,
|
||||
depth + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -240,7 +248,8 @@ export class UILayoutSystem extends EntitySystem {
|
||||
private layoutChildren(
|
||||
layout: UILayoutComponent,
|
||||
parentTransform: UITransformComponent,
|
||||
children: Entity[]
|
||||
children: Entity[],
|
||||
depth: number
|
||||
): void {
|
||||
const contentStartX = parentTransform.worldX + layout.paddingLeft;
|
||||
// Y-up 系统:worldY 是底部,顶部 = worldY + height
|
||||
@@ -252,13 +261,13 @@ export class UILayoutSystem extends EntitySystem {
|
||||
|
||||
switch (layout.type) {
|
||||
case UILayoutType.Horizontal:
|
||||
this.layoutHorizontal(layout, parentTransform, children, contentStartX, contentStartY, contentWidth, contentHeight);
|
||||
this.layoutHorizontal(layout, parentTransform, children, contentStartX, contentStartY, contentWidth, contentHeight, depth);
|
||||
break;
|
||||
case UILayoutType.Vertical:
|
||||
this.layoutVertical(layout, parentTransform, children, contentStartX, contentStartY, contentWidth, contentHeight);
|
||||
this.layoutVertical(layout, parentTransform, children, contentStartX, contentStartY, contentWidth, contentHeight, depth);
|
||||
break;
|
||||
case UILayoutType.Grid:
|
||||
this.layoutGrid(layout, parentTransform, children, contentStartX, contentStartY, contentWidth, contentHeight);
|
||||
this.layoutGrid(layout, parentTransform, children, contentStartX, contentStartY, contentWidth, contentHeight, depth);
|
||||
break;
|
||||
default:
|
||||
// 默认按正常方式递归(传递顶部 Y)
|
||||
@@ -270,7 +279,9 @@ export class UILayoutSystem extends EntitySystem {
|
||||
parentTransform.computedWidth,
|
||||
parentTransform.computedHeight,
|
||||
parentTransform.worldAlpha,
|
||||
parentTransform.localToWorldMatrix
|
||||
parentTransform.localToWorldMatrix,
|
||||
parentTransform.worldVisible,
|
||||
depth
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -287,7 +298,8 @@ export class UILayoutSystem extends EntitySystem {
|
||||
startX: number,
|
||||
startY: number,
|
||||
contentWidth: number,
|
||||
contentHeight: number
|
||||
contentHeight: number,
|
||||
depth: number
|
||||
): void {
|
||||
// 计算总子元素宽度
|
||||
const childSizes = children.map(child => {
|
||||
@@ -366,12 +378,14 @@ export class UILayoutSystem extends EntitySystem {
|
||||
childTransform.worldAlpha = parentTransform.worldAlpha * childTransform.alpha;
|
||||
// 传播世界可见性 | Propagate world visibility
|
||||
childTransform.worldVisible = parentTransform.worldVisible && childTransform.visible;
|
||||
// 计算世界层内顺序 | Calculate world order in layer
|
||||
childTransform.worldOrderInLayer = depth * 1000 + childTransform.orderInLayer;
|
||||
// 使用矩阵乘法计算世界旋转和缩放
|
||||
this.updateWorldMatrix(childTransform, parentTransform.localToWorldMatrix);
|
||||
childTransform.layoutDirty = false;
|
||||
|
||||
// 递归处理子元素的子元素
|
||||
this.processChildrenRecursive(child, childTransform);
|
||||
this.processChildrenRecursive(child, childTransform, depth);
|
||||
|
||||
offsetX += size.width + gap;
|
||||
}
|
||||
@@ -389,7 +403,8 @@ export class UILayoutSystem extends EntitySystem {
|
||||
startX: number,
|
||||
startY: number,
|
||||
contentWidth: number,
|
||||
contentHeight: number
|
||||
contentHeight: number,
|
||||
depth: number
|
||||
): void {
|
||||
// 计算总子元素高度
|
||||
const childSizes = children.map(child => {
|
||||
@@ -466,11 +481,13 @@ export class UILayoutSystem extends EntitySystem {
|
||||
childTransform.worldAlpha = parentTransform.worldAlpha * childTransform.alpha;
|
||||
// 传播世界可见性 | Propagate world visibility
|
||||
childTransform.worldVisible = parentTransform.worldVisible && childTransform.visible;
|
||||
// 计算世界层内顺序 | Calculate world order in layer
|
||||
childTransform.worldOrderInLayer = depth * 1000 + childTransform.orderInLayer;
|
||||
// 使用矩阵乘法计算世界旋转和缩放
|
||||
this.updateWorldMatrix(childTransform, parentTransform.localToWorldMatrix);
|
||||
childTransform.layoutDirty = false;
|
||||
|
||||
this.processChildrenRecursive(child, childTransform);
|
||||
this.processChildrenRecursive(child, childTransform, depth);
|
||||
|
||||
// 移动到下一个元素的顶部位置(向下 = Y 减小)
|
||||
currentTopY -= size.height + gap;
|
||||
@@ -489,7 +506,8 @@ export class UILayoutSystem extends EntitySystem {
|
||||
startX: number,
|
||||
startY: number,
|
||||
contentWidth: number,
|
||||
_contentHeight: number
|
||||
_contentHeight: number,
|
||||
depth: number
|
||||
): void {
|
||||
const columns = layout.columns;
|
||||
const gapX = layout.getHorizontalGap();
|
||||
@@ -524,11 +542,13 @@ export class UILayoutSystem extends EntitySystem {
|
||||
childTransform.worldAlpha = parentTransform.worldAlpha * childTransform.alpha;
|
||||
// 传播世界可见性 | Propagate world visibility
|
||||
childTransform.worldVisible = parentTransform.worldVisible && childTransform.visible;
|
||||
// 计算世界层内顺序 | Calculate world order in layer
|
||||
childTransform.worldOrderInLayer = depth * 1000 + childTransform.orderInLayer;
|
||||
// 使用矩阵乘法计算世界旋转和缩放
|
||||
this.updateWorldMatrix(childTransform, parentTransform.localToWorldMatrix);
|
||||
childTransform.layoutDirty = false;
|
||||
|
||||
this.processChildrenRecursive(child, childTransform);
|
||||
this.processChildrenRecursive(child, childTransform, depth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,7 +585,7 @@ export class UILayoutSystem extends EntitySystem {
|
||||
* 递归处理子元素
|
||||
* Recursively process children
|
||||
*/
|
||||
private processChildrenRecursive(entity: Entity, parentTransform: UITransformComponent): void {
|
||||
private processChildrenRecursive(entity: Entity, parentTransform: UITransformComponent, depth: number): void {
|
||||
const children = this.getUIChildren(entity);
|
||||
if (children.length === 0) return;
|
||||
|
||||
@@ -574,7 +594,7 @@ export class UILayoutSystem extends EntitySystem {
|
||||
|
||||
const layout = entity.getComponent(UILayoutComponent);
|
||||
if (layout && layout.type !== UILayoutType.None) {
|
||||
this.layoutChildren(layout, parentTransform, children);
|
||||
this.layoutChildren(layout, parentTransform, children, depth + 1);
|
||||
} else {
|
||||
for (const child of children) {
|
||||
this.layoutEntity(
|
||||
@@ -585,7 +605,8 @@ export class UILayoutSystem extends EntitySystem {
|
||||
parentTransform.computedHeight,
|
||||
parentTransform.worldAlpha,
|
||||
parentTransform.localToWorldMatrix,
|
||||
parentTransform.worldVisible
|
||||
parentTransform.worldVisible,
|
||||
depth + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,9 +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;
|
||||
// 使用排序层和层内顺序 | Use sorting layer and order in layer
|
||||
// 使用排序层和世界层内顺序 | Use sorting layer and world order in layer
|
||||
const sortingLayer = transform.sortingLayer;
|
||||
const orderInLayer = transform.orderInLayer;
|
||||
const orderInLayer = transform.worldOrderInLayer;
|
||||
// 使用 transform 的 pivot 作为旋转/缩放中心
|
||||
const pivotX = transform.pivotX;
|
||||
const pivotY = transform.pivotY;
|
||||
|
||||
@@ -55,9 +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;
|
||||
// 使用排序层和层内顺序 | Use sorting layer and order in layer
|
||||
// 使用排序层和世界层内顺序 | Use sorting layer and world order in layer
|
||||
const sortingLayer = transform.sortingLayer;
|
||||
const orderInLayer = transform.orderInLayer;
|
||||
const orderInLayer = transform.worldOrderInLayer;
|
||||
// 使用 transform 的 pivot 作为旋转/缩放中心
|
||||
const pivotX = transform.pivotX;
|
||||
const pivotY = transform.pivotY;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
import { EntitySystem, Matcher, Entity, ECSSystem } from '@esengine/ecs-framework';
|
||||
import { UITransformComponent } from '../../components/UITransformComponent';
|
||||
import { UIRenderComponent } from '../../components/UIRenderComponent';
|
||||
import { UIRenderComponent, UIRenderType } from '../../components/UIRenderComponent';
|
||||
import { UIButtonComponent } from '../../components/widgets/UIButtonComponent';
|
||||
import { UIProgressBarComponent } from '../../components/widgets/UIProgressBarComponent';
|
||||
import { UISliderComponent } from '../../components/widgets/UISliderComponent';
|
||||
@@ -68,9 +68,11 @@ export class UIRectRenderSystem extends EntitySystem {
|
||||
const alpha = transform.worldAlpha ?? transform.alpha;
|
||||
// 使用世界旋转(考虑父级旋转)
|
||||
const rotation = transform.worldRotation ?? transform.rotation;
|
||||
// 使用排序层和层内顺序 | Use sorting layer and order in layer
|
||||
// 使用排序层和世界层内顺序 | Use sorting layer and world order in layer
|
||||
const sortingLayer = transform.sortingLayer;
|
||||
const orderInLayer = transform.orderInLayer;
|
||||
// worldOrderInLayer 考虑了父子层级关系,确保子元素渲染在父元素之上
|
||||
// worldOrderInLayer considers parent-child hierarchy, ensuring children render on top of parents
|
||||
const orderInLayer = transform.worldOrderInLayer;
|
||||
// 使用 transform 的 pivot 作为旋转/缩放中心
|
||||
const pivotX = transform.pivotX;
|
||||
const pivotY = transform.pivotY;
|
||||
@@ -107,24 +109,56 @@ export class UIRectRenderSystem extends EntitySystem {
|
||||
const textureGuid = typeof render.textureGuid === 'string' ? render.textureGuid : undefined;
|
||||
const textureId = typeof render.textureGuid === 'number' ? render.textureGuid : undefined;
|
||||
|
||||
collector.addRect(
|
||||
renderX, renderY,
|
||||
width, height,
|
||||
render.textureTint,
|
||||
alpha,
|
||||
sortingLayer,
|
||||
orderInLayer,
|
||||
{
|
||||
rotation,
|
||||
pivotX,
|
||||
pivotY,
|
||||
textureId,
|
||||
textureGuid,
|
||||
uv: render.textureUV
|
||||
? [render.textureUV.u0, render.textureUV.v0, render.textureUV.u1, render.textureUV.v1]
|
||||
: undefined
|
||||
}
|
||||
);
|
||||
|
||||
// Handle nine-patch rendering
|
||||
// 处理九宫格渲染
|
||||
if (render.type === UIRenderType.NinePatch &&
|
||||
render.textureWidth > 0 &&
|
||||
render.textureHeight > 0) {
|
||||
// addNinePatch expects top-left corner coordinates
|
||||
// Y-up coordinate system: top = bottom + height
|
||||
// addNinePatch 期望左上角坐标
|
||||
// Y轴向上坐标系:顶部 = 底部 + 高度
|
||||
const topLeftX = x;
|
||||
const topLeftY = y + height;
|
||||
collector.addNinePatch(
|
||||
topLeftX, topLeftY,
|
||||
width, height,
|
||||
render.ninePatchMargins,
|
||||
render.textureWidth,
|
||||
render.textureHeight,
|
||||
render.textureTint,
|
||||
alpha,
|
||||
sortingLayer,
|
||||
orderInLayer,
|
||||
{
|
||||
rotation,
|
||||
textureId,
|
||||
textureGuid
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Standard image rendering
|
||||
// 标准图像渲染
|
||||
collector.addRect(
|
||||
renderX, renderY,
|
||||
width, height,
|
||||
render.textureTint,
|
||||
alpha,
|
||||
sortingLayer,
|
||||
orderInLayer,
|
||||
{
|
||||
rotation,
|
||||
pivotX,
|
||||
pivotY,
|
||||
textureId,
|
||||
textureGuid,
|
||||
uv: render.textureUV
|
||||
? [render.textureUV.u0, render.textureUV.v0, render.textureUV.u1, render.textureUV.v1]
|
||||
: undefined
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
// Render background color if fill is enabled
|
||||
// 如果启用填充,渲染背景颜色
|
||||
|
||||
@@ -56,7 +56,7 @@ export interface UIRenderPrimitive {
|
||||
rotation: number;
|
||||
/** Pivot/Origin X (0-1, 0=left, 0.5=center, 1=right) | 锚点 X (0-1, 0=左, 0.5=中心, 1=右) */
|
||||
pivotX: number;
|
||||
/** Pivot/Origin Y (0-1, 0=top, 0.5=center, 1=bottom) | 锚点 Y (0-1, 0=上, 0.5=中心, 1=下) */
|
||||
/** Pivot/Origin Y (0-1, 0=bottom, 0.5=center, 1=top) in Y-up system | 锚点 Y (0-1, 0=下, 0.5=中心, 1=上) Y轴向上坐标系 */
|
||||
pivotY: number;
|
||||
/** Packed color (0xAABBGGRR) | 打包颜色 */
|
||||
color: number;
|
||||
@@ -171,6 +171,136 @@ export class UIRenderCollector {
|
||||
this.cache = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a nine-patch (9-slice) primitive
|
||||
* 添加九宫格原语
|
||||
*
|
||||
* Nine-patch divides the texture into 9 regions:
|
||||
* - Corners: Keep original size
|
||||
* - Edges: Stretch in one direction
|
||||
* - Center: Stretches in both directions
|
||||
*
|
||||
* 九宫格将纹理分为 9 个区域:
|
||||
* - 角落:保持原始尺寸
|
||||
* - 边缘:单向拉伸
|
||||
* - 中心:双向拉伸
|
||||
*
|
||||
* @param x - X position | X 坐标
|
||||
* @param y - Y position | Y 坐标
|
||||
* @param width - Target width | 目标宽度
|
||||
* @param height - Target height | 目标高度
|
||||
* @param margins - Nine-patch margins [top, right, bottom, left] | 九宫格边距
|
||||
* @param textureWidth - Source texture width | 源纹理宽度
|
||||
* @param textureHeight - Source texture height | 源纹理高度
|
||||
* @param color - Tint color | 着色颜色
|
||||
* @param alpha - Alpha value | 透明度
|
||||
* @param sortingLayer - Sorting layer | 排序层
|
||||
* @param orderInLayer - Order in layer | 层内顺序
|
||||
* @param options - Additional options | 额外选项
|
||||
*/
|
||||
addNinePatch(
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
margins: [number, number, number, number],
|
||||
textureWidth: number,
|
||||
textureHeight: number,
|
||||
color: number,
|
||||
alpha: number,
|
||||
sortingLayer: string,
|
||||
orderInLayer: number,
|
||||
options?: {
|
||||
rotation?: number;
|
||||
textureId?: number;
|
||||
textureGuid?: string;
|
||||
}
|
||||
): void {
|
||||
const [marginTop, marginRight, marginBottom, marginLeft] = margins;
|
||||
|
||||
// Ensure minimum size to avoid negative dimensions
|
||||
// 确保最小尺寸以避免负尺寸
|
||||
const minWidth = marginLeft + marginRight;
|
||||
const minHeight = marginTop + marginBottom;
|
||||
const targetWidth = Math.max(width, minWidth);
|
||||
const targetHeight = Math.max(height, minHeight);
|
||||
|
||||
// Calculate center dimensions
|
||||
// 计算中心区域尺寸
|
||||
const centerWidth = targetWidth - marginLeft - marginRight;
|
||||
const centerHeight = targetHeight - marginTop - marginBottom;
|
||||
|
||||
// Source texture UV boundaries (normalized)
|
||||
// 源纹理 UV 边界(归一化)
|
||||
const uvLeft = marginLeft / textureWidth;
|
||||
const uvRight = (textureWidth - marginRight) / textureWidth;
|
||||
const uvTop = marginTop / textureHeight;
|
||||
const uvBottom = (textureHeight - marginBottom) / textureHeight;
|
||||
|
||||
// Common options for all patches
|
||||
// 所有 patch 的公共选项
|
||||
// Note: pivotY=1 means position is top-left corner (Y-up coordinate system)
|
||||
// 注意:pivotY=1 表示位置是左上角(Y轴向上坐标系)
|
||||
const baseOptions = {
|
||||
rotation: options?.rotation ?? 0,
|
||||
pivotX: 0,
|
||||
pivotY: 1,
|
||||
textureId: options?.textureId,
|
||||
textureGuid: options?.textureGuid
|
||||
};
|
||||
|
||||
// Helper to add a patch with specific UVs
|
||||
// 辅助函数:添加具有特定 UV 的 patch
|
||||
const addPatch = (
|
||||
px: number,
|
||||
py: number,
|
||||
pw: number,
|
||||
ph: number,
|
||||
u0: number,
|
||||
v0: number,
|
||||
u1: number,
|
||||
v1: number
|
||||
) => {
|
||||
if (pw <= 0 || ph <= 0) return;
|
||||
this.addRect(px, py, pw, ph, color, alpha, sortingLayer, orderInLayer, {
|
||||
...baseOptions,
|
||||
uv: [u0, v0, u1, v1]
|
||||
});
|
||||
};
|
||||
|
||||
// Y-up coordinate system: y decreases as we go down
|
||||
// Y轴向上坐标系:向下移动时 y 减小
|
||||
// (x, y) is top-left corner, patches extend downward (negative y direction)
|
||||
// (x, y) 是左上角,patch 向下延伸(y 减小方向)
|
||||
|
||||
// Top-left corner | 左上角
|
||||
addPatch(x, y, marginLeft, marginTop, 0, 0, uvLeft, uvTop);
|
||||
|
||||
// Top edge | 顶边
|
||||
addPatch(x + marginLeft, y, centerWidth, marginTop, uvLeft, 0, uvRight, uvTop);
|
||||
|
||||
// Top-right corner | 右上角
|
||||
addPatch(x + marginLeft + centerWidth, y, marginRight, marginTop, uvRight, 0, 1, uvTop);
|
||||
|
||||
// Left edge | 左边 (move down = subtract y)
|
||||
addPatch(x, y - marginTop, marginLeft, centerHeight, 0, uvTop, uvLeft, uvBottom);
|
||||
|
||||
// Center | 中心
|
||||
addPatch(x + marginLeft, y - marginTop, centerWidth, centerHeight, uvLeft, uvTop, uvRight, uvBottom);
|
||||
|
||||
// Right edge | 右边
|
||||
addPatch(x + marginLeft + centerWidth, y - marginTop, marginRight, centerHeight, uvRight, uvTop, 1, uvBottom);
|
||||
|
||||
// Bottom-left corner | 左下角
|
||||
addPatch(x, y - marginTop - centerHeight, marginLeft, marginBottom, 0, uvBottom, uvLeft, 1);
|
||||
|
||||
// Bottom edge | 底边
|
||||
addPatch(x + marginLeft, y - marginTop - centerHeight, centerWidth, marginBottom, uvLeft, uvBottom, uvRight, 1);
|
||||
|
||||
// Bottom-right corner | 右下角
|
||||
addPatch(x + marginLeft + centerWidth, y - marginTop - centerHeight, marginRight, marginBottom, uvRight, uvBottom, 1, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get render data
|
||||
* 获取渲染数据
|
||||
|
||||
@@ -56,9 +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;
|
||||
// 使用排序层和层内顺序 | Use sorting layer and order in layer
|
||||
// 使用排序层和世界层内顺序 | Use sorting layer and world order in layer
|
||||
const sortingLayer = transform.sortingLayer;
|
||||
const orderInLayer = transform.orderInLayer;
|
||||
const orderInLayer = transform.worldOrderInLayer;
|
||||
// 使用 transform 的 pivot 计算位置
|
||||
const pivotX = transform.pivotX;
|
||||
const pivotY = transform.pivotY;
|
||||
|
||||
@@ -55,9 +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;
|
||||
// 使用排序层和层内顺序 | Use sorting layer and order in layer
|
||||
// 使用排序层和世界层内顺序 | Use sorting layer and world order in layer
|
||||
const sortingLayer = transform.sortingLayer;
|
||||
const orderInLayer = transform.orderInLayer;
|
||||
const orderInLayer = transform.worldOrderInLayer;
|
||||
// 使用 transform 的 pivot 计算中心位置
|
||||
const pivotX = transform.pivotX;
|
||||
const pivotY = transform.pivotY;
|
||||
|
||||
@@ -112,9 +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;
|
||||
// 使用排序层和层内顺序 | Use sorting layer and order in layer
|
||||
// 使用排序层和世界层内顺序 | Use sorting layer and world order in layer
|
||||
const sortingLayer = transform.sortingLayer;
|
||||
const orderInLayer = transform.orderInLayer;
|
||||
const orderInLayer = transform.worldOrderInLayer;
|
||||
// 使用 transform 的 pivot 作为旋转/缩放中心
|
||||
const pivotX = transform.pivotX;
|
||||
const pivotY = transform.pivotY;
|
||||
|
||||
Reference in New Issue
Block a user