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);
let delay = 350;
let delay = 700;
for (const b of battles) {
const fromLeft = b.attackerId === first.attackerId;
const shooter = fromLeft ? left : right;
@ -99,20 +99,20 @@ export function playBattleAnim(scene, rules, texKey, battles, onDone) {
if (finished) return;
scene.tweens.add({
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);
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);
if (fromLeft) { rightHp = b.defender.hp; setBar(right, rightHp); }
else { leftHp = b.defender.hp; setBar(left, leftHp); }
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 };
}

View File

@ -549,7 +549,7 @@ export default class AdvanceWarsGame extends Phaser.Scene {
run.view.syncUnits();
run.view.refreshFog(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.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.propViews = new Map(); // tileKey -> image
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)
.setOrigin(0.5, 1)
.setScale(this.spriteScaleFor())
.setDepth(this.depths.props);
.setDepth(this.depths.actors + y);
this.propViews.set(k, img);
}
this.refreshProps();
@ -371,7 +376,7 @@ export class AdvanceWarsMapView {
seen.add(u.id);
let v = this.unitViews.get(u.id);
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)
.setOrigin(0.5, 1).setScale(this.spriteScaleFor());
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)) {
if (!v) return;
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.setFlipX(u.army !== 0);
const base = armyColorInt(rules, u.army);
@ -491,21 +496,22 @@ export class AdvanceWarsMapView {
boom(x, y, onDone) {
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);
this.scene.time.delayedCall(110, () => img.setFrame(UI_FRAMES.explosion2));
this.scene.time.delayedCall(260, () => { img.destroy(); onDone?.(); });
this.scene.time.delayedCall(200, () => img.setFrame(UI_FRAMES.explosion2));
this.scene.time.delayedCall(450, () => { img.destroy(); onDone?.(); });
}
// animate one unit view along a path (state already updated)
animateMove(unitId, path, onDone) {
const v = this.unitViews.get(unitId);
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;
const step = () => {
if (i >= points.length) { onDone?.(); return; }
const p = points[i++];
v.c.setDepth(this.depths.actors + p.row);
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();