feat(advancewars): improve battle animations and depth sorting

- Add new unit sprite sheet (advancewars-units.png/psd)
- Slow down all battle animations for better readability:
  - Attack, flash, death, and delay timings increased
  - Unit movement animation duration increased (70→180ms)
  - Explosion animation slowed down
  - Post-turn wait time increased (140→300ms)
- Merge props and units into shared Y-sorted "actors" depth band
  - Buildings and units now sort correctly by screen row
  - Move/attack overlays remain below actors for proper decal rendering
This commit is contained in:
Brian Fertig 2026-07-19 11:48:30 -06:00
parent bf71bfc3c7
commit fcc74bef28
5 changed files with 21 additions and 15 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

View File

@ -90,7 +90,7 @@ export function playBattleAnim(scene, rules, texKey, battles, onDone) {
}; };
dim.on('pointerdown', cleanup); dim.on('pointerdown', cleanup);
let delay = 350; let delay = 700;
for (const b of battles) { for (const b of battles) {
const fromLeft = b.attackerId === first.attackerId; const fromLeft = b.attackerId === first.attackerId;
const shooter = fromLeft ? left : right; const shooter = fromLeft ? left : right;
@ -99,20 +99,20 @@ export function playBattleAnim(scene, rules, texKey, battles, onDone) {
if (finished) return; if (finished) return;
scene.tweens.add({ scene.tweens.add({
targets: shooter.img, x: shooter.img.x + (fromLeft ? 40 : -40), targets: shooter.img, x: shooter.img.x + (fromLeft ? 40 : -40),
duration: 90, yoyo: true, duration: 150, yoyo: true,
}); });
const flash = scene.add.rectangle(victim.img.x, victim.img.y - 60, W / 2 - 20, H - 60, 0xffffff, 0.55).setDepth(53); const flash = scene.add.rectangle(victim.img.x, victim.img.y - 60, W / 2 - 20, H - 60, 0xffffff, 0.55).setDepth(53);
objs.push(flash); objs.push(flash);
scene.tweens.add({ targets: flash, alpha: 0, duration: 240, onComplete: () => flash.destroy() }); scene.tweens.add({ targets: flash, alpha: 0, duration: 450, onComplete: () => flash.destroy() });
scene.cameras.main.shake(120, 0.004); scene.cameras.main.shake(120, 0.004);
if (fromLeft) { rightHp = b.defender.hp; setBar(right, rightHp); } if (fromLeft) { rightHp = b.defender.hp; setBar(right, rightHp); }
else { leftHp = b.defender.hp; setBar(left, leftHp); } else { leftHp = b.defender.hp; setBar(left, leftHp); }
if (b.killed) { if (b.killed) {
scene.tweens.add({ targets: victim.img, alpha: 0, y: victim.img.y + 16, duration: 320 }); scene.tweens.add({ targets: victim.img, alpha: 0, y: victim.img.y + 16, duration: 550 });
} }
})); }));
delay += 620; delay += 1200;
} }
timers.push(scene.time.delayedCall(delay + 350, cleanup)); timers.push(scene.time.delayedCall(delay + 700, cleanup));
return { skip: cleanup }; return { skip: cleanup };
} }

View File

@ -549,7 +549,7 @@ export default class AdvanceWarsGame extends Phaser.Scene {
run.view.syncUnits(); run.view.syncUnits();
run.view.refreshFog(0); run.view.refreshFog(0);
run.hud.refresh(run.state, 0); run.hud.refresh(run.state, 0);
if (enemy && (moved || battles.length)) await this.wait(140); if (enemy && (moved || battles.length)) await this.wait(300);
} }
} }

View File

