421 lines
12 KiB
TypeScript
421 lines
12 KiB
TypeScript
|
|
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);
|
|||
|
|
}
|
|||
|
|
}
|