Files
esengine/packages/rendering/fairygui/src/display/DisplayObject.ts
YHH 155411e743 refactor: reorganize package structure and decouple framework packages (#338)
* refactor: reorganize package structure and decouple framework packages

## Package Structure Reorganization
- Reorganized 55 packages into categorized subdirectories:
  - packages/framework/ - Generic framework (Laya/Cocos compatible)
  - packages/engine/ - ESEngine core modules
  - packages/rendering/ - Rendering modules (WASM dependent)
  - packages/physics/ - Physics modules
  - packages/streaming/ - World streaming
  - packages/network-ext/ - Network extensions
  - packages/editor/ - Editor framework and plugins
  - packages/rust/ - Rust WASM engine
  - packages/tools/ - Build tools and SDK

## Framework Package Decoupling
- Decoupled behavior-tree and blueprint packages from ESEngine dependencies
- Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent)
- ESEngine-specific code moved to esengine/ subpath exports
- Framework packages now usable with Cocos/Laya without ESEngine

## CI Configuration
- Updated CI to only type-check and lint framework packages
- Added type-check:framework and lint:framework scripts

## Breaking Changes
- Package import paths changed due to directory reorganization
- ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine')

* fix: update es-engine file path after directory reorganization

* docs: update README to focus on framework over engine

* ci: only build framework packages, remove Rust/WASM dependencies

* fix: remove esengine subpath from behavior-tree and blueprint builds

ESEngine integration code will only be available in full engine builds.
Framework packages are now purely engine-agnostic.

* fix: move network-protocols to framework, build both in CI

* fix: update workflow paths from packages/core to packages/framework/core

* fix: exclude esengine folder from type-check in behavior-tree and blueprint

* fix: update network tsconfig references to new paths

* fix: add test:ci:framework to only test framework packages in CI

* fix: only build core and math npm packages in CI

* fix: exclude test files from CodeQL and fix string escaping security issue
2025-12-26 14:50:35 +08:00