@ -200,7 +200,12 @@ export class AdvanceWarsMapView {
this.ox = areaX + Math.floor((areaW - this.tile * state.w) / 2); this.ox = areaX + Math.floor((areaW - this.tile * state.w) / 2);
this.oy = areaY + Math.floor((areaH - this.tile * state.h) / 2); this.oy = areaY + Math.floor((areaH - this.tile * state.h) / 2);
this.depths = { terrain: 1, props: 2, moveOv: 3, atkOv: 4, units: 5, fog: 12, path: 13, cursor: 14 }; // props (buildings) and units share one Y-sorted "actors" band — depth is
// actors + row, so anything lower on screen (higher row) always draws in
// front of anything above it, regardless of whether it's a unit or a
// building. moveOv/atkOv sit below the actor band so the tinted-tile
// overlay reads as a ground decal instead of washing over sprites.
this.depths = { terrain: 1, moveOv: 2, atkOv: 3, actors: 4, fog: 20, path: 21, cursor: 22 };
this.unitViews = new Map(); // unitId -> { c, sprite, hp, badge } this.unitViews = new Map(); // unitId -> { c, sprite, hp, badge }
this.propViews = new Map(); // tileKey -> image this.propViews = new Map(); // tileKey -> image
this.animStep = 0; this.animStep = 0;
@ -350,7 +355,7 @@ export class AdvanceWarsMapView {
const img = this.scene.add.image(this.px(x), this.oy + (y + 1) * this.tile, this.tex.buildings, t.frame) const img = this.scene.add.image(this.px(x), this.oy + (y + 1) * this.tile, this.tex.buildings, t.frame)
.setOrigin(0.5, 1) .setOrigin(0.5, 1)
.setScale(this.spriteScaleFor()) .setScale(this.spriteScaleFor())
.setDepth(this.depths.props); .setDepth(this.depths.actors + y);
this.propViews.set(k, img); this.propViews.set(k, img);
} }
this.refreshProps(); this.refreshProps();
@ -371,7 +376,7 @@ export class AdvanceWarsMapView {
seen.add(u.id); seen.add(u.id);
let v = this.unitViews.get(u.id); let v = this.unitViews.get(u.id);
if (!v) { if (!v) {
const c = scene.add.container(0, 0).setDepth(this.depths.units); const c = scene.add.container(0, 0);
const sprite = scene.add.image(0, 0, this.tex.units, rules.unitById[u.type].frame) const sprite = scene.add.image(0, 0, this.tex.units, rules.unitById[u.type].frame)
.setOrigin(0.5, 1).setScale(this.spriteScaleFor()); .setOrigin(0.5, 1).setScale(this.spriteScaleFor());
const hp = scene.add.text(this.tile * 0.30, -2, '', { const hp = scene.add.text(this.tile * 0.30, -2, '', {
@ -395,7 +400,7 @@ export class AdvanceWarsMapView {
placeUnit(u, v = this.unitViews.get(u.id)) { placeUnit(u, v = this.unitViews.get(u.id)) {
if (!v) return; if (!v) return;
const { rules } = this; const { rules } = this;
v.c.setPosition(this.px(u.x), this.oy + (u.y + 1) * this.tile); v.c.setPosition(this.px(u.x), this.oy + (u.y + 1) * this.tile).setDepth(this.depths.actors + u.y);
v.sprite.setFrame(rules.unitById[u.type].frame + this.animStep); v.sprite.setFrame(rules.unitById[u.type].frame + this.animStep);
v.sprite.setFlipX(u.army !== 0); v.sprite.setFlipX(u.army !== 0);
const base = armyColorInt(rules, u.army); const base = armyColorInt(rules, u.army);
@ -491,21 +496,22 @@ export class AdvanceWarsMapView {
boom(x, y, onDone) { boom(x, y, onDone) {
const img = this.scene.add.image(this.px(x), this.py(y), this.tex.ui, UI_FRAMES.explosion1) const img = this.scene.add.image(this.px(x), this.py(y), this.tex.ui, UI_FRAMES.explosion1)
.setScale(this.spriteScaleFor()).setDepth(this.depths.cursor); .setScale(this.spriteScaleFor()).setDepth(this.depths.cursor);
this.scene.time.delayedCall(110, () => img.setFrame(UI_FRAMES.explosion2)); this.scene.time.delayedCall(200, () => img.setFrame(UI_FRAMES.explosion2));
this.scene.time.delayedCall(260, () => { img.destroy(); onDone?.(); }); this.scene.time.delayedCall(450, () => { img.destroy(); onDone?.(); });
} }
// animate one unit view along a path (state already updated) // animate one unit view along a path (state already updated)
animateMove(unitId, path, onDone) { animateMove(unitId, path, onDone) {
const v = this.unitViews.get(unitId); const v = this.unitViews.get(unitId);
if (!v || !path?.length) { onDone?.(); return; } if (!v || !path?.length) { onDone?.(); return; }
const points = path.map((s) => ({ x: this.px(s.x), y: this.oy + (s.y + 1) * this.tile })); const points = path.map((s) => ({ x: this.px(s.x), y: this.oy + (s.y + 1) * this.tile, row: s.y }));
let i = 0; let i = 0;
const step = () => { const step = () => {
if (i >= points.length) { onDone?.(); return; } if (i >= points.length) { onDone?.(); return; }
const p = points[i++]; const p = points[i++];
v.c.setDepth(this.depths.actors + p.row);
this.scene.tweens.add({ this.scene.tweens.add({
targets: v.c, x: p.x, y: p.y, duration: 70, onComplete: step, targets: v.c, x: p.x, y: p.y, duration: 180, onComplete: step,
}); });
}; };
step(); step();