Newer
Older
rpg / packages / engine / src / render / fx / FxFloatText.ts
import type { Updatable } from '../../core/Updatables';
import type { Vec2 } from '../../math/Vec2';
import { PixelText, type PixelTextOptions } from '../../ui/PixelText';
import { sampleFloat, type FxFloatSpec } from './fxSim';

/**
 * Всплывающий текст: подъём + затухание (числа урона, тосты, подборы).
 * Владеет своим PixelText: создаёт, тикает и уничтожает себя по завершении.
 * Позиция задаётся при создании (at), update только смещает от неё.
 */
export class FxFloatText extends PixelText implements Updatable {
    private t = 0;
    private cancelled = false;
    private readonly baseX: number;
    private readonly baseY: number;

    constructor(
        text: string,
        at: Vec2,
        private readonly spec: FxFloatSpec,
        style?: Partial<PixelTextOptions>
    ) {
        super({ text, size: 11, ...style });
        this.anchor.set(0.5);
        this.baseX = at.x;
        this.baseY = at.y;
        this.position.set(at.x, at.y);
    }

    /** Досрочно погасить (например, новый тост вытесняет старый). */
    cancel(): void {
        if (this.cancelled) return;
        this.cancelled = true;
        this.destroy({ children: true });
    }

    /** true, когда текст доиграл (или погашен). */
    get done(): boolean {
        return this.cancelled || this.t >= this.spec.duration - 1e-6;
    }

    update(dt: number): void {
        if (this.destroyed) return;
        this.t += dt;
        const s = sampleFloat(this.t, this.spec);
        this.x = this.baseX + s.x;
        this.y = this.baseY + s.y;
        this.alpha = s.alpha;
        this.scale.set(s.scale);
        if (this.done) this.destroy({ children: true });
    }
}