Add combat action sprite sheets
BIN
src/assets/images/units/unit-guan-yu-actions.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
src/assets/images/units/unit-liu-bei-actions.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
src/assets/images/units/unit-rebel-actions.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
src/assets/images/units/unit-rebel-archer-actions.png
Normal file
|
After Width: | Height: | Size: 913 KiB |
BIN
src/assets/images/units/unit-rebel-cavalry-actions.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
src/assets/images/units/unit-rebel-leader-actions.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
src/assets/images/units/unit-zhang-fei-actions.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||