feat(ui): 添加场景切换和文本闪烁组件
新增组件: - SceneLoadTriggerComponent: 场景切换触发器 - TextBlinkComponent: 文本闪烁效果 新增系统: - SceneLoadTriggerSystem: 处理场景切换逻辑 - TextBlinkSystem: 处理文本闪烁动画 其他改进: - UIRuntimeModule 适配新组件注册接口 - UI 渲染系统优化
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
import type { IScene } from '@esengine/ecs-framework';
|
import type { IScene, IComponentRegistry } from '@esengine/ecs-framework';
|
||||||
import { ComponentRegistry } from '@esengine/ecs-framework';
|
|
||||||
import type { IRuntimeModule, IRuntimePlugin, ModuleManifest, SystemContext } from '@esengine/engine-core';
|
import type { IRuntimeModule, IRuntimePlugin, ModuleManifest, SystemContext } from '@esengine/engine-core';
|
||||||
import { EngineBridgeToken } from '@esengine/ecs-engine-bindgen';
|
import { EngineBridgeToken } from '@esengine/ecs-engine-bindgen';
|
||||||
|
|
||||||
@@ -14,10 +13,14 @@ import {
|
|||||||
UISliderComponent,
|
UISliderComponent,
|
||||||
UIScrollViewComponent
|
UIScrollViewComponent
|
||||||
} from './components';
|
} from './components';
|
||||||
|
import { TextBlinkComponent } from './components/TextBlinkComponent';
|
||||||
|
import { SceneLoadTriggerComponent } from './components/SceneLoadTriggerComponent';
|
||||||
import { UILayoutSystem } from './systems/UILayoutSystem';
|
import { UILayoutSystem } from './systems/UILayoutSystem';
|
||||||
import { UIInputSystem } from './systems/UIInputSystem';
|
import { UIInputSystem } from './systems/UIInputSystem';
|
||||||
import { UIAnimationSystem } from './systems/UIAnimationSystem';
|
import { UIAnimationSystem } from './systems/UIAnimationSystem';
|
||||||
import { UIRenderDataProvider } from './systems/UIRenderDataProvider';
|
import { UIRenderDataProvider } from './systems/UIRenderDataProvider';
|
||||||
|
import { TextBlinkSystem } from './systems/TextBlinkSystem';
|
||||||
|
import { SceneLoadTriggerSystem } from './systems/SceneLoadTriggerSystem';
|
||||||
import {
|
import {
|
||||||
UIRenderBeginSystem,
|
UIRenderBeginSystem,
|
||||||
UIRectRenderSystem,
|
UIRectRenderSystem,
|
||||||
@@ -43,7 +46,7 @@ export {
|
|||||||
} from './tokens';
|
} from './tokens';
|
||||||
|
|
||||||
class UIRuntimeModule implements IRuntimeModule {
|
class UIRuntimeModule implements IRuntimeModule {
|
||||||
registerComponents(registry: typeof ComponentRegistry): void {
|
registerComponents(registry: IComponentRegistry): void {
|
||||||
registry.register(UITransformComponent);
|
registry.register(UITransformComponent);
|
||||||
registry.register(UIRenderComponent);
|
registry.register(UIRenderComponent);
|
||||||
registry.register(UIInteractableComponent);
|
registry.register(UIInteractableComponent);
|
||||||
@@ -53,6 +56,8 @@ class UIRuntimeModule implements IRuntimeModule {
|
|||||||
registry.register(UIProgressBarComponent);
|
registry.register(UIProgressBarComponent);
|
||||||
registry.register(UISliderComponent);
|
registry.register(UISliderComponent);
|
||||||
registry.register(UIScrollViewComponent);
|
registry.register(UIScrollViewComponent);
|
||||||
|
registry.register(TextBlinkComponent);
|
||||||
|
registry.register(SceneLoadTriggerComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
createSystems(scene: IScene, context: SystemContext): void {
|
createSystems(scene: IScene, context: SystemContext): void {
|
||||||
@@ -65,6 +70,14 @@ class UIRuntimeModule implements IRuntimeModule {
|
|||||||
const animationSystem = new UIAnimationSystem();
|
const animationSystem = new UIAnimationSystem();
|
||||||
scene.addSystem(animationSystem);
|
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();
|
const renderBeginSystem = new UIRenderBeginSystem();
|
||||||
scene.addSystem(renderBeginSystem);
|
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]
|
* 九宫格边距 [top, right, bottom, left]
|
||||||
* Nine-patch margins
|
* 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];
|
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 =====
|
// ===== 边框 Border =====
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -275,6 +275,15 @@ export class UITransformComponent extends Component implements ISortable {
|
|||||||
*/
|
*/
|
||||||
public worldScaleY: number = 1;
|
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 计算)
|
* 本地到世界的 2D 变换矩阵(只读,由 UILayoutSystem 计算)
|
||||||
* Local to world 2D transformation matrix (readonly, computed by UILayoutSystem)
|
* Local to world 2D transformation matrix (readonly, computed by UILayoutSystem)
|
||||||
|
|||||||
@@ -88,6 +88,9 @@ export {
|
|||||||
type UIFontWeight
|
type UIFontWeight
|
||||||
} from './components/UITextComponent';
|
} from './components/UITextComponent';
|
||||||
|
|
||||||
|
export { TextBlinkComponent } from './components/TextBlinkComponent';
|
||||||
|
export { SceneLoadTriggerComponent } from './components/SceneLoadTriggerComponent';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
UILayoutComponent,
|
UILayoutComponent,
|
||||||
UILayoutType,
|
UILayoutType,
|
||||||
@@ -124,6 +127,8 @@ export { UILayoutSystem } from './systems/UILayoutSystem';
|
|||||||
export { UIInputSystem, type UIInputEvent } from './systems/UIInputSystem';
|
export { UIInputSystem, type UIInputEvent } from './systems/UIInputSystem';
|
||||||
export { UIAnimationSystem, UIEasing, type EasingFunction, type EasingName } from './systems/UIAnimationSystem';
|
export { UIAnimationSystem, UIEasing, type EasingFunction, type EasingName } from './systems/UIAnimationSystem';
|
||||||
export { UIRenderDataProvider, type IUIRenderDataProvider } from './systems/UIRenderDataProvider';
|
export { UIRenderDataProvider, type IUIRenderDataProvider } from './systems/UIRenderDataProvider';
|
||||||
|
export { TextBlinkSystem } from './systems/TextBlinkSystem';
|
||||||
|
export { SceneLoadTriggerSystem } from './systems/SceneLoadTriggerSystem';
|
||||||
|
|
||||||
// Systems - Render (ECS-compliant render systems)
|
// Systems - Render (ECS-compliant render systems)
|
||||||
export {
|
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 };
|
const identityMatrix: Matrix2D = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
|
||||||
|
|
||||||
for (const entity of rootEntities) {
|
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,
|
parentHeight: number,
|
||||||
parentAlpha: number,
|
parentAlpha: number,
|
||||||
parentMatrix: Matrix2D,
|
parentMatrix: Matrix2D,
|
||||||
parentVisible: boolean = true
|
parentVisible: boolean = true,
|
||||||
|
depth: number = 0
|
||||||
): void {
|
): void {
|
||||||
const transform = entity.getComponent(UITransformComponent);
|
const transform = entity.getComponent(UITransformComponent);
|
||||||
if (!transform) return;
|
if (!transform) return;
|
||||||
@@ -199,6 +200,12 @@ export class UILayoutSystem extends EntitySystem {
|
|||||||
// Calculate world visibility (if parent is invisible, children are also invisible)
|
// Calculate world visibility (if parent is invisible, children are also invisible)
|
||||||
transform.worldVisible = parentVisible && transform.visible;
|
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);
|
this.updateWorldMatrix(transform, parentMatrix);
|
||||||
|
|
||||||
@@ -215,7 +222,7 @@ export class UILayoutSystem extends EntitySystem {
|
|||||||
// 检查是否有布局组件
|
// 检查是否有布局组件
|
||||||
const layout = entity.getComponent(UILayoutComponent);
|
const layout = entity.getComponent(UILayoutComponent);
|
||||||
if (layout && layout.type !== UILayoutType.None) {
|
if (layout && layout.type !== UILayoutType.None) {
|
||||||
this.layoutChildren(layout, transform, children);
|
this.layoutChildren(layout, transform, children, depth + 1);
|
||||||
} else {
|
} else {
|
||||||
// 无布局组件,直接递归处理子元素
|
// 无布局组件,直接递归处理子元素
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
@@ -227,7 +234,8 @@ export class UILayoutSystem extends EntitySystem {
|
|||||||
height,
|
height,
|
||||||
transform.worldAlpha,
|
transform.worldAlpha,
|
||||||
transform.localToWorldMatrix,
|
transform.localToWorldMatrix,
|
||||||
transform.worldVisible
|
transform.worldVisible,
|
||||||
|
depth + 1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,7 +248,8 @@ export class UILayoutSystem extends EntitySystem {
|
|||||||
private layoutChildren(
|
private layoutChildren(
|
||||||
layout: UILayoutComponent,
|
layout: UILayoutComponent,
|
||||||
parentTransform: UITransformComponent,
|
parentTransform: UITransformComponent,
|
||||||
children: Entity[]
|
children: Entity[],
|
||||||
|
depth: number
|
||||||
): void {
|
): void {
|
||||||
const contentStartX = parentTransform.worldX + layout.paddingLeft;
|
const contentStartX = parentTransform.worldX + layout.paddingLeft;
|
||||||
// Y-up 系统:worldY 是底部,顶部 = worldY + height
|
// Y-up 系统:worldY 是底部,顶部 = worldY + height
|
||||||
@@ -252,13 +261,13 @@ export class UILayoutSystem extends EntitySystem {
|
|||||||
|
|
||||||
switch (layout.type) {
|
switch (layout.type) {
|
||||||
case UILayoutType.Horizontal:
|
case UILayoutType.Horizontal:
|
||||||
this.layoutHorizontal(layout, parentTransform, children, contentStartX, contentStartY, contentWidth, contentHeight);
|
this.layoutHorizontal(layout, parentTransform, children, contentStartX, contentStartY, contentWidth, contentHeight, depth);
|
||||||
break;
|
break;
|
||||||
case UILayoutType.Vertical:
|
case UILayoutType.Vertical:
|
||||||
this.layoutVertical(layout, parentTransform, children, contentStartX, contentStartY, contentWidth, contentHeight);
|
this.layoutVertical(layout, parentTransform, children, contentStartX, contentStartY, contentWidth, contentHeight, depth);
|
||||||
break;
|
break;
|
||||||
case UILayoutType.Grid:
|
case UILayoutType.Grid:
|
||||||
this.layoutGrid(layout, parentTransform, children, contentStartX, contentStartY, contentWidth, contentHeight);
|
this.layoutGrid(layout, parentTransform, children, contentStartX, contentStartY, contentWidth, contentHeight, depth);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// 默认按正常方式递归(传递顶部 Y)
|
// 默认按正常方式递归(传递顶部 Y)
|
||||||
@@ -270,7 +279,9 @@ export class UILayoutSystem extends EntitySystem {
|
|||||||
parentTransform.computedWidth,
|
parentTransform.computedWidth,
|
||||||
parentTransform.computedHeight,
|
parentTransform.computedHeight,
|
||||||
parentTransform.worldAlpha,
|
parentTransform.worldAlpha,
|
||||||
parentTransform.localToWorldMatrix
|
parentTransform.localToWorldMatrix,
|
||||||
|
parentTransform.worldVisible,
|
||||||
|
depth
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,7 +298,8 @@ export class UILayoutSystem extends EntitySystem {
|
|||||||
startX: number,
|
startX: number,
|
||||||
startY: number,
|
startY: number,
|
||||||
contentWidth: number,
|
contentWidth: number,
|
||||||
contentHeight: number
|
contentHeight: number,
|
||||||
|
depth: number
|
||||||
): void {
|
): void {
|
||||||
// 计算总子元素宽度
|
// 计算总子元素宽度
|
||||||
const childSizes = children.map(child => {
|
const childSizes = children.map(child => {
|
||||||
@@ -366,12 +378,14 @@ export class UILayoutSystem extends EntitySystem {
|
|||||||
childTransform.worldAlpha = parentTransform.worldAlpha * childTransform.alpha;
|
childTransform.worldAlpha = parentTransform.worldAlpha * childTransform.alpha;
|
||||||
// 传播世界可见性 | Propagate world visibility
|
// 传播世界可见性 | Propagate world visibility
|
||||||
childTransform.worldVisible = parentTransform.worldVisible && childTransform.visible;
|
childTransform.worldVisible = parentTransform.worldVisible && childTransform.visible;
|
||||||
|
// 计算世界层内顺序 | Calculate world order in layer
|
||||||
|
childTransform.worldOrderInLayer = depth * 1000 + childTransform.orderInLayer;
|
||||||
// 使用矩阵乘法计算世界旋转和缩放
|
// 使用矩阵乘法计算世界旋转和缩放
|
||||||
this.updateWorldMatrix(childTransform, parentTransform.localToWorldMatrix);
|
this.updateWorldMatrix(childTransform, parentTransform.localToWorldMatrix);
|
||||||
childTransform.layoutDirty = false;
|
childTransform.layoutDirty = false;
|
||||||
|
|
||||||
// 递归处理子元素的子元素
|
// 递归处理子元素的子元素
|
||||||
this.processChildrenRecursive(child, childTransform);
|
this.processChildrenRecursive(child, childTransform, depth);
|
||||||
|
|
||||||
offsetX += size.width + gap;
|
offsetX += size.width + gap;
|
||||||
}
|
}
|
||||||
@@ -389,7 +403,8 @@ export class UILayoutSystem extends EntitySystem {
|
|||||||
startX: number,
|
startX: number,
|
||||||
startY: number,
|
startY: number,
|
||||||
contentWidth: number,
|
contentWidth: number,
|
||||||
contentHeight: number
|
contentHeight: number,
|
||||||
|
depth: number
|
||||||
): void {
|
): void {
|
||||||
// 计算总子元素高度
|
// 计算总子元素高度
|
||||||
const childSizes = children.map(child => {
|
const childSizes = children.map(child => {
|
||||||
@@ -466,11 +481,13 @@ export class UILayoutSystem extends EntitySystem {
|
|||||||
childTransform.worldAlpha = parentTransform.worldAlpha * childTransform.alpha;
|
childTransform.worldAlpha = parentTransform.worldAlpha * childTransform.alpha;
|
||||||
// 传播世界可见性 | Propagate world visibility
|
// 传播世界可见性 | Propagate world visibility
|
||||||
childTransform.worldVisible = parentTransform.worldVisible && childTransform.visible;
|
childTransform.worldVisible = parentTransform.worldVisible && childTransform.visible;
|
||||||
|
// 计算世界层内顺序 | Calculate world order in layer
|
||||||
|
childTransform.worldOrderInLayer = depth * 1000 + childTransform.orderInLayer;
|
||||||
// 使用矩阵乘法计算世界旋转和缩放
|
// 使用矩阵乘法计算世界旋转和缩放
|
||||||
this.updateWorldMatrix(childTransform, parentTransform.localToWorldMatrix);
|
this.updateWorldMatrix(childTransform, parentTransform.localToWorldMatrix);
|
||||||
childTransform.layoutDirty = false;
|
childTransform.layoutDirty = false;
|
||||||
|
|
||||||
this.processChildrenRecursive(child, childTransform);
|
this.processChildrenRecursive(child, childTransform, depth);
|
||||||
|
|
||||||
// 移动到下一个元素的顶部位置(向下 = Y 减小)
|
// 移动到下一个元素的顶部位置(向下 = Y 减小)
|
||||||
currentTopY -= size.height + gap;
|
currentTopY -= size.height + gap;
|
||||||
@@ -489,7 +506,8 @@ export class UILayoutSystem extends EntitySystem {
|
|||||||
startX: number,
|
startX: number,
|
||||||
startY: number,
|
startY: number,
|
||||||
contentWidth: number,
|
contentWidth: number,
|
||||||
_contentHeight: number
|
_contentHeight: number,
|
||||||
|
depth: number
|
||||||
): void {
|
): void {
|
||||||
const columns = layout.columns;
|
const columns = layout.columns;
|
||||||
const gapX = layout.getHorizontalGap();
|
const gapX = layout.getHorizontalGap();
|
||||||
@@ -524,11 +542,13 @@ export class UILayoutSystem extends EntitySystem {
|
|||||||
childTransform.worldAlpha = parentTransform.worldAlpha * childTransform.alpha;
|
childTransform.worldAlpha = parentTransform.worldAlpha * childTransform.alpha;
|
||||||
// 传播世界可见性 | Propagate world visibility
|
// 传播世界可见性 | Propagate world visibility
|
||||||
childTransform.worldVisible = parentTransform.worldVisible && childTransform.visible;
|
childTransform.worldVisible = parentTransform.worldVisible && childTransform.visible;
|
||||||
|
// 计算世界层内顺序 | Calculate world order in layer
|
||||||
|
childTransform.worldOrderInLayer = depth * 1000 + childTransform.orderInLayer;
|
||||||
// 使用矩阵乘法计算世界旋转和缩放
|
// 使用矩阵乘法计算世界旋转和缩放
|
||||||
this.updateWorldMatrix(childTransform, parentTransform.localToWorldMatrix);
|
this.updateWorldMatrix(childTransform, parentTransform.localToWorldMatrix);
|
||||||
childTransform.layoutDirty = false;
|
childTransform.layoutDirty = false;
|
||||||
|
|
||||||
this.processChildrenRecursive(child, childTransform);
|
this.processChildrenRecursive(child, childTransform, depth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -565,7 +585,7 @@ export class UILayoutSystem extends EntitySystem {
|
|||||||
* 递归处理子元素
|
* 递归处理子元素
|
||||||
* Recursively process children
|
* Recursively process children
|
||||||
*/
|
*/
|
||||||
private processChildrenRecursive(entity: Entity, parentTransform: UITransformComponent): void {
|
private processChildrenRecursive(entity: Entity, parentTransform: UITransformComponent, depth: number): void {
|
||||||
const children = this.getUIChildren(entity);
|
const children = this.getUIChildren(entity);
|
||||||
if (children.length === 0) return;
|
if (children.length === 0) return;
|
||||||
|
|
||||||
@@ -574,7 +594,7 @@ export class UILayoutSystem extends EntitySystem {
|
|||||||
|
|
||||||
const layout = entity.getComponent(UILayoutComponent);
|
const layout = entity.getComponent(UILayoutComponent);
|
||||||
if (layout && layout.type !== UILayoutType.None) {
|
if (layout && layout.type !== UILayoutType.None) {
|
||||||
this.layoutChildren(layout, parentTransform, children);
|
this.layoutChildren(layout, parentTransform, children, depth + 1);
|
||||||
} else {
|
} else {
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
this.layoutEntity(
|
this.layoutEntity(
|
||||||
@@ -585,7 +605,8 @@ export class UILayoutSystem extends EntitySystem {
|
|||||||
parentTransform.computedHeight,
|
parentTransform.computedHeight,
|
||||||
parentTransform.worldAlpha,
|
parentTransform.worldAlpha,
|
||||||
parentTransform.localToWorldMatrix,
|
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 width = (transform.computedWidth ?? transform.width) * scaleX;
|
||||||
const height = (transform.computedHeight ?? transform.height) * scaleY;
|
const height = (transform.computedHeight ?? transform.height) * scaleY;
|
||||||
const alpha = transform.worldAlpha ?? transform.alpha;
|
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 sortingLayer = transform.sortingLayer;
|
||||||
const orderInLayer = transform.orderInLayer;
|
const orderInLayer = transform.worldOrderInLayer;
|
||||||
// 使用 transform 的 pivot 作为旋转/缩放中心
|
// 使用 transform 的 pivot 作为旋转/缩放中心
|
||||||
const pivotX = transform.pivotX;
|
const pivotX = transform.pivotX;
|
||||||
const pivotY = transform.pivotY;
|
const pivotY = transform.pivotY;
|
||||||
|
|||||||
@@ -55,9 +55,9 @@ export class UIProgressBarRenderSystem extends EntitySystem {
|
|||||||
const width = (transform.computedWidth ?? transform.width) * scaleX;
|
const width = (transform.computedWidth ?? transform.width) * scaleX;
|
||||||
const height = (transform.computedHeight ?? transform.height) * scaleY;
|
const height = (transform.computedHeight ?? transform.height) * scaleY;
|
||||||
const alpha = transform.worldAlpha ?? transform.alpha;
|
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 sortingLayer = transform.sortingLayer;
|
||||||
const orderInLayer = transform.orderInLayer;
|
const orderInLayer = transform.worldOrderInLayer;
|
||||||
// 使用 transform 的 pivot 作为旋转/缩放中心
|
// 使用 transform 的 pivot 作为旋转/缩放中心
|
||||||
const pivotX = transform.pivotX;
|
const pivotX = transform.pivotX;
|
||||||
const pivotY = transform.pivotY;
|
const pivotY = transform.pivotY;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
import { EntitySystem, Matcher, Entity, ECSSystem } from '@esengine/ecs-framework';
|
import { EntitySystem, Matcher, Entity, ECSSystem } from '@esengine/ecs-framework';
|
||||||
import { UITransformComponent } from '../../components/UITransformComponent';
|
import { UITransformComponent } from '../../components/UITransformComponent';
|
||||||
import { UIRenderComponent } from '../../components/UIRenderComponent';
|
import { UIRenderComponent, UIRenderType } from '../../components/UIRenderComponent';
|
||||||
import { UIButtonComponent } from '../../components/widgets/UIButtonComponent';
|
import { UIButtonComponent } from '../../components/widgets/UIButtonComponent';
|
||||||
import { UIProgressBarComponent } from '../../components/widgets/UIProgressBarComponent';
|
import { UIProgressBarComponent } from '../../components/widgets/UIProgressBarComponent';
|
||||||
import { UISliderComponent } from '../../components/widgets/UISliderComponent';
|
import { UISliderComponent } from '../../components/widgets/UISliderComponent';
|
||||||
@@ -68,9 +68,11 @@ export class UIRectRenderSystem extends EntitySystem {
|
|||||||
const alpha = transform.worldAlpha ?? transform.alpha;
|
const alpha = transform.worldAlpha ?? transform.alpha;
|
||||||
// 使用世界旋转(考虑父级旋转)
|
// 使用世界旋转(考虑父级旋转)
|
||||||
const rotation = transform.worldRotation ?? transform.rotation;
|
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 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 作为旋转/缩放中心
|
// 使用 transform 的 pivot 作为旋转/缩放中心
|
||||||
const pivotX = transform.pivotX;
|
const pivotX = transform.pivotX;
|
||||||
const pivotY = transform.pivotY;
|
const pivotY = transform.pivotY;
|
||||||
@@ -107,24 +109,56 @@ export class UIRectRenderSystem extends EntitySystem {
|
|||||||
const textureGuid = typeof render.textureGuid === 'string' ? render.textureGuid : undefined;
|
const textureGuid = typeof render.textureGuid === 'string' ? render.textureGuid : undefined;
|
||||||
const textureId = typeof render.textureGuid === 'number' ? render.textureGuid : undefined;
|
const textureId = typeof render.textureGuid === 'number' ? render.textureGuid : undefined;
|
||||||
|
|
||||||
collector.addRect(
|
|
||||||
renderX, renderY,
|
// Handle nine-patch rendering
|
||||||
width, height,
|
// 处理九宫格渲染
|
||||||
render.textureTint,
|
if (render.type === UIRenderType.NinePatch &&
|
||||||
alpha,
|
render.textureWidth > 0 &&
|
||||||
sortingLayer,
|
render.textureHeight > 0) {
|
||||||
orderInLayer,
|
// addNinePatch expects top-left corner coordinates
|
||||||
{
|
// Y-up coordinate system: top = bottom + height
|
||||||
rotation,
|
// addNinePatch 期望左上角坐标
|
||||||
pivotX,
|
// Y轴向上坐标系:顶部 = 底部 + 高度
|
||||||
pivotY,
|
const topLeftX = x;
|
||||||
textureId,
|
const topLeftY = y + height;
|
||||||
textureGuid,
|
collector.addNinePatch(
|
||||||
uv: render.textureUV
|
topLeftX, topLeftY,
|
||||||
? [render.textureUV.u0, render.textureUV.v0, render.textureUV.u1, render.textureUV.v1]
|
width, height,
|
||||||
: undefined
|
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
|
// Render background color if fill is enabled
|
||||||
// 如果启用填充,渲染背景颜色
|
// 如果启用填充,渲染背景颜色
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export interface UIRenderPrimitive {
|
|||||||
rotation: number;
|
rotation: number;
|
||||||
/** Pivot/Origin X (0-1, 0=left, 0.5=center, 1=right) | 锚点 X (0-1, 0=左, 0.5=中心, 1=右) */
|
/** Pivot/Origin X (0-1, 0=left, 0.5=center, 1=right) | 锚点 X (0-1, 0=左, 0.5=中心, 1=右) */
|
||||||
pivotX: number;
|
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;
|
pivotY: number;
|
||||||
/** Packed color (0xAABBGGRR) | 打包颜色 */
|
/** Packed color (0xAABBGGRR) | 打包颜色 */
|
||||||
color: number;
|
color: number;
|
||||||
@@ -171,6 +171,136 @@ export class UIRenderCollector {
|
|||||||
this.cache = null;
|
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
|
* Get render data
|
||||||
* 获取渲染数据
|
* 获取渲染数据
|
||||||
|
|||||||
@@ -56,9 +56,9 @@ export class UIScrollViewRenderSystem extends EntitySystem {
|
|||||||
const width = (transform.computedWidth ?? transform.width) * scaleX;
|
const width = (transform.computedWidth ?? transform.width) * scaleX;
|
||||||
const height = (transform.computedHeight ?? transform.height) * scaleY;
|
const height = (transform.computedHeight ?? transform.height) * scaleY;
|
||||||
const alpha = transform.worldAlpha ?? transform.alpha;
|
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 sortingLayer = transform.sortingLayer;
|
||||||
const orderInLayer = transform.orderInLayer;
|
const orderInLayer = transform.worldOrderInLayer;
|
||||||
// 使用 transform 的 pivot 计算位置
|
// 使用 transform 的 pivot 计算位置
|
||||||
const pivotX = transform.pivotX;
|
const pivotX = transform.pivotX;
|
||||||
const pivotY = transform.pivotY;
|
const pivotY = transform.pivotY;
|
||||||
|
|||||||
@@ -55,9 +55,9 @@ export class UISliderRenderSystem extends EntitySystem {
|
|||||||
const width = (transform.computedWidth ?? transform.width) * scaleX;
|
const width = (transform.computedWidth ?? transform.width) * scaleX;
|
||||||
const height = (transform.computedHeight ?? transform.height) * scaleY;
|
const height = (transform.computedHeight ?? transform.height) * scaleY;
|
||||||
const alpha = transform.worldAlpha ?? transform.alpha;
|
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 sortingLayer = transform.sortingLayer;
|
||||||
const orderInLayer = transform.orderInLayer;
|
const orderInLayer = transform.worldOrderInLayer;
|
||||||
// 使用 transform 的 pivot 计算中心位置
|
// 使用 transform 的 pivot 计算中心位置
|
||||||
const pivotX = transform.pivotX;
|
const pivotX = transform.pivotX;
|
||||||
const pivotY = transform.pivotY;
|
const pivotY = transform.pivotY;
|
||||||
|
|||||||
@@ -112,9 +112,9 @@ export class UITextRenderSystem extends EntitySystem {
|
|||||||
const width = (transform.computedWidth ?? transform.width) * scaleX;
|
const width = (transform.computedWidth ?? transform.width) * scaleX;
|
||||||
const height = (transform.computedHeight ?? transform.height) * scaleY;
|
const height = (transform.computedHeight ?? transform.height) * scaleY;
|
||||||
const alpha = transform.worldAlpha ?? transform.alpha;
|
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 sortingLayer = transform.sortingLayer;
|
||||||
const orderInLayer = transform.orderInLayer;
|
const orderInLayer = transform.worldOrderInLayer;
|
||||||
// 使用 transform 的 pivot 作为旋转/缩放中心
|
// 使用 transform 的 pivot 作为旋转/缩放中心
|
||||||
const pivotX = transform.pivotX;
|
const pivotX = transform.pivotX;
|
||||||
const pivotY = transform.pivotY;
|
const pivotY = transform.pivotY;
|
||||||
|
|||||||
Reference in New Issue
Block a user