* 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
639 lines
16 KiB
TypeScript
639 lines
16 KiB
TypeScript
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();
|
||
}
|
||
}
|