feat(combat): implement alternating initiative with enhanced attack animations
- Added `playerGoesFirst` state to `CombatEngine` to alternate turn order each round. - Updated `beginTurn` and `beginCommit` to handle AI deployment based on the current initiative (opponent deploys first if they have initiative, otherwise after the player). - Modified `_buildPendingAttacks` to respect the alternating attack order. - Added `animateHPLoss` to `CardObject.js` for a visual HP reduction animation similar to the Berserk gain effect. - Enhanced `_animateAttack` in `BattleScene.js`: - Integrated sprite animations for attacks (gun turret) and explosions using new spritesheets. - Synchronized HP loss and Berserk gain animations with the attack sprite completion. - Added an "Attacks First" indicator icon next to the commander of the side with initiative. - Updated `BootScene.js` to load the new `attacksFirst` UI image and `attacks` spritesheet.
This commit is contained in:
parent
73ce826100
commit
c7f4dc9bbb
Binary file not shown.
|
After Width: | Height: | Size: 474 KiB |
|
|
@ -28,6 +28,7 @@ export class CombatEngine {
|
|||
this.log = [];
|
||||
this.winner = null; // 'player' | 'opponent' | null
|
||||
this.events = []; // events for animation: [{type, data}]
|
||||
this.playerGoesFirst = true; // alternates each turn
|
||||
}
|
||||
|
||||
_log(msg) {
|
||||
|
|
@ -260,22 +261,24 @@ export class CombatEngine {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Phase 1: draw cards, AI deploys, return player's hand for selection
|
||||
// Phase 1: draw cards, AI deploys (only when opponent goes first), return player's hand
|
||||
beginTurn() {
|
||||
if (this.winner) return { hand: [], canDeploy: false };
|
||||
if (this.winner) return { hand: [], canDeploy: false, playerGoesFirst: this.playerGoesFirst };
|
||||
this.events = [];
|
||||
this.turn++;
|
||||
this._log(`--- Turn ${this.turn} ---`);
|
||||
this._drawPhase();
|
||||
// AI deploys its card immediately
|
||||
const aiCard = this.ai.getNextCard();
|
||||
if (aiCard && this.opponentLanes.length < MAX_LANES) {
|
||||
this.opponentLanes.push(aiCard);
|
||||
this._log(`Opponent deploys ${aiCard.name}`);
|
||||
this.events.push({ type: 'deploy', side: 'opponent', card: aiCard });
|
||||
// AI deploys immediately only on opponent-first turns
|
||||
if (!this.playerGoesFirst) {
|
||||
const aiCard = this.ai.getNextCard();
|
||||
if (aiCard && this.opponentLanes.length < MAX_LANES) {
|
||||
this.opponentLanes.push(aiCard);
|
||||
this._log(`Opponent deploys ${aiCard.name}`);
|
||||
this.events.push({ type: 'deploy', side: 'opponent', card: aiCard });
|
||||
}
|
||||
}
|
||||
const canDeploy = this.playerHand.length > 0 && this.playerLanes.length < MAX_LANES;
|
||||
return { hand: [...this.playerHand], canDeploy };
|
||||
return { hand: [...this.playerHand], canDeploy, playerGoesFirst: this.playerGoesFirst };
|
||||
}
|
||||
|
||||
// Phase 2: deploy chosen card (or null to pass), then resolve the turn
|
||||
|
|
@ -314,40 +317,48 @@ export class CombatEngine {
|
|||
this.events.push({ type: 'deploy', side: 'player', card: chosenCard });
|
||||
}
|
||||
}
|
||||
// On player-first turns, AI deploys after the player
|
||||
if (this.playerGoesFirst) {
|
||||
const aiCard = this.ai.getNextCard();
|
||||
if (aiCard && this.opponentLanes.length < MAX_LANES) {
|
||||
this.opponentLanes.push(aiCard);
|
||||
this._log(`Opponent deploys ${aiCard.name}`);
|
||||
this.events.push({ type: 'deploy', side: 'opponent', card: aiCard });
|
||||
}
|
||||
}
|
||||
this._activationPhase();
|
||||
this._commanderSkillPhase();
|
||||
this._rupturePhase();
|
||||
this._jamPhase();
|
||||
this._buildPendingAttacks();
|
||||
this.playerGoesFirst = !this.playerGoesFirst; // flip initiative for next turn
|
||||
return [...this.events];
|
||||
}
|
||||
|
||||
// Build the ordered list of attacks for this turn (called by beginCommit).
|
||||
// Attack order respects the current initiative (playerGoesFirst, before it is flipped).
|
||||
_buildPendingAttacks() {
|
||||
this._pendingAttacks = [];
|
||||
for (let i = 0; i < this.playerLanes.length; i++) {
|
||||
const attacker = this.playerLanes[i];
|
||||
if (attacker.currentHP <= 0 || attacker.currentDelay > 0 || attacker.jamTurns > 0) continue;
|
||||
this._pendingAttacks.push({
|
||||
attacker,
|
||||
target: this.opponentLanes[i] || null,
|
||||
alliedLanes: this.playerLanes,
|
||||
enemyLanes: this.opponentLanes,
|
||||
enemyCommander: this.opponentCommander,
|
||||
side: 'player'
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < this.opponentLanes.length; i++) {
|
||||
const attacker = this.opponentLanes[i];
|
||||
if (attacker.currentHP <= 0 || attacker.currentDelay > 0 || attacker.jamTurns > 0) continue;
|
||||
this._pendingAttacks.push({
|
||||
attacker,
|
||||
target: this.playerLanes[i] || null,
|
||||
alliedLanes: this.opponentLanes,
|
||||
enemyLanes: this.playerLanes,
|
||||
enemyCommander: this.playerCommander,
|
||||
side: 'opponent'
|
||||
});
|
||||
const addAttacks = (attackerLanes, enemyLanes, enemyCommander, alliedLanes, side) => {
|
||||
for (let i = 0; i < attackerLanes.length; i++) {
|
||||
const attacker = attackerLanes[i];
|
||||
if (attacker.currentHP <= 0 || attacker.currentDelay > 0 || attacker.jamTurns > 0) continue;
|
||||
this._pendingAttacks.push({
|
||||
attacker,
|
||||
target: enemyLanes[i] || null,
|
||||
alliedLanes,
|
||||
enemyLanes,
|
||||
enemyCommander,
|
||||
side
|
||||
});
|
||||
}
|
||||
};
|
||||
if (this.playerGoesFirst) {
|
||||
addAttacks(this.playerLanes, this.opponentLanes, this.opponentCommander, this.playerLanes, 'player');
|
||||
addAttacks(this.opponentLanes, this.playerLanes, this.playerCommander, this.opponentLanes, 'opponent');
|
||||
} else {
|
||||
addAttacks(this.opponentLanes, this.playerLanes, this.playerCommander, this.opponentLanes, 'opponent');
|
||||
addAttacks(this.playerLanes, this.opponentLanes, this.opponentCommander, this.playerLanes, 'player');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -247,6 +247,65 @@ export class CardObject extends Phaser.GameObjects.Container {
|
|||
});
|
||||
}
|
||||
|
||||
animateHPLoss(damage, onComplete) {
|
||||
if (!this.hpText || !this.scene) { if (onComplete) onComplete(); return; }
|
||||
|
||||
const w = this.options.width || 80;
|
||||
const s = w / 80;
|
||||
const h = this.options.height || 110;
|
||||
const bannerH = Math.round(h * 0.12);
|
||||
const bottomBannerCY = h / 2 - bannerH / 2;
|
||||
|
||||
// Rewind to pre-damage value so animation transitions old → new
|
||||
this.hpText.setText(`${Math.max(0, this.cardData.currentHP + damage)}`);
|
||||
|
||||
this.scene.tweens.add({
|
||||
targets: this.hpText,
|
||||
scaleX: 2, scaleY: 2,
|
||||
duration: 200,
|
||||
ease: 'Back.Out',
|
||||
onComplete: () => {
|
||||
if (!this.scene) { if (onComplete) onComplete(); return; }
|
||||
|
||||
const lossText = this.scene.add.text(
|
||||
this.hpText.x + this.hpText.width * 2 + Math.round(4 * s),
|
||||
bottomBannerCY,
|
||||
`-${damage}`,
|
||||
{
|
||||
fontSize: `${Math.round(11 * s)}px`,
|
||||
color: '#ff4444',
|
||||
fontStyle: 'bold',
|
||||
stroke: '#000000',
|
||||
strokeThickness: Math.max(1, Math.round(2 * s))
|
||||
}
|
||||
).setOrigin(0, 0.5).setDepth(50);
|
||||
this.add(lossText);
|
||||
|
||||
this.scene.time.delayedCall(500, () => {
|
||||
if (!this.scene) { if (onComplete) onComplete(); return; }
|
||||
this.hpText.setText(`${Math.max(0, this.cardData.currentHP)}`);
|
||||
this.scene.tweens.add({
|
||||
targets: this.hpText,
|
||||
scaleX: 1, scaleY: 1,
|
||||
duration: 200,
|
||||
ease: 'Power2'
|
||||
});
|
||||
this.scene.tweens.add({
|
||||
targets: lossText,
|
||||
alpha: 0,
|
||||
y: bottomBannerCY - Math.round(14 * s),
|
||||
duration: 300,
|
||||
ease: 'Power2',
|
||||
onComplete: () => {
|
||||
if (lossText.scene) lossText.destroy();
|
||||
if (onComplete) onComplete();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
flash(color = 0xff4444) {
|
||||
const factionColor = FACTION_COLORS[this.cardData.faction] || 0x444444;
|
||||
this.scene.tweens.add({
|
||||
|
|
|
|||
|
|
@ -31,6 +31,24 @@ export class BattleScene extends Phaser.Scene {
|
|||
g.destroy();
|
||||
}
|
||||
|
||||
// Sprite animations (global — skip if already registered from a previous battle)
|
||||
if (!this.anims.exists('attack_anim')) {
|
||||
this.anims.create({
|
||||
key: 'attack_anim',
|
||||
frames: this.anims.generateFrameNumbers('attacks', { start: 0, end: 4 }),
|
||||
frameRate: 10,
|
||||
repeat: 0
|
||||
});
|
||||
}
|
||||
if (!this.anims.exists('explosion_anim')) {
|
||||
this.anims.create({
|
||||
key: 'explosion_anim',
|
||||
frames: this.anims.generateFrameNumbers('attacks', { start: 5, end: 8 }),
|
||||
frameRate: 10,
|
||||
repeat: 0
|
||||
});
|
||||
}
|
||||
|
||||
// Background
|
||||
this.add.rectangle(width / 2, height / 2, width, height, 0x1a1a2e);
|
||||
|
||||
|
|
@ -48,7 +66,7 @@ export class BattleScene extends Phaser.Scene {
|
|||
|
||||
// Side labels
|
||||
this.add.text(14, 95, 'OPPONENT', { fontSize: '17px', color: '#ff8888' });
|
||||
this.add.text(14, 520, 'PLAYER', { fontSize: '17px', color: '#88aaff' });
|
||||
this.add.text(14, 520, 'PLAYER', { fontSize: '17px', color: '#88aaff' });
|
||||
|
||||
this.battlefield = new BattleField(this, {
|
||||
playerY: 715, opponentY: 305,
|
||||
|
|
@ -118,6 +136,8 @@ export class BattleScene extends Phaser.Scene {
|
|||
this.autoTimer = null;
|
||||
this.waitingForPick = false;
|
||||
this.isAnimating = false;
|
||||
this.playerGoesFirst = true;
|
||||
this.initiativeIndicator = null;
|
||||
|
||||
const autoBtn = this.add.rectangle(1680, 35, 180, 44, 0x224422)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
|
|
@ -147,8 +167,8 @@ export class BattleScene extends Phaser.Scene {
|
|||
this.commanderObjects = new Map(); // instanceId → CardObject (persists across _renderState)
|
||||
|
||||
const specs = [
|
||||
{ data: state.opponent.commander, cx: 150, cy: 305, label: 'ENEMY CMD', labelColor: '#ff8888' },
|
||||
{ data: state.player.commander, cx: 150, cy: 715, label: 'COMMANDER', labelColor: '#ffd700' }
|
||||
{ data: state.opponent.commander, cx: 150, cy: 305, label: 'ENEMY CMD', labelColor: '#ff8888' },
|
||||
{ data: state.player.commander, cx: 150, cy: 715, label: 'COMMANDER', labelColor: '#ffd700' }
|
||||
];
|
||||
|
||||
for (const s of specs) {
|
||||
|
|
@ -173,18 +193,16 @@ export class BattleScene extends Phaser.Scene {
|
|||
if (this.waitingForPick || this.isAnimating) return;
|
||||
if (this.engine.winner) { this._showResult(); return; }
|
||||
|
||||
// Snapshot which opponent cards are already on the field before this turn
|
||||
// Snapshot opponent lanes before this turn (only matters on opponent-first turns)
|
||||
const oldOpponentIds = new Set(
|
||||
this.engine.getState().opponent.lanes.map(c => c.instanceId)
|
||||
);
|
||||
|
||||
const { hand, canDeploy } = this.engine.beginTurn();
|
||||
const { hand, canDeploy, playerGoesFirst } = this.engine.beginTurn();
|
||||
this.playerGoesFirst = playerGoesFirst;
|
||||
this.turnText.setText(`Turn ${this.engine.turn}`);
|
||||
this._renderState();
|
||||
|
||||
// Find the newly deployed opponent card (if any)
|
||||
const newOpponentCard = this.engine.getState().opponent.lanes
|
||||
.find(c => !oldOpponentIds.has(c.instanceId));
|
||||
this._updateInitiativeIndicator(playerGoesFirst);
|
||||
|
||||
const proceedAfterDeploy = () => {
|
||||
if (canDeploy && !this.autoPlay) {
|
||||
|
|
@ -196,6 +214,12 @@ export class BattleScene extends Phaser.Scene {
|
|||
}
|
||||
};
|
||||
|
||||
// On opponent-first turns, show enemy deploy animation before showing picker.
|
||||
// On player-first turns, opponent hasn't deployed yet — go straight to picker.
|
||||
const newOpponentCard = !playerGoesFirst
|
||||
? this.engine.getState().opponent.lanes.find(c => !oldOpponentIds.has(c.instanceId))
|
||||
: null;
|
||||
|
||||
if (newOpponentCard) {
|
||||
this.isAnimating = true;
|
||||
const cardObj = this.cardObjects.get(newOpponentCard.instanceId);
|
||||
|
|
@ -214,29 +238,48 @@ export class BattleScene extends Phaser.Scene {
|
|||
this.isAnimating = true;
|
||||
this.statusText.setText('Deploying...');
|
||||
|
||||
// Snapshot player lanes before deploy so we can detect the new card
|
||||
// Snapshot both sides before deploy so we can detect new cards
|
||||
const oldPlayerIds = new Set(
|
||||
this.engine.getState().player.lanes.map(c => c.instanceId)
|
||||
);
|
||||
const oldOpponentIds = new Set(
|
||||
this.engine.getState().opponent.lanes.map(c => c.instanceId)
|
||||
);
|
||||
|
||||
// Deploy + pre-attack phases (NO damage applied yet)
|
||||
// On player-first turns, beginCommit also deploys the AI card
|
||||
this.engine.beginCommit(chosenCard);
|
||||
|
||||
// Render the field with the newly deployed card(s) but pre-combat HP
|
||||
// Render the field with all newly deployed cards but pre-combat HP
|
||||
this._renderState();
|
||||
|
||||
const newPlayerCard = chosenCard
|
||||
? this.engine.getState().player.lanes.find(c => !oldPlayerIds.has(c.instanceId))
|
||||
: null;
|
||||
|
||||
// On player-first turns the AI deployed inside beginCommit — animate it after player
|
||||
const newOpponentCard = this.playerGoesFirst
|
||||
? this.engine.getState().opponent.lanes.find(c => !oldOpponentIds.has(c.instanceId))
|
||||
: null;
|
||||
|
||||
const startAttacks = () => this.time.delayedCall(200, () => this._processNextAttackStep());
|
||||
|
||||
const animateOpponentThenAttack = () => {
|
||||
if (newOpponentCard) {
|
||||
const cardObj = this.cardObjects.get(newOpponentCard.instanceId);
|
||||
this.statusText.setText(`Enemy deploys: ${newOpponentCard.name}`);
|
||||
this._animateDeploy(cardObj, startAttacks);
|
||||
} else {
|
||||
startAttacks();
|
||||
}
|
||||
};
|
||||
|
||||
if (newPlayerCard) {
|
||||
const cardObj = this.cardObjects.get(newPlayerCard.instanceId);
|
||||
this.statusText.setText(`You deploy: ${newPlayerCard.name}`);
|
||||
this._animateDeploy(cardObj, startAttacks);
|
||||
this._animateDeploy(cardObj, animateOpponentThenAttack);
|
||||
} else {
|
||||
startAttacks();
|
||||
animateOpponentThenAttack();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -279,38 +322,28 @@ export class BattleScene extends Phaser.Scene {
|
|||
const berserkEvent = events.find(e => e.type === 'berserk');
|
||||
|
||||
this._animateAttack(attackEvent, () => {
|
||||
// Refresh / kill defender
|
||||
this._refreshCardAfterAttack(attackEvent.defender);
|
||||
|
||||
const proceed = () => {
|
||||
this._refreshCardAfterAttack(attackEvent.attacker);
|
||||
this.time.delayedCall(220, () => this._processNextAttackStep());
|
||||
};
|
||||
|
||||
if (berserkEvent) {
|
||||
const cardObj = this.cardObjects.get(berserkEvent.card.instanceId)
|
||||
?? this.commanderObjects?.get(berserkEvent.card.instanceId);
|
||||
if (cardObj) {
|
||||
// Rewind ATK text to pre-gain value so animation transitions old → new
|
||||
cardObj.atkText.setText(`${berserkEvent.card.currentAttack - berserkEvent.gain}`);
|
||||
cardObj.animateBerserkGain(berserkEvent.gain, proceed);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
proceed();
|
||||
});
|
||||
this._refreshCardAfterAttack(attackEvent.attacker);
|
||||
this.time.delayedCall(220, () => this._processNextAttackStep());
|
||||
}, berserkEvent);
|
||||
}
|
||||
|
||||
// Update a card's HP bar and stats at the moment of impact, then handle death.
|
||||
_refreshCardAfterAttack(cardData) {
|
||||
if (!cardData) return;
|
||||
const obj = this.cardObjects.get(cardData.instanceId)
|
||||
?? this.commanderObjects?.get(cardData.instanceId);
|
||||
?? this.commanderObjects?.get(cardData.instanceId);
|
||||
if (!obj) return;
|
||||
obj.refresh(); // updates HP bar + stats text to current values
|
||||
// Commanders never get destroyed — the win-check handles game-over
|
||||
if (cardData.currentHP <= 0 && !obj.isCommander) {
|
||||
// Explosion sprite centered on the card
|
||||
const explosionSprite = this.add.sprite(obj.x, obj.y, 'attacks')
|
||||
.setDisplaySize(630, 630)
|
||||
.setDepth(23);
|
||||
explosionSprite.play('explosion_anim');
|
||||
explosionSprite.once('animationcomplete', () => { if (explosionSprite.scene) explosionSprite.destroy(); });
|
||||
|
||||
this.cardObjects.delete(cardData.instanceId);
|
||||
this.tweens.killTweensOf(obj);
|
||||
obj.setScale(1).setDepth(0);
|
||||
|
|
@ -318,7 +351,7 @@ export class BattleScene extends Phaser.Scene {
|
|||
targets: obj,
|
||||
scaleX: 1.25, scaleY: 1.25,
|
||||
alpha: 0,
|
||||
duration: 480,
|
||||
duration: 400,
|
||||
ease: 'Power2',
|
||||
onComplete: () => { if (obj.scene) obj.destroy(); }
|
||||
});
|
||||
|
|
@ -350,20 +383,20 @@ export class BattleScene extends Phaser.Scene {
|
|||
// Animate a single attack.
|
||||
// Cards slide to center (attacker left, defender right), scale up, hold while
|
||||
// particle burst + shake + damage number play, then scale down and slide back.
|
||||
_animateAttack(event, onComplete) {
|
||||
const SCALE = 1.78;
|
||||
_animateAttack(event, onComplete, berserkEvent = null) {
|
||||
const SCALE = 1.78;
|
||||
const SCALE_UP_MS = 280;
|
||||
const HOLD_MS = 3100;
|
||||
const HOLD_MS = 3100;
|
||||
const SCALE_DN_MS = 280;
|
||||
const ATTACK_MS = SCALE_UP_MS + HOLD_MS + SCALE_DN_MS; // 1660 ms
|
||||
const ATTACK_MS = SCALE_UP_MS + HOLD_MS + SCALE_DN_MS; // 1660 ms
|
||||
|
||||
const { width, height } = this.scale;
|
||||
const cardW = 260;
|
||||
const enlargedW = cardW * SCALE; // ~463px at 1.78×
|
||||
const gap = enlargedW * 0.75; // ~347px between enlarged cards
|
||||
const cardW = 260;
|
||||
const enlargedW = cardW * SCALE; // ~463px at 1.78×
|
||||
const gap = enlargedW * 0.75; // ~347px between enlarged cards
|
||||
const attackerDestX = width / 2 - gap / 2 - enlargedW / 2; // ~554
|
||||
const defenderDestX = width / 2 + gap / 2 + enlargedW / 2; // ~1366
|
||||
const centerY = height / 2; // 540
|
||||
const centerY = height / 2; // 540
|
||||
|
||||
const _lookup = id => id && (this.cardObjects.get(id) || this.commanderObjects?.get(id));
|
||||
const attackerObj = _lookup(event.attacker.instanceId);
|
||||
|
|
@ -422,7 +455,7 @@ export class BattleScene extends Phaser.Scene {
|
|||
});
|
||||
};
|
||||
|
||||
enlargeCard(attackerObj, event.attacker, attackerDestX, attackerOrigX, attackerOrigY);
|
||||
enlargeCard(attackerObj, event.attacker, attackerDestX, attackerOrigX, attackerOrigY);
|
||||
enlargeCard(defenderObj, event.defender ?? { currentHP: 1 }, defenderDestX, defenderOrigX, defenderOrigY);
|
||||
|
||||
// ── VS image — appears between cards once they arrive at center ───────────
|
||||
|
|
@ -443,11 +476,24 @@ export class BattleScene extends Phaser.Scene {
|
|||
});
|
||||
});
|
||||
|
||||
// ── Attacker: particle burst — fires once card has arrived at center ──────
|
||||
// ── Attacker: attack sprite + particle burst — fires once card arrives at center ──
|
||||
if (attackerObj) {
|
||||
const enlargedH = 364 * SCALE;
|
||||
this.time.delayedCall(SCALE_UP_MS, () => {
|
||||
if (!attackerObj.scene) return;
|
||||
|
||||
// Gun turret animation centered on the attacking card
|
||||
const attackSprite = this.add.sprite(
|
||||
attackerDestX,
|
||||
centerY,
|
||||
'attacks'
|
||||
).setDisplaySize(480, 480).setDepth(22);
|
||||
attackSprite.play('attack_anim');
|
||||
attackSprite.once('animationcomplete', () => {
|
||||
if (attackSprite.scene) attackSprite.destroy();
|
||||
this._onAttackReconcile(event, berserkEvent);
|
||||
});
|
||||
|
||||
const emitter = this.add.particles(attackerDestX, centerY, 'particle_dot', {
|
||||
speed: { min: 90, max: 300 },
|
||||
scale: { start: 1.8, end: 0 },
|
||||
|
|
@ -516,6 +562,24 @@ export class BattleScene extends Phaser.Scene {
|
|||
this.time.delayedCall(ATTACK_MS, onComplete);
|
||||
}
|
||||
|
||||
// Fires when the attack animation ends — attach all post-attack consequences here.
|
||||
_onAttackReconcile(event, berserkEvent) {
|
||||
const _lookup = id => id && (this.cardObjects.get(id) || this.commanderObjects?.get(id));
|
||||
const defenderObj = _lookup(event.defender?.instanceId);
|
||||
const attackerObj = _lookup(event.attacker?.instanceId);
|
||||
|
||||
// HP loss animation on defender
|
||||
if (defenderObj?.scene && event.damage > 0) {
|
||||
defenderObj.animateHPLoss(event.damage);
|
||||
}
|
||||
|
||||
// Berserk ATK gain animation on attacker
|
||||
if (berserkEvent && attackerObj?.scene) {
|
||||
attackerObj.atkText.setText(`${berserkEvent.card.currentAttack - berserkEvent.gain}`);
|
||||
attackerObj.animateBerserkGain(berserkEvent.gain);
|
||||
}
|
||||
}
|
||||
|
||||
_showCardPicker(hand) {
|
||||
const { width, height } = this.scale;
|
||||
|
||||
|
|
@ -547,7 +611,7 @@ export class BattleScene extends Phaser.Scene {
|
|||
this.pickerObjects.push(title);
|
||||
|
||||
const startX = width / 2 - totalW / 2 + cardW / 2;
|
||||
const cardY = panelY + 8;
|
||||
const cardY = panelY + 8;
|
||||
|
||||
hand.forEach((card, i) => {
|
||||
const x = startX + i * (cardW + gap);
|
||||
|
|
@ -704,6 +768,20 @@ export class BattleScene extends Phaser.Scene {
|
|||
backBtn.on('pointerdown', () => this.scene.start(this.missionData ? 'CampaignScene' : 'MainMenuScene'));
|
||||
}
|
||||
|
||||
_updateInitiativeIndicator(playerGoesFirst) {
|
||||
if (this.initiativeIndicator) {
|
||||
this.initiativeIndicator.destroy();
|
||||
this.initiativeIndicator = null;
|
||||
}
|
||||
if (!this.textures.exists('attacksFirst')) return;
|
||||
// Position the indicator to the left of the commander card (commander at x=150, width=240)
|
||||
const indicatorX = 22;
|
||||
const indicatorY = playerGoesFirst ? 715 : 305;
|
||||
this.initiativeIndicator = this.add.image(indicatorX, indicatorY, 'attacksFirst')
|
||||
.setDisplaySize(44, 44)
|
||||
.setDepth(5);
|
||||
}
|
||||
|
||||
_makeBackButton() {
|
||||
const bg = this.add.rectangle(80, 35, 180, 44, 0x333333)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ export class BootScene extends Phaser.Scene {
|
|||
preload() {
|
||||
// Load all JSON data files
|
||||
this.load.image('vs', 'assets/images/ui/vs.png');
|
||||
this.load.image('attacksFirst', 'assets/images/ui/attacksFirst.png');
|
||||
this.load.spritesheet('attacks', 'assets/images/spritesheets/attacks.png', { frameWidth: 460, frameHeight: 460 });
|
||||
this.load.json('cards', 'data/cards.json');
|
||||
this.load.json('packs', 'data/packs.json');
|
||||
this.load.json('missions', 'data/missions.json');
|
||||
|
|
|
|||
Loading…
Reference in New Issue