feat: add pulsing animation to attack target tiles in Advance Wars

Introduce a breathing pulse effect (red fill + white border) for attack
target tiles to draw the player's eye when in "must pick a target" fire
mode. The previous static tint is preserved for the indirect-range preview
shown while a unit is merely selected.

- showAttackTiles now accepts optional { pulse: true } parameter
- Added tween-based animation with Sine.easeInOut breathing effect
- Properly clean up pulse tween in destroy() and clearOverlays()
- Updated AdvanceWarsGame to pass pulse: true when showing targets
This commit is contained in:
Brian Fertig 2026-07-19 16:21:13 -06:00
parent a0b3b5f063
commit 138b078dd2
5 changed files with 33 additions and 5 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -424,7 +424,7 @@ export default class AdvanceWarsGame extends Phaser.Scene {
const run = this.run;
run.mode = 'target';
run.targets = targets;
run.view.showAttackTiles(targets.map((t) => ({ x: t.x, y: t.y })));
run.view.showAttackTiles(targets.map((t) => ({ x: t.x, y: t.y })), { pulse: true });
}
onTargetClick(tile) {

View File

@ -246,6 +246,7 @@ export class AdvanceWarsMapView {
destroy() {
this.flipTimer?.remove();
this.atkPulseTween?.stop();
this.rt?.destroy();
this.moveG?.destroy(); this.atkG?.destroy(); this.fogG?.destroy();
this.pathG?.destroy(); this.cursor?.destroy();
@ -492,15 +493,42 @@ export class AdvanceWarsMapView {
}
}
showAttackTiles(tiles) {
// pulse: true for "you must pick a target now" (entering FIRE target mode)
// — red fill + white border breathing to draw the eye. false (default)
// keeps the old static tint, used for the indirect-range preview shown
// while a unit is merely selected, before a move/target is committed to.
showAttackTiles(tiles, { pulse = false } = {}) {
this.atkPulseTween?.stop();
this.atkPulseTween = null;
this.atkG.clear();
this.atkG.fillStyle(0xff3d3d, 0.42);
for (const { x, y } of tiles) {
this.atkG.fillRect(this.ox + x * this.tile + 1, this.oy + y * this.tile + 1, this.tile - 2, this.tile - 2);
if (!pulse) {
this.atkG.fillStyle(0xff3d3d, 0.42);
for (const { x, y } of tiles) {
this.atkG.fillRect(this.ox + x * this.tile + 1, this.oy + y * this.tile + 1, this.tile - 2, this.tile - 2);
}
return;
}
const draw = (fillA, lineA) => {
this.atkG.clear();
this.atkG.fillStyle(0xff3d3d, fillA);
this.atkG.lineStyle(Math.max(2, this.tile * 0.06), 0xffffff, lineA);
for (const { x, y } of tiles) {
const px = this.ox + x * this.tile + 1, py = this.oy + y * this.tile + 1, s = this.tile - 2;
this.atkG.fillRect(px, py, s, s);
this.atkG.strokeRect(px, py, s, s);
}
};
const phase = { t: 0 };
draw(0.30, 0.55);
this.atkPulseTween = this.scene.tweens.add({
targets: phase, t: 1, duration: 550, yoyo: true, repeat: -1, ease: 'Sine.easeInOut',
onUpdate: () => draw(0.28 + phase.t * 0.34, 0.5 + phase.t * 0.5),
});
}
clearOverlays() {
this.atkPulseTween?.stop();
this.atkPulseTween = null;
this.moveG.clear();
this.atkG.clear();
this.pathG.clear();