Add combat action sprite sheets

This commit is contained in:
2026-06-22 15:06:27 +09:00
parent 3538a3aaaa
commit f30b99a78e
11 changed files with 279 additions and 12 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -42,6 +42,13 @@ const unitFrameRows: Record<UnitDirection, number> = {
west: 3
};
const unitActionColumns: Record<UnitActionPose, number> = {
attack: 0,
strategy: 1,
item: 2,
hurt: 3
};
type BattleLayout = {
mapX: number;
mapY: number;
@@ -83,6 +90,7 @@ type UnitView = {
};
type UnitDirection = 'south' | 'east' | 'north' | 'west';
type UnitActionPose = 'attack' | 'strategy' | 'item' | 'hurt';
type BattlePhase = 'idle' | 'moving' | 'command' | 'targeting' | 'animating' | 'resolved';
type BattleCommand = 'attack' | 'strategy' | 'item' | 'wait';
@@ -2805,7 +2813,12 @@ export class BattleScene extends Phaser.Scene {
const attackerDirection = 'east';
const defenderDirection = 'west';
const attackerSprite = this.trackCombatObject(
this.add.sprite(attackerX, groundY, unitTexture[result.attacker.id] ?? 'unit-rebel', this.unitFrameIndex(attackerDirection))
this.add.sprite(
attackerX,
groundY,
this.unitActionTexture(result.attacker),
this.unitActionFrameIndex(attackerDirection, this.actionPoseForCommand(result.action))
)
);
attackerSprite.setDepth(depth + 6);
attackerSprite.setDisplaySize(126, 126);
@@ -2824,7 +2837,7 @@ export class BattleScene extends Phaser.Scene {
attackerStatus.expText.setText(`${result.characterGrowth.previousExp} / ${result.characterGrowth.next}`);
await this.delay(180);
await this.playCombatActionMotion(result, attackerSprite, defenderSprite, attackerX, defenderX, groundY, depth + 10);
await this.playCombatActionMotion(result, attackerSprite, defenderSprite, attackerX, defenderX, groundY, depth + 10, defenderDirection);
if (result.hit) {
this.showCombatImpact(result, defenderX, groundY - 46, depth + 14);
} else {
@@ -2902,7 +2915,10 @@ export class BattleScene extends Phaser.Scene {
title.setOrigin(0.5, 0);
title.setDepth(depth + 2);
const userSprite = this.trackCombatObject(this.add.sprite(left + 160, top + 146, unitTexture[result.user.id] ?? 'unit-rebel', this.unitFrameIndex('east')));
const supportPose: UnitActionPose = result.usable.command === 'strategy' ? 'strategy' : 'item';
const userSprite = this.trackCombatObject(
this.add.sprite(left + 160, top + 146, this.unitActionTexture(result.user), this.unitActionFrameIndex('east', supportPose))
);
userSprite.setDepth(depth + 3);
userSprite.setDisplaySize(112, 112);
@@ -3053,7 +3069,8 @@ export class BattleScene extends Phaser.Scene {
attackerX: number,
defenderX: number,
groundY: number,
depth: number
depth: number,
defenderDirection: UnitDirection
) {
const direction = result.attacker.faction === 'ally' ? 1 : -1;
if (result.action === 'attack') {
@@ -3066,6 +3083,9 @@ export class BattleScene extends Phaser.Scene {
ease: 'Sine.easeInOut'
});
await this.delay(180);
if (result.hit) {
defenderSprite.setTexture(this.unitActionTexture(result.defender), this.unitActionFrameIndex(defenderDirection, 'hurt'));
}
this.tweens.add({
targets: defenderSprite,
x: defenderX + direction * (result.hit ? 12 : 28),
@@ -3096,6 +3116,9 @@ export class BattleScene extends Phaser.Scene {
ease: 'Cubic.easeIn'
});
await this.delay(450);
if (result.hit) {
defenderSprite.setTexture(this.unitActionTexture(result.defender), this.unitActionFrameIndex(defenderDirection, 'hurt'));
}
projectile.destroy();
}
@@ -3746,6 +3769,26 @@ export class BattleScene extends Phaser.Scene {
return unitFrameRows[direction] * 4 + frame;
}
private unitActionTexture(unit: UnitData) {
const base = unitTexture[unit.id] ?? 'unit-rebel';
const actionKey = `${base}-actions`;
return this.textures.exists(actionKey) ? actionKey : base;
}
private unitActionFrameIndex(direction: UnitDirection, pose: UnitActionPose) {
return unitFrameRows[direction] * 4 + unitActionColumns[pose];
}
private actionPoseForCommand(action: DamageCommand): UnitActionPose {
if (action === 'strategy') {
return 'strategy';
}
if (action === 'item') {
return 'item';
}
return 'attack';
}
private applyActedStyle(unit: UnitData) {
const view = this.unitViews.get(unit.id);
if (!view) {

View File

@@ -9,12 +9,19 @@ import storyThreeHeroesUrl from '../../assets/images/story/03-three-heroes-meet.
import storyMilitiaUrl from '../../assets/images/story/04-volunteer-militia.png';
import storySortieUrl from '../../assets/images/story/05-first-sortie.png';
import titleBackgroundUrl from '../../assets/images/taoyuan-oath-title.png';
import guanYuActionSheetUrl from '../../assets/images/units/unit-guan-yu-actions.png';
import guanYuUnitSheetUrl from '../../assets/images/units/unit-guan-yu.png';
import liuBeiActionSheetUrl from '../../assets/images/units/unit-liu-bei-actions.png';
import liuBeiUnitSheetUrl from '../../assets/images/units/unit-liu-bei.png';
import rebelArcherActionSheetUrl from '../../assets/images/units/unit-rebel-archer-actions.png';
import rebelArcherUnitSheetUrl from '../../assets/images/units/unit-rebel-archer.png';
import rebelCavalryActionSheetUrl from '../../assets/images/units/unit-rebel-cavalry-actions.png';
import rebelCavalryUnitSheetUrl from '../../assets/images/units/unit-rebel-cavalry.png';
import rebelLeaderActionSheetUrl from '../../assets/images/units/unit-rebel-leader-actions.png';
import rebelLeaderUnitSheetUrl from '../../assets/images/units/unit-rebel-leader.png';
import rebelActionSheetUrl from '../../assets/images/units/unit-rebel-actions.png';
import rebelUnitSheetUrl from '../../assets/images/units/unit-rebel.png';
import zhangFeiActionSheetUrl from '../../assets/images/units/unit-zhang-fei-actions.png';
import zhangFeiUnitSheetUrl from '../../assets/images/units/unit-zhang-fei.png';
type UnitDirection = 'south' | 'east' | 'north' | 'west';
@@ -31,13 +38,13 @@ const unitSheetRows: Record<UnitDirection, number> = {
};
const unitSheets = [
{ key: 'unit-liu-bei', url: liuBeiUnitSheetUrl },
{ key: 'unit-guan-yu', url: guanYuUnitSheetUrl },
{ key: 'unit-zhang-fei', url: zhangFeiUnitSheetUrl },
{ key: 'unit-rebel', url: rebelUnitSheetUrl },
{ key: 'unit-rebel-archer', url: rebelArcherUnitSheetUrl },
{ key: 'unit-rebel-cavalry', url: rebelCavalryUnitSheetUrl },
{ key: 'unit-rebel-leader', url: rebelLeaderUnitSheetUrl }
{ key: 'unit-liu-bei', url: liuBeiUnitSheetUrl, actionKey: 'unit-liu-bei-actions', actionUrl: liuBeiActionSheetUrl },
{ key: 'unit-guan-yu', url: guanYuUnitSheetUrl, actionKey: 'unit-guan-yu-actions', actionUrl: guanYuActionSheetUrl },
{ key: 'unit-zhang-fei', url: zhangFeiUnitSheetUrl, actionKey: 'unit-zhang-fei-actions', actionUrl: zhangFeiActionSheetUrl },
{ key: 'unit-rebel', url: rebelUnitSheetUrl, actionKey: 'unit-rebel-actions', actionUrl: rebelActionSheetUrl },
{ key: 'unit-rebel-archer', url: rebelArcherUnitSheetUrl, actionKey: 'unit-rebel-archer-actions', actionUrl: rebelArcherActionSheetUrl },
{ key: 'unit-rebel-cavalry', url: rebelCavalryUnitSheetUrl, actionKey: 'unit-rebel-cavalry-actions', actionUrl: rebelCavalryActionSheetUrl },
{ key: 'unit-rebel-leader', url: rebelLeaderUnitSheetUrl, actionKey: 'unit-rebel-leader-actions', actionUrl: rebelLeaderActionSheetUrl }
];
export class BootScene extends Phaser.Scene {
@@ -57,11 +64,15 @@ export class BootScene extends Phaser.Scene {
this.load.image('portrait-guan-yu', guanYuPortraitUrl);
this.load.image('portrait-zhang-fei', zhangFeiPortraitUrl);
unitSheets.forEach(({ key, url }) => {
unitSheets.forEach(({ key, url, actionKey, actionUrl }) => {
this.load.spritesheet(key, url, {
frameWidth: unitSheetFrameSize,
frameHeight: unitSheetFrameSize
});
this.load.spritesheet(actionKey, actionUrl, {
frameWidth: unitSheetFrameSize,
frameHeight: unitSheetFrameSize
});
});
}