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
This commit is contained in:
420
packages/rendering/fairygui/src/display/MovieClip.ts
Normal file
420
packages/rendering/fairygui/src/display/MovieClip.ts
Normal file
@@ -0,0 +1,420 @@
|
||||
import { Image } from './Image';
|
||||
import { Timer } from '../core/Timer';
|
||||
import { FGUIEvents } from '../events/Events';
|
||||
import type { IRenderCollector } from '../render/IRenderCollector';
|
||||
|
||||
/**
|
||||
* Frame data for movie clip animation
|
||||
* 动画帧数据
|
||||
*/
|
||||
export interface IFrame {
|
||||
/** Additional delay for this frame | 该帧额外延迟 */
|
||||
addDelay: number;
|
||||
/** Texture ID for this frame | 该帧的纹理 ID */
|
||||
texture?: string | number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple callback handler
|
||||
* 简单回调处理器
|
||||
*/
|
||||
export type SimpleHandler = (() => void) | { run: () => void };
|
||||
|
||||
/**
|
||||
* MovieClip
|
||||
*
|
||||
* Animated sprite display object with frame-based animation.
|
||||
*
|
||||
* 基于帧的动画精灵显示对象
|
||||
*
|
||||
* Features:
|
||||
* - Frame-by-frame animation
|
||||
* - Swing (ping-pong) mode
|
||||
* - Time scale control
|
||||
* - Play range and loop control
|
||||
*/
|
||||
export class MovieClip extends Image {
|
||||
/** Frame interval in milliseconds | 帧间隔(毫秒) */
|
||||
public interval: number = 0;
|
||||
|
||||
/** Swing mode (ping-pong) | 摆动模式 */
|
||||
public swing: boolean = false;
|
||||
|
||||
/** Delay between loops | 循环间延迟 */
|
||||
public repeatDelay: number = 0;
|
||||
|
||||
/** Time scale multiplier | 时间缩放 */
|
||||
public timeScale: number = 1;
|
||||
|
||||
private _playing: boolean = true;
|
||||
private _frameCount: number = 0;
|
||||
private _frames: IFrame[] = [];
|
||||
private _frame: number = 0;
|
||||
private _start: number = 0;
|
||||
private _end: number = 0;
|
||||
private _times: number = 0;
|
||||
private _endAt: number = 0;
|
||||
private _status: number = 0; // 0-none, 1-next loop, 2-ending, 3-ended
|
||||
|
||||
private _frameElapsed: number = 0;
|
||||
private _reversed: boolean = false;
|
||||
private _repeatedCount: number = 0;
|
||||
private _endHandler: SimpleHandler | null = null;
|
||||
private _isOnStage: boolean = false;
|
||||
private _lastTime: number = 0;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.touchable = false;
|
||||
this.setPlaySettings();
|
||||
|
||||
// Subscribe to stage lifecycle events
|
||||
// 订阅舞台生命周期事件
|
||||
this.on(FGUIEvents.ADDED_TO_STAGE, this.onAddToStage, this);
|
||||
this.on(FGUIEvents.REMOVED_FROM_STAGE, this.onRemoveFromStage, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get animation frames
|
||||
* 获取动画帧
|
||||
*/
|
||||
public get frames(): IFrame[] {
|
||||
return this._frames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set animation frames
|
||||
* 设置动画帧
|
||||
*/
|
||||
public set frames(value: IFrame[]) {
|
||||
this._frames = value;
|
||||
this.scaleByTile = false;
|
||||
this.scale9Grid = null;
|
||||
|
||||
if (this._frames && this._frames.length > 0) {
|
||||
this._frameCount = this._frames.length;
|
||||
|
||||
if (this._end === -1 || this._end > this._frameCount - 1) {
|
||||
this._end = this._frameCount - 1;
|
||||
}
|
||||
if (this._endAt === -1 || this._endAt > this._frameCount - 1) {
|
||||
this._endAt = this._frameCount - 1;
|
||||
}
|
||||
if (this._frame < 0 || this._frame > this._frameCount - 1) {
|
||||
this._frame = this._frameCount - 1;
|
||||
}
|
||||
|
||||
this._frameElapsed = 0;
|
||||
this._repeatedCount = 0;
|
||||
this._reversed = false;
|
||||
} else {
|
||||
this._frameCount = 0;
|
||||
}
|
||||
|
||||
this.drawFrame();
|
||||
this.checkTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get frame count
|
||||
* 获取帧数
|
||||
*/
|
||||
public get frameCount(): number {
|
||||
return this._frameCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current frame index
|
||||
* 获取当前帧索引
|
||||
*/
|
||||
public get frame(): number {
|
||||
return this._frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current frame index
|
||||
* 设置当前帧索引
|
||||
*/
|
||||
public set frame(value: number) {
|
||||
if (this._frame !== value) {
|
||||
if (this._frames && value >= this._frameCount) {
|
||||
value = this._frameCount - 1;
|
||||
}
|
||||
|
||||
this._frame = value;
|
||||
this._frameElapsed = 0;
|
||||
this.drawFrame();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get playing state
|
||||
* 获取播放状态
|
||||
*/
|
||||
public get playing(): boolean {
|
||||
return this._playing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set playing state
|
||||
* 设置播放状态
|
||||
*/
|
||||
public set playing(value: boolean) {
|
||||
if (this._playing !== value) {
|
||||
this._playing = value;
|
||||
this.checkTimer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind to first frame
|
||||
* 倒回到第一帧
|
||||
*/
|
||||
public rewind(): void {
|
||||
this._frame = 0;
|
||||
this._frameElapsed = 0;
|
||||
this._reversed = false;
|
||||
this._repeatedCount = 0;
|
||||
|
||||
this.drawFrame();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync status from another MovieClip
|
||||
* 从另一个 MovieClip 同步状态
|
||||
*/
|
||||
public syncStatus(anotherMc: MovieClip): void {
|
||||
this._frame = anotherMc._frame;
|
||||
this._frameElapsed = anotherMc._frameElapsed;
|
||||
this._reversed = anotherMc._reversed;
|
||||
this._repeatedCount = anotherMc._repeatedCount;
|
||||
|
||||
this.drawFrame();
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance animation by time
|
||||
* 推进动画时间
|
||||
*
|
||||
* @param timeInMilliseconds Time to advance | 推进时间(毫秒)
|
||||
*/
|
||||
public advance(timeInMilliseconds: number): void {
|
||||
const beginFrame = this._frame;
|
||||
const beginReversed = this._reversed;
|
||||
const backupTime = timeInMilliseconds;
|
||||
|
||||
while (true) {
|
||||
let tt = this.interval + this._frames[this._frame].addDelay;
|
||||
if (this._frame === 0 && this._repeatedCount > 0) {
|
||||
tt += this.repeatDelay;
|
||||
}
|
||||
if (timeInMilliseconds < tt) {
|
||||
this._frameElapsed = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
timeInMilliseconds -= tt;
|
||||
|
||||
if (this.swing) {
|
||||
if (this._reversed) {
|
||||
this._frame--;
|
||||
if (this._frame <= 0) {
|
||||
this._frame = 0;
|
||||
this._repeatedCount++;
|
||||
this._reversed = !this._reversed;
|
||||
}
|
||||
} else {
|
||||
this._frame++;
|
||||
if (this._frame > this._frameCount - 1) {
|
||||
this._frame = Math.max(0, this._frameCount - 2);
|
||||
this._repeatedCount++;
|
||||
this._reversed = !this._reversed;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._frame++;
|
||||
if (this._frame > this._frameCount - 1) {
|
||||
this._frame = 0;
|
||||
this._repeatedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Completed one round
|
||||
if (this._frame === beginFrame && this._reversed === beginReversed) {
|
||||
const roundTime = backupTime - timeInMilliseconds;
|
||||
timeInMilliseconds -= Math.floor(timeInMilliseconds / roundTime) * roundTime;
|
||||
}
|
||||
}
|
||||
|
||||
this.drawFrame();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set play settings
|
||||
* 设置播放参数
|
||||
*
|
||||
* @param start Start frame | 开始帧
|
||||
* @param end End frame (-1 for last) | 结束帧(-1 为最后一帧)
|
||||
* @param times Loop times (0 for infinite) | 循环次数(0 为无限)
|
||||
* @param endAt Stop at frame (-1 for end) | 停止帧(-1 为结束帧)
|
||||
* @param endHandler Callback on end | 结束回调
|
||||
*/
|
||||
public setPlaySettings(
|
||||
start: number = 0,
|
||||
end: number = -1,
|
||||
times: number = 0,
|
||||
endAt: number = -1,
|
||||
endHandler: SimpleHandler | null = null
|
||||
): void {
|
||||
this._start = start;
|
||||
this._end = end;
|
||||
if (this._end === -1 || this._end > this._frameCount - 1) {
|
||||
this._end = this._frameCount - 1;
|
||||
}
|
||||
this._times = times;
|
||||
this._endAt = endAt;
|
||||
if (this._endAt === -1) {
|
||||
this._endAt = this._end;
|
||||
}
|
||||
this._status = 0;
|
||||
this._endHandler = endHandler;
|
||||
this.frame = start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when added to stage
|
||||
* 添加到舞台时调用
|
||||
*/
|
||||
public onAddToStage(): void {
|
||||
this._isOnStage = true;
|
||||
this._lastTime = Timer.time;
|
||||
this.checkTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when removed from stage
|
||||
* 从舞台移除时调用
|
||||
*/
|
||||
public onRemoveFromStage(): void {
|
||||
this._isOnStage = false;
|
||||
this.checkTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update animation (called each frame)
|
||||
* 更新动画(每帧调用)
|
||||
*/
|
||||
public update(): void {
|
||||
if (!this._playing || this._frameCount === 0 || this._status === 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentTime = Timer.time;
|
||||
let dt = currentTime - this._lastTime;
|
||||
this._lastTime = currentTime;
|
||||
|
||||
if (dt > 100) {
|
||||
dt = 100;
|
||||
}
|
||||
if (this.timeScale !== 1) {
|
||||
dt *= this.timeScale;
|
||||
}
|
||||
|
||||
this._frameElapsed += dt;
|
||||
let tt = this.interval + this._frames[this._frame].addDelay;
|
||||
if (this._frame === 0 && this._repeatedCount > 0) {
|
||||
tt += this.repeatDelay;
|
||||
}
|
||||
if (this._frameElapsed < tt) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._frameElapsed -= tt;
|
||||
if (this._frameElapsed > this.interval) {
|
||||
this._frameElapsed = this.interval;
|
||||
}
|
||||
|
||||
if (this.swing) {
|
||||
if (this._reversed) {
|
||||
this._frame--;
|
||||
if (this._frame <= 0) {
|
||||
this._frame = 0;
|
||||
this._repeatedCount++;
|
||||
this._reversed = !this._reversed;
|
||||
}
|
||||
} else {
|
||||
this._frame++;
|
||||
if (this._frame > this._frameCount - 1) {
|
||||
this._frame = Math.max(0, this._frameCount - 2);
|
||||
this._repeatedCount++;
|
||||
this._reversed = !this._reversed;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._frame++;
|
||||
if (this._frame > this._frameCount - 1) {
|
||||
this._frame = 0;
|
||||
this._repeatedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._status === 1) {
|
||||
// New loop
|
||||
this._frame = this._start;
|
||||
this._frameElapsed = 0;
|
||||
this._status = 0;
|
||||
} else if (this._status === 2) {
|
||||
// Ending
|
||||
this._frame = this._endAt;
|
||||
this._frameElapsed = 0;
|
||||
this._status = 3; // Ended
|
||||
|
||||
// Play end callback
|
||||
if (this._endHandler) {
|
||||
const handler = this._endHandler;
|
||||
this._endHandler = null;
|
||||
if (typeof handler === 'function') {
|
||||
handler();
|
||||
} else {
|
||||
handler.run();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this._frame === this._end) {
|
||||
if (this._times > 0) {
|
||||
this._times--;
|
||||
if (this._times === 0) {
|
||||
this._status = 2; // Ending
|
||||
} else {
|
||||
this._status = 1; // New loop
|
||||
}
|
||||
} else {
|
||||
this._status = 1; // New loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.drawFrame();
|
||||
}
|
||||
|
||||
private drawFrame(): void {
|
||||
if (this._frameCount > 0 && this._frame < this._frames.length) {
|
||||
const frame = this._frames[this._frame];
|
||||
this.texture = frame.texture ?? null;
|
||||
} else {
|
||||
this.texture = null;
|
||||
}
|
||||
}
|
||||
|
||||
private checkTimer(): void {
|
||||
if (this._playing && this._frameCount > 0 && this._isOnStage) {
|
||||
Timer.add(this.update, this);
|
||||
} else {
|
||||
Timer.remove(this.update, this);
|
||||
}
|
||||
}
|
||||
|
||||
public collectRenderData(collector: IRenderCollector): void {
|
||||
super.collectRenderData(collector);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user