Files
OpenFrontIO/src/client/graphics/fx/SpriteFx.ts
T
DevelopingTom e8834e15e6 Add naval combat animations (#858)
## Description:


https://github.com/user-attachments/assets/b46f949a-eb50-4656-8492-216cf820ac46

Add a couple animations for naval combat:

- shell hit
- ship explosion
- ship sinking

Added a simple `Timeline` class to spread FX animations over time.
Added a `ColoredAnimatedSprite` similar to the existing `ColoredSprite`.
Refactored the latter to avoid code duplication.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

## Please put your Discord username so you can be contacted if a bug or
regression is found:

IngloriousTom
2025-05-26 19:59:11 -04:00

93 lines
2.4 KiB
TypeScript

import { Theme } from "../../../core/configuration/Config";
import { consolex } from "../../../core/Consolex";
import { PlayerView } from "../../../core/game/GameView";
import { AnimatedSprite } from "../AnimatedSprite";
import { AnimatedSpriteLoader } from "../AnimatedSpriteLoader";
import { Fx, FxType } from "./Fx";
function fadeInOut(
t: number,
fadeIn: number = 0.3,
fadeOut: number = 0.7,
): number {
if (t < fadeIn) {
const f = t / fadeIn; // Map to [0, 1]
return f * f;
} else if (t < fadeOut) {
return 1;
} else {
const f = (t - fadeOut) / (1 - fadeOut); // Map to [0, 1]
return 1 - f * f;
}
}
/**
* Fade in/out another FX
*/
export class FadeFx implements Fx {
constructor(
private fxToFade: SpriteFx,
private fadeIn: number,
private fadeOut: number,
) {}
renderTick(duration: number, ctx: CanvasRenderingContext2D): boolean {
const t = this.fxToFade.getElapsedTime() / this.fxToFade.getDuration();
ctx.save();
ctx.globalAlpha = fadeInOut(t, this.fadeIn, this.fadeOut);
const result = this.fxToFade.renderTick(duration, ctx);
ctx.restore();
return result;
}
}
/**
* Animated sprite. Can be colored if provided an owner/theme
*/
export class SpriteFx implements Fx {
protected animatedSprite: AnimatedSprite | null;
protected elapsedTime = 0;
protected duration = 1000;
constructor(
animatedSpriteLoader: AnimatedSpriteLoader,
protected x: number,
protected y: number,
fxType: FxType,
duration?: number,
private owner?: PlayerView,
private theme?: Theme,
) {
this.animatedSprite = animatedSpriteLoader.createAnimatedSprite(
fxType,
owner,
theme,
);
if (!this.animatedSprite) {
consolex.error("Could not load animated sprite", fxType);
} else {
this.duration = duration ?? this.animatedSprite.lifeTime() ?? 1000;
}
}
renderTick(frameTime: number, ctx: CanvasRenderingContext2D): boolean {
if (!this.animatedSprite) return false;
this.elapsedTime += frameTime;
if (this.elapsedTime >= this.duration) return false;
if (!this.animatedSprite.isActive()) return false;
const t = this.elapsedTime / this.duration;
this.animatedSprite.update(frameTime);
this.animatedSprite.draw(ctx, this.x, this.y);
return true;
}
getElapsedTime(): number {
return this.elapsedTime;
}
getDuration(): number {
return this.duration;
}
}