Normalize battle unit animation sprites

This commit is contained in:
2026-06-29 00:32:54 +09:00
parent cd4102a7c2
commit d1a9dcd37c
184 changed files with 178 additions and 84 deletions

View File

@@ -1017,8 +1017,11 @@ const unitActionColumns: Record<UnitActionPose, number> = {
attack: 0,
strategy: 1,
item: 2,
hurt: 3
hurt: 3,
celebrate: 4
};
const unitActionFrameCount = 4;
const unitActionPoseCount = 5;
type BattleLayout = {
mapX: number;
@@ -1066,10 +1069,9 @@ type UnitView = {
};
type UnitDirection = 'south' | 'east' | 'north' | 'west';
type UnitActionPose = 'attack' | 'strategy' | 'item' | 'hurt';
type UnitActionPose = 'attack' | 'strategy' | 'item' | 'hurt' | 'celebrate';
const levelUpCelebrationPoseSequence: UnitActionPose[] = ['strategy', 'item', 'attack', 'strategy', 'item', 'attack', 'strategy'];
const levelUpCelebrationStepOffsets = [0, -4, 0, -6, -2, 0, -3];
const levelUpCelebrationStepOffsets = [0, -7, -2, -9, 0, -6, -3, -8];
type BattlePhase = 'idle' | 'moving' | 'command' | 'targeting' | 'animating' | 'resolved';
type BattleCommand = 'attack' | 'strategy' | 'item' | 'wait';
@@ -5361,6 +5363,7 @@ export class BattleScene extends Phaser.Scene {
const sprite = this.add.sprite(-width / 2 + 56, 10, this.unitActionTexture(animation.unit), this.unitActionFrameIndex('south', this.celebrationPose(animation.unit)));
sprite.setDisplaySize(76, 76);
container.add(sprite);
void this.playUnitActionFrames(sprite, animation.unit, 'south', this.celebrationPose(animation.unit), 105, 2);
}
this.tweens.add({ targets: burst, angle: 240, scale: 1.34, alpha: 0.16, duration: 760, ease: 'Sine.easeOut' });
@@ -5404,26 +5407,26 @@ export class BattleScene extends Phaser.Scene {
const baseY = sprite.y;
const baseX = sprite.x;
const baseAngle = sprite.angle;
const preferredPose = this.celebrationPose(unit);
const poses = [preferredPose, ...levelUpCelebrationPoseSequence];
const pose = this.celebrationPose(unit);
const frames = Array.from({ length: unitActionFrameCount * 2 }, (_, index) => index % unitActionFrameCount);
return new Promise<void>((resolve) => {
poses.forEach((pose, index) => {
this.time.delayedCall(index * 120, () => {
frames.forEach((frame, index) => {
this.time.delayedCall(index * 105, () => {
if (!sprite.active) {
return;
}
sprite.setFrame(this.unitActionFrameIndex('south', pose));
this.setUnitActionFrame(sprite, unit, 'south', pose, frame);
sprite.setX(baseX + (index % 2 === 0 ? 0 : 1));
sprite.setY(baseY + levelUpCelebrationStepOffsets[index % levelUpCelebrationStepOffsets.length]);
sprite.setAngle(baseAngle);
});
});
this.time.delayedCall(poses.length * 120 + 90, () => {
this.time.delayedCall(frames.length * 105 + 80, () => {
if (sprite.active) {
sprite.setX(baseX);
sprite.setY(baseY);
sprite.setAngle(baseAngle);
sprite.setFrame(this.unitActionFrameIndex('south', preferredPose));
this.setUnitActionFrame(sprite, unit, 'south', pose, 0);
}
resolve();
});
@@ -7231,6 +7234,7 @@ export class BattleScene extends Phaser.Scene {
);
userSprite.setDepth(depth + 3);
userSprite.setDisplaySize(112, 112);
void this.playUnitActionFrames(userSprite, result.user, 'east', supportPose, 100, 2);
const targetSprite = this.trackCombatObject(this.add.sprite(left + panelWidth - 160, top + 146, this.unitTextureKey(result.target), this.unitFrameIndex('west')));
targetSprite.setDepth(depth + 3);
@@ -7631,6 +7635,7 @@ export class BattleScene extends Phaser.Scene {
const burst = this.add.star(-panelWidth / 2 + 56, -12, 8, 12, 34, 0xffdf7b, 0.72);
container.add([burst, sprite]);
this.tweens.add({ targets: burst, angle: 220, scale: 1.32, alpha: 0.18, duration: 760, ease: 'Sine.easeOut' });
void this.playUnitActionFrames(sprite, celebrant, 'south', this.celebrationPose(celebrant), 105, 2);
}
this.tweens.add({ targets: container, alpha: 1, scale: 1.04, duration: 180, ease: 'Back.easeOut' });
@@ -7764,13 +7769,7 @@ export class BattleScene extends Phaser.Scene {
}
private celebrationPose(unit: UnitData): UnitActionPose {
if (unit.id === 'liu-bei') {
return 'strategy';
}
if (unit.id === 'zhang-fei') {
return 'attack';
}
return 'item';
return 'celebrate';
}
private playGrowthCelebration(sprite: Phaser.GameObjects.Sprite, unit: UnitData) {
@@ -7846,6 +7845,7 @@ export class BattleScene extends Phaser.Scene {
const contactX = defenderX - direction * 126;
soundDirector.playMeleeRush(mounted);
void this.playUnitActionFrames(attackerSprite, result.attacker, 'east', 'attack', mounted ? 72 : 82);
this.createDustPuff(attackerX - direction * 18, groundY + 48, depth - 1, mounted ? 1.08 : 0.82);
this.tweens.add({
targets: attackerSprite,
@@ -7870,11 +7870,11 @@ export class BattleScene extends Phaser.Scene {
await this.delay(mounted ? 170 : 205);
soundDirector.playMeleeSwing(result.hit, result.critical);
attackerSprite.setTexture(this.unitActionTexture(result.attacker), this.unitActionFrameIndex('east', 'attack'));
this.setUnitActionFrame(attackerSprite, result.attacker, 'east', 'attack', 2);
this.createSlashArc(defenderX - direction * 68, groundY - 48, direction, depth + 2, result.critical);
if (result.hit) {
defenderSprite.setTexture(this.unitActionTexture(result.defender), this.unitActionFrameIndex(defenderDirection, 'hurt'));
void this.playUnitActionFrames(defenderSprite, result.defender, defenderDirection, 'hurt', 70);
this.cameras.main.shake(result.critical ? 130 : 90, result.critical ? 0.006 : 0.0035);
this.shakeCombatSprite(defenderSprite, defenderX, groundY, direction, result.critical);
await this.delay(result.critical ? 130 : 92);
@@ -7912,7 +7912,7 @@ export class BattleScene extends Phaser.Scene {
defenderDirection: UnitDirection,
direction: number
) {
attackerSprite.setTexture(this.unitActionTexture(result.attacker), this.unitActionFrameIndex('east', 'attack'));
void this.playUnitActionFrames(attackerSprite, result.attacker, 'east', 'attack', 88);
this.createBowDrawEffect(attackerX + direction * 44, groundY - 52, direction, depth + 2);
this.tweens.add({
targets: attackerSprite,
@@ -7939,7 +7939,7 @@ export class BattleScene extends Phaser.Scene {
await this.delay(result.hit ? 332 : 382);
if (result.hit) {
defenderSprite.setTexture(this.unitActionTexture(result.defender), this.unitActionFrameIndex(defenderDirection, 'hurt'));
void this.playUnitActionFrames(defenderSprite, result.defender, defenderDirection, 'hurt', 70);
this.createArrowImpact(defenderX - direction * 58, groundY - 60, direction, depth + 5, result.critical);
this.shakeCombatSprite(defenderSprite, defenderX, groundY, direction, result.critical);
arrow.destroy();
@@ -7978,7 +7978,7 @@ export class BattleScene extends Phaser.Scene {
soundDirector.playStrategyPulse();
}
attackerSprite.setTexture(this.unitActionTexture(result.attacker), this.unitActionFrameIndex('east', this.actionPoseForCommand(result.action)));
void this.playUnitActionFrames(attackerSprite, result.attacker, 'east', this.actionPoseForCommand(result.action), 92);
this.createCasterPulse(attackerX + direction * 48, groundY - 50, depth + 2, result.action === 'item' ? 0xd8b15f : 0x6cc5ff);
this.tweens.add({
targets: attackerSprite,
@@ -8005,7 +8005,7 @@ export class BattleScene extends Phaser.Scene {
});
await this.delay(450);
if (result.hit) {
defenderSprite.setTexture(this.unitActionTexture(result.defender), this.unitActionFrameIndex(defenderDirection, 'hurt'));
void this.playUnitActionFrames(defenderSprite, result.defender, defenderDirection, 'hurt', 70);
this.createSpecialImpact(defenderX - direction * 68, result.action === 'item' ? groundY - 20 : groundY - 56, depth + 4, result.action);
this.shakeCombatSprite(defenderSprite, defenderX, groundY, direction, result.critical);
}
@@ -9303,8 +9303,34 @@ export class BattleScene extends Phaser.Scene {
return this.textures.exists(actionKey) ? actionKey : base;
}
private unitActionFrameIndex(direction: UnitDirection, pose: UnitActionPose) {
return unitFrameRows[direction] * 4 + unitActionColumns[pose];
private unitActionFrameIndex(direction: UnitDirection, pose: UnitActionPose, frame = 0) {
const safeFrame = Phaser.Math.Clamp(frame, 0, unitActionFrameCount - 1);
return unitFrameRows[direction] * unitActionPoseCount * unitActionFrameCount + unitActionColumns[pose] * unitActionFrameCount + safeFrame;
}
private setUnitActionFrame(sprite: Phaser.GameObjects.Sprite, unit: UnitData, direction: UnitDirection, pose: UnitActionPose, frame = 0) {
sprite.setTexture(this.unitActionTexture(unit), this.unitActionFrameIndex(direction, pose, frame));
}
private playUnitActionFrames(
sprite: Phaser.GameObjects.Sprite,
unit: UnitData,
direction: UnitDirection,
pose: UnitActionPose,
frameDuration = 86,
loops = 1
) {
const totalFrames = unitActionFrameCount * Math.max(1, loops);
return new Promise<void>((resolve) => {
for (let index = 0; index < totalFrames; index += 1) {
this.time.delayedCall(index * frameDuration, () => {
if (sprite.active) {
this.setUnitActionFrame(sprite, unit, direction, pose, index % unitActionFrameCount);
}
});
}
this.time.delayedCall(totalFrames * frameDuration, () => resolve());
});
}
private actionPoseForCommand(action: DamageCommand): UnitActionPose {