639 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { EventDispatcher } from '../events/EventDispatcher';
import { FGUIEvents } from '../events/Events';
import { Point, Rectangle } from '../utils/MathTypes';
import type { IRenderCollector } from '../render/IRenderCollector';
import type { GObject } from '../core/GObject';
/**
* DisplayObject
*
* Abstract display object base class for all visual elements.
*
* 抽象显示对象基类,所有可视元素的基础
*/
export abstract class DisplayObject extends EventDispatcher {
/** Name of this display object | 显示对象名称 */
public name: string = '';
// Transform properties | 变换属性
protected _x: number = 0;
protected _y: number = 0;
protected _width: number = 0;
protected _height: number = 0;
protected _scaleX: number = 1;
protected _scaleY: number = 1;
protected _rotation: number = 0;
protected _pivotX: number = 0;
protected _pivotY: number = 0;
protected _skewX: number = 0;
protected _skewY: number = 0;
// Display properties | 显示属性
protected _alpha: number = 1;
protected _visible: boolean = true;
protected _touchable: boolean = true;
protected _grayed: boolean = false;
// Hierarchy | 层级关系
protected _parent: DisplayObject | null = null;
protected _children: DisplayObject[] = [];
// Stage reference | 舞台引用
protected _stage: DisplayObject | null = null;
// Dirty flags | 脏标记
protected _transformDirty: boolean = true;
protected _boundsDirty: boolean = true;
// Cached values | 缓存值
protected _worldAlpha: number = 1;
protected _worldMatrix: Float32Array = new Float32Array([1, 0, 0, 1, 0, 0]);
protected _bounds: Rectangle = new Rectangle();
// User data | 用户数据
public userData: unknown = null;
/** Owner GObject reference | 所属 GObject 引用 */
public gOwner: GObject | null = null;
constructor() {
super();
}
// Position | 位置
public get x(): number {
return this._x;
}
public set x(value: number) {
if (this._x !== value) {
this._x = value;
this.markTransformDirty();
}
}
public get y(): number {
return this._y;
}
public set y(value: number) {
if (this._y !== value) {
this._y = value;
this.markTransformDirty();
}
}
public setPosition(x: number, y: number): void {
if (this._x !== x || this._y !== y) {
this._x = x;
this._y = y;
this.markTransformDirty();
}
}
// Size | 尺寸
public get width(): number {
return this._width;
}
public set width(value: number) {
if (this._width !== value) {
this._width = value;
this.markBoundsDirty();
}
}
public get height(): number {
return this._height;
}
public set height(value: number) {
if (this._height !== value) {
this._height = value;
this.markBoundsDirty();
}
}
public setSize(width: number, height: number): void {
if (this._width !== width || this._height !== height) {
this._width = width;
this._height = height;
this.markBoundsDirty();
}
}
// Scale | 缩放
public get scaleX(): number {
return this._scaleX;
}
public set scaleX(value: number) {
if (this._scaleX !== value) {
this._scaleX = value;
this.markTransformDirty();
}
}
public get scaleY(): number {
return this._scaleY;
}
public set scaleY(value: number) {
if (this._scaleY !== value) {
this._scaleY = value;
this.markTransformDirty();
}
}
public setScale(scaleX: number, scaleY: number): void {
if (this._scaleX !== scaleX || this._scaleY !== scaleY) {
this._scaleX = scaleX;
this._scaleY = scaleY;
this.markTransformDirty();
}
}
// Rotation | 旋转
public get rotation(): number {
return this._rotation;
}
public set rotation(value: number) {
if (this._rotation !== value) {
this._rotation = value;
this.markTransformDirty();
}
}
// Pivot | 轴心点
public get pivotX(): number {
return this._pivotX;
}
public set pivotX(value: number) {
if (this._pivotX !== value) {
this._pivotX = value;
this.markTransformDirty();
}
}
public get pivotY(): number {
return this._pivotY;
}
public set pivotY(value: number) {
if (this._pivotY !== value) {
this._pivotY = value;
this.markTransformDirty();
}
}
public setPivot(pivotX: number, pivotY: number): void {
if (this._pivotX !== pivotX || this._pivotY !== pivotY) {
this._pivotX = pivotX;
this._pivotY = pivotY;
this.markTransformDirty();
}
}
// Skew | 倾斜
public get skewX(): number {
return this._skewX;
}
public set skewX(value: number) {
if (this._skewX !== value) {
this._skewX = value;
this.markTransformDirty();
}
}
public get skewY(): number {
return this._skewY;
}
public set skewY(value: number) {
if (this._skewY !== value) {
this._skewY = value;
this.markTransformDirty();
}
}
// Alpha | 透明度
public get alpha(): number {
return this._alpha;
}
public set alpha(value: number) {
if (this._alpha !== value) {
this._alpha = value;
}
}
// Visibility | 可见性
public get visible(): boolean {
return this._visible;
}
public set visible(value: boolean) {
this._visible = value;
}
// Touchable | 可触摸
public get touchable(): boolean {
return this._touchable;
}
public set touchable(value: boolean) {
this._touchable = value;
}
// Grayed | 灰度
public get grayed(): boolean {
return this._grayed;
}
public set grayed(value: boolean) {
this._grayed = value;
}
// Hierarchy | 层级
public get parent(): DisplayObject | null {
return this._parent;
}
/**
* Get stage reference
* 获取舞台引用
*/
public get stage(): DisplayObject | null {
return this._stage;
}
/**
* Set stage reference (internal use)
* 设置舞台引用(内部使用)
*
* @internal
*/
public setStage(stage: DisplayObject | null): void {
this._stage = stage;
}
public get numChildren(): number {
return this._children.length;
}
/**
* Add a child display object
* 添加子显示对象
*/
public addChild(child: DisplayObject): void {
this.addChildAt(child, this._children.length);
}
/**
* Add a child at specific index
* 在指定位置添加子显示对象
*/
public addChildAt(child: DisplayObject, index: number): void {
if (child._parent === this) {
this.setChildIndex(child, index);
return;
}
if (child._parent) {
child._parent.removeChild(child);
}
index = Math.max(0, Math.min(index, this._children.length));
this._children.splice(index, 0, child);
child._parent = this;
child.markTransformDirty();
// Dispatch addedToStage event if this is on stage
// 如果当前对象在舞台上,分发 addedToStage 事件
if (this._stage !== null) {
this.setChildStage(child, this._stage);
}
}
/**
* Set stage for child and its descendants, dispatch events
* 为子对象及其后代设置舞台,分发事件
*/
private setChildStage(child: DisplayObject, stage: DisplayObject | null): void {
const wasOnStage = child._stage !== null;
const isOnStage = stage !== null;
child._stage = stage;
if (!wasOnStage && isOnStage) {
// Dispatch addedToStage event
child.emit(FGUIEvents.ADDED_TO_STAGE);
} else if (wasOnStage && !isOnStage) {
// Dispatch removedFromStage event
child.emit(FGUIEvents.REMOVED_FROM_STAGE);
}
// Recursively set stage for all children
for (const grandChild of child._children) {
this.setChildStage(grandChild, stage);
}
}
/**
* Remove a child display object
* 移除子显示对象
*/
public removeChild(child: DisplayObject): void {
const index = this._children.indexOf(child);
if (index >= 0) {
this.removeChildAt(index);
}
}
/**
* Remove child at specific index
* 移除指定位置的子显示对象
*/
public removeChildAt(index: number): DisplayObject | null {
if (index < 0 || index >= this._children.length) {
return null;
}
const child = this._children[index];
// Dispatch removedFromStage event if on stage
// 如果在舞台上,分发 removedFromStage 事件
if (this._stage !== null) {
this.setChildStage(child, null);
}
this._children.splice(index, 1);
child._parent = null;
return child;
}
/**
* Remove all children
* 移除所有子显示对象
*/
public removeChildren(): void {
// Dispatch removedFromStage events if on stage
// 如果在舞台上,分发 removedFromStage 事件
if (this._stage !== null) {
for (const child of this._children) {
this.setChildStage(child, null);
}
}
for (const child of this._children) {
child._parent = null;
}
this._children.length = 0;
}
/**
* Get child at index
* 获取指定位置的子显示对象
*/
public getChildAt(index: number): DisplayObject | null {
if (index < 0 || index >= this._children.length) {
return null;
}
return this._children[index];
}
/**
* Get child index
* 获取子显示对象的索引
*/
public getChildIndex(child: DisplayObject): number {
return this._children.indexOf(child);
}
/**
* Set child index
* 设置子显示对象的索引
*/
public setChildIndex(child: DisplayObject, index: number): void {
const currentIndex = this._children.indexOf(child);
if (currentIndex < 0) return;
index = Math.max(0, Math.min(index, this._children.length - 1));
if (currentIndex === index) return;
this._children.splice(currentIndex, 1);
this._children.splice(index, 0, child);
}
/**
* Swap two children
* 交换两个子显示对象
*/
public swapChildren(child1: DisplayObject, child2: DisplayObject): void {
const index1 = this._children.indexOf(child1);
const index2 = this._children.indexOf(child2);
if (index1 >= 0 && index2 >= 0) {
this._children[index1] = child2;
this._children[index2] = child1;
}
}
/**
* Get child by name
* 通过名称获取子显示对象
*/
public getChildByName(name: string): DisplayObject | null {
for (const child of this._children) {
if (child.name === name) {
return child;
}
}
return null;
}
// Transform | 变换
/**
* Update world matrix
* 更新世界矩阵
*
* World matrix is in FGUI's coordinate system (top-left origin, Y-down).
* Coordinate system conversion to engine (center origin, Y-up) is done in FGUIRenderDataProvider.
*
* 世界矩阵使用 FGUI 坐标系左上角原点Y 向下)。
* 坐标系转换到引擎中心原点Y 向上)在 FGUIRenderDataProvider 中完成。
*/
public updateTransform(): void {
if (!this._transformDirty) return;
const m = this._worldMatrix;
const rad = (this._rotation * Math.PI) / 180;
const cos = Math.cos(rad);
const sin = Math.sin(rad);
m[0] = cos * this._scaleX;
m[1] = sin * this._scaleX;
m[2] = -sin * this._scaleY;
m[3] = cos * this._scaleY;
// Keep FGUI's coordinate system (top-left origin, Y-down)
// 保持 FGUI 坐标系左上角原点Y 向下)
m[4] = this._x - this._pivotX * m[0] - this._pivotY * m[2];
m[5] = this._y - this._pivotX * m[1] - this._pivotY * m[3];
if (this._parent) {
const pm = this._parent._worldMatrix;
const a = m[0], b = m[1], c = m[2], d = m[3], tx = m[4], ty = m[5];
m[0] = a * pm[0] + b * pm[2];
m[1] = a * pm[1] + b * pm[3];
m[2] = c * pm[0] + d * pm[2];
m[3] = c * pm[1] + d * pm[3];
m[4] = tx * pm[0] + ty * pm[2] + pm[4];
m[5] = tx * pm[1] + ty * pm[3] + pm[5];
this._worldAlpha = this._alpha * this._parent._worldAlpha;
} else {
this._worldAlpha = this._alpha;
}
this._transformDirty = false;
for (const child of this._children) {
child.markTransformDirty();
child.updateTransform();
}
}
/**
* Local to global point conversion
* 本地坐标转全局坐标
*/
public localToGlobal(localPoint: Point, outPoint?: Point): Point {
this.updateTransform();
outPoint = outPoint || new Point();
const m = this._worldMatrix;
outPoint.x = localPoint.x * m[0] + localPoint.y * m[2] + m[4];
outPoint.y = localPoint.x * m[1] + localPoint.y * m[3] + m[5];
return outPoint;
}
/**
* Global to local point conversion
* 全局坐标转本地坐标
*/
public globalToLocal(globalPoint: Point, outPoint?: Point): Point {
this.updateTransform();
outPoint = outPoint || new Point();
const m = this._worldMatrix;
const det = m[0] * m[3] - m[1] * m[2];
if (det === 0) {
outPoint.x = 0;
outPoint.y = 0;
} else {
const invDet = 1 / det;
const x = globalPoint.x - m[4];
const y = globalPoint.y - m[5];
outPoint.x = (x * m[3] - y * m[2]) * invDet;
outPoint.y = (y * m[0] - x * m[1]) * invDet;
}
return outPoint;
}
/**
* Hit test
* 碰撞检测
*/
public hitTest(globalX: number, globalY: number): DisplayObject | null {
if (!this._visible || !this._touchable) {
return null;
}
const localPoint = this.globalToLocal(new Point(globalX, globalY));
if (
localPoint.x >= 0 &&
localPoint.x < this._width &&
localPoint.y >= 0 &&
localPoint.y < this._height
) {
for (let i = this._children.length - 1; i >= 0; i--) {
const hit = this._children[i].hitTest(globalX, globalY);
if (hit) return hit;
}
return this;
}
return null;
}
// Dirty flags | 脏标记
protected markTransformDirty(): void {
this._transformDirty = true;
this._boundsDirty = true;
}
protected markBoundsDirty(): void {
this._boundsDirty = true;
}
// Render data collection | 渲染数据收集
/**
* Collect render data (abstract - implemented by subclasses)
* 收集渲染数据(抽象方法 - 由子类实现)
*/
public abstract collectRenderData(collector: IRenderCollector): void;
/**
* Get world matrix
* 获取世界矩阵
*/
public get worldMatrix(): Float32Array {
return this._worldMatrix;
}
/**
* Get world alpha
* 获取世界透明度
*/
public get worldAlpha(): number {
return this._worldAlpha;
}
/**
* Dispose
* 销毁
*/
public dispose(): void {
if (this._parent) {
this._parent.removeChild(this);
}
for (const child of this._children) {
child.dispose();
}
this._children.length = 0;
super.dispose();
}
}