feat(dominion): add animated comet trails to playable hand cards
- Implement a visual effect using Phaser Graphics to draw animated "comet" trails along the perimeter of playable action and treasure cards. - Add `perimPoint` helper to calculate coordinates along a rectangle's perimeter. - Manage particle graphics and tweens via new `_handFxGraphics` and `_handFxTweens` arrays, ensuring proper cleanup during render cycles. - Replace simple face highlighting with the new `_buildHandFxItem` method for enhanced visual feedback.
This commit is contained in:
parent
285d992915
commit
41466e14f3
|
|
@ -18,6 +18,20 @@ const CX = GAME_WIDTH / 2;
|
||||||
|
|
||||||
const D = { bg: -1, supply: 5, inplay: 6, hand: 10, hud: 15, portrait: 20, prompt: 40, modal: 80, popup: 95 };
|
const D = { bg: -1, supply: 5, inplay: 6, hand: 10, hud: 15, portrait: 20, prompt: 40, modal: 80, popup: 95 };
|
||||||
|
|
||||||
|
// Returns the screen (x, y) at distance d along the clockwise perimeter of a
|
||||||
|
// rectangle with top-left corner (left, top) and dimensions W×H.
|
||||||
|
function perimPoint(d, W, H, left, top) {
|
||||||
|
const perim = 2 * (W + H);
|
||||||
|
d = ((d % perim) + perim) % perim;
|
||||||
|
if (d < W) return [left + d, top ];
|
||||||
|
d -= W;
|
||||||
|
if (d < H) return [left + W, top + d];
|
||||||
|
d -= H;
|
||||||
|
if (d < W) return [left + W - d, top + H];
|
||||||
|
d -= W;
|
||||||
|
return [left, top + H - d];
|
||||||
|
}
|
||||||
|
|
||||||
const SUPPLY_W = 100, SUPPLY_H = 144;
|
const SUPPLY_W = 100, SUPPLY_H = 144;
|
||||||
const HAND_W = 132, HAND_H = 190;
|
const HAND_W = 132, HAND_H = 190;
|
||||||
const PLAY_W = 78, PLAY_H = 112;
|
const PLAY_W = 78, PLAY_H = 112;
|
||||||
|
|
@ -84,6 +98,8 @@ export default class DominionGame extends Phaser.Scene {
|
||||||
this._arrowSeat = null;
|
this._arrowSeat = null;
|
||||||
this._boughtThisTurn = false;
|
this._boughtThisTurn = false;
|
||||||
this._suppressTurnUi = false;
|
this._suppressTurnUi = false;
|
||||||
|
this._handFxGraphics = [];
|
||||||
|
this._handFxTweens = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
|
|
@ -404,6 +420,7 @@ export default class DominionGame extends Phaser.Scene {
|
||||||
// ── Render (dynamic layer) ───────────────────────────────────────────────────
|
// ── Render (dynamic layer) ───────────────────────────────────────────────────
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
this._clearHandFx();
|
||||||
if (this.hoverTimer) { this.hoverTimer.remove(); this.hoverTimer = null; }
|
if (this.hoverTimer) { this.hoverTimer.remove(); this.hoverTimer = null; }
|
||||||
this.hideHover();
|
this.hideHover();
|
||||||
this.dynamicLayer?.destroy(true);
|
this.dynamicLayer?.destroy(true);
|
||||||
|
|
@ -554,8 +571,8 @@ export default class DominionGame extends Phaser.Scene {
|
||||||
const hs = { iid: c.iid, id: c.id, def, x, baseY, face, hit, isPlayableAction, isPlayableTreasure };
|
const hs = { iid: c.iid, id: c.id, def, x, baseY, face, hit, isPlayableAction, isPlayableTreasure };
|
||||||
this.handSprites.push(hs);
|
this.handSprites.push(hs);
|
||||||
|
|
||||||
if (isPlayableAction) this.highlightFace(face, COLORS.accent);
|
if (isPlayableAction) this._buildHandFxItem(hs, COLORS.accent);
|
||||||
else if (isPlayableTreasure) this.highlightFace(face, COLORS.gold);
|
else if (isPlayableTreasure) this._buildHandFxItem(hs, COLORS.gold);
|
||||||
|
|
||||||
hit.on('pointerdown', (ptr) => {
|
hit.on('pointerdown', (ptr) => {
|
||||||
if (this._animating) return;
|
if (this._animating) return;
|
||||||
|
|
@ -577,6 +594,62 @@ export default class DominionGame extends Phaser.Scene {
|
||||||
face.addAt(glow, 0);
|
face.addAt(glow, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_clearHandFx() {
|
||||||
|
this._handFxTweens.forEach(t => t.destroy());
|
||||||
|
this._handFxGraphics.forEach(g => g.destroy());
|
||||||
|
this._handFxTweens = [];
|
||||||
|
this._handFxGraphics = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildHandFxItem(hs, color) {
|
||||||
|
const gfx = this.add.graphics();
|
||||||
|
gfx.setDepth(D.hand + 25);
|
||||||
|
this._handFxGraphics.push(gfx);
|
||||||
|
|
||||||
|
const W = HAND_W, H = HAND_H;
|
||||||
|
const perim = 2 * (W + H);
|
||||||
|
const TAIL_PX = 90;
|
||||||
|
const SEGMENTS = 20;
|
||||||
|
const COMETS = 2;
|
||||||
|
|
||||||
|
const tracker = { t: 0 };
|
||||||
|
const tween = this.tweens.add({
|
||||||
|
targets: tracker,
|
||||||
|
t: { from: 0, to: 1 },
|
||||||
|
duration: 1200,
|
||||||
|
repeat: -1,
|
||||||
|
ease: 'Linear',
|
||||||
|
onUpdate: () => {
|
||||||
|
const left = hs.x - W / 2;
|
||||||
|
const top = hs.face.y - H / 2;
|
||||||
|
gfx.clear();
|
||||||
|
|
||||||
|
gfx.lineStyle(2, color, 0.2);
|
||||||
|
gfx.strokeRect(left, top, W, H);
|
||||||
|
|
||||||
|
for (let c = 0; c < COMETS; c++) {
|
||||||
|
const headDist = ((tracker.t + c / COMETS) % 1) * perim;
|
||||||
|
for (let seg = 0; seg < SEGMENTS; seg++) {
|
||||||
|
const frac = seg / SEGMENTS;
|
||||||
|
const d1 = ((headDist - (1 - frac) * TAIL_PX + perim * 100) % perim);
|
||||||
|
const d2 = ((headDist - (1 - (seg + 1) / SEGMENTS) * TAIL_PX + perim * 100) % perim);
|
||||||
|
const [x1, y1] = perimPoint(d1, W, H, left, top);
|
||||||
|
const [x2, y2] = perimPoint(d2, W, H, left, top);
|
||||||
|
const alpha = 0.1 + frac * 0.85;
|
||||||
|
const lineColor = frac > 0.85 ? 0xffffff : color;
|
||||||
|
const lineWidth = frac > 0.85 ? 4 : 2;
|
||||||
|
gfx.lineStyle(lineWidth, lineColor, alpha);
|
||||||
|
gfx.beginPath();
|
||||||
|
gfx.moveTo(x1, y1);
|
||||||
|
gfx.lineTo(x2, y2);
|
||||||
|
gfx.strokePath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this._handFxTweens.push(tween);
|
||||||
|
}
|
||||||
|
|
||||||
renderCounts() {
|
renderCounts() {
|
||||||
const gs = this.gs;
|
const gs = this.gs;
|
||||||
// Human deck — face-down card pile with count badge
|
// Human deck — face-down card pile with count badge
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue