Use file-based faction unit sprites

This commit is contained in:
2026-06-25 01:08:39 +09:00
parent 2f66dd155a
commit 16eb8f0be7
40 changed files with 95 additions and 77 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1003 KiB

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: 905 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 730 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

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.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1000 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 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: 728 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1002 KiB

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: 904 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 KiB

View File

@@ -5138,16 +5138,40 @@ export class BattleScene extends Phaser.Scene {
}
const { x, y, width, height, depth } = animation.flashArea;
const spriteX = Math.min(x + width - 46, animation.valueText.x + 34);
const spriteY = y + height / 2 + 4;
const spriteX = Math.min(x + width - 52, animation.valueText.x + 40);
const spriteY = y + height / 2 + 2;
this.createResultLevelUpBurst(spriteX, spriteY, depth + 4);
const sprite = this.trackResultObject(
this.add.sprite(spriteX, spriteY, this.unitActionTexture(animation.unit), this.unitActionFrameIndex('south', 'attack'))
this.add.sprite(spriteX, spriteY + 2, this.unitActionTexture(animation.unit), this.unitActionFrameIndex('south', this.celebrationPose(animation.unit)))
);
sprite.setDisplaySize(58, 58);
sprite.setDepth(depth + 4);
sprite.setDisplaySize(70, 70);
sprite.setDepth(depth + 6);
const banner = this.trackResultObject(this.add.text(spriteX, spriteY - 44, 'LEVEL UP', {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '13px',
color: '#fff2b8',
fontStyle: '700',
stroke: '#2b1606',
strokeThickness: 3
}));
banner.setOrigin(0.5);
banner.setDepth(depth + 7);
await this.playUnitCelebrationFrames(sprite);
}
private createResultLevelUpBurst(x: number, y: number, depth: number) {
const ring = this.trackResultObject(this.add.circle(x, y + 2, 33));
ring.setStrokeStyle(3, 0xffdf7b, 0.84);
ring.setDepth(depth);
const halo = this.trackResultObject(this.add.circle(x, y + 4, 22, 0xffdf7b, 0.12));
halo.setDepth(depth - 1);
const weaponFlash = this.trackResultObject(this.add.star(x + 22, y - 28, 5, 4, 15, 0xfff2b0, 0.92));
weaponFlash.setDepth(depth + 2);
this.tweens.add({ targets: ring, angle: 180, alpha: 0, duration: 680, ease: 'Sine.easeOut' });
this.tweens.add({ targets: halo, alpha: 0.02, duration: 680, ease: 'Sine.easeOut' });
this.tweens.add({ targets: weaponFlash, y: weaponFlash.y - 12, angle: 180, alpha: 0, duration: 620, ease: 'Sine.easeOut' });
}
private flashTextColor(text: Phaser.GameObjects.Text, color: string, duration = 360) {
const previousColor = typeof text.style.color === 'string' ? text.style.color : '#f0e4c8';
text.setColor(color);
@@ -5160,20 +5184,23 @@ export class BattleScene extends Phaser.Scene {
private playUnitCelebrationFrames(sprite: Phaser.GameObjects.Sprite) {
const baseY = sprite.y;
const poses: UnitActionPose[] = ['attack', 'item', 'strategy', 'attack', 'item'];
const baseAngle = sprite.angle;
const poses: UnitActionPose[] = ['item', 'attack', 'strategy', 'attack', 'item', 'attack'];
return new Promise<void>((resolve) => {
poses.forEach((pose, index) => {
this.time.delayedCall(index * 120, () => {
this.time.delayedCall(index * 110, () => {
if (!sprite.active) {
return;
}
sprite.setFrame(this.unitActionFrameIndex('south', pose));
sprite.setY(baseY + (index % 2 === 0 ? -5 : 3));
sprite.setY(baseY + (index % 2 === 0 ? -7 : 2));
sprite.setAngle(baseAngle + (index % 2 === 0 ? -3 : 3));
});
});
this.time.delayedCall(poses.length * 120 + 80, () => {
this.time.delayedCall(poses.length * 110 + 80, () => {
if (sprite.active) {
sprite.setY(baseY);
sprite.setAngle(baseAngle);
sprite.setFrame(this.unitActionFrameIndex('south', 'item'));
}
resolve();

View File

@@ -47,12 +47,50 @@ import rebelLeaderActionSheetUrl from '../../assets/images/units/unit-rebel-lead
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 nanmanInfantryActionSheetUrl from '../../assets/images/units/unit-nanman-infantry-actions.png';
import nanmanInfantryUnitSheetUrl from '../../assets/images/units/unit-nanman-infantry.png';
import nanmanOfficerActionSheetUrl from '../../assets/images/units/unit-nanman-officer-actions.png';
import nanmanOfficerUnitSheetUrl from '../../assets/images/units/unit-nanman-officer.png';
import nanmanShamanActionSheetUrl from '../../assets/images/units/unit-nanman-shaman-actions.png';
import nanmanShamanUnitSheetUrl from '../../assets/images/units/unit-nanman-shaman.png';
import shuArcherActionSheetUrl from '../../assets/images/units/unit-shu-archer-actions.png';
import shuArcherUnitSheetUrl from '../../assets/images/units/unit-shu-archer.png';
import shuCavalryActionSheetUrl from '../../assets/images/units/unit-shu-cavalry-actions.png';
import shuCavalryUnitSheetUrl from '../../assets/images/units/unit-shu-cavalry.png';
import shuInfantryActionSheetUrl from '../../assets/images/units/unit-shu-infantry-actions.png';
import shuInfantryUnitSheetUrl from '../../assets/images/units/unit-shu-infantry.png';
import shuOfficerActionSheetUrl from '../../assets/images/units/unit-shu-officer-actions.png';
import shuOfficerUnitSheetUrl from '../../assets/images/units/unit-shu-officer.png';
import shuSpearmanActionSheetUrl from '../../assets/images/units/unit-shu-spearman-actions.png';
import shuSpearmanUnitSheetUrl from '../../assets/images/units/unit-shu-spearman.png';
import shuStrategistActionSheetUrl from '../../assets/images/units/unit-shu-strategist-actions.png';
import shuStrategistUnitSheetUrl from '../../assets/images/units/unit-shu-strategist.png';
import simaYiActionSheetUrl from '../../assets/images/units/unit-sima-yi-actions.png';
import simaYiUnitSheetUrl from '../../assets/images/units/unit-sima-yi.png';
import wangPingActionSheetUrl from '../../assets/images/units/unit-wang-ping-actions.png';
import wangPingUnitSheetUrl from '../../assets/images/units/unit-wang-ping.png';
import weiArcherActionSheetUrl from '../../assets/images/units/unit-wei-archer-actions.png';
import weiArcherUnitSheetUrl from '../../assets/images/units/unit-wei-archer.png';
import weiCavalryActionSheetUrl from '../../assets/images/units/unit-wei-cavalry-actions.png';
import weiCavalryUnitSheetUrl from '../../assets/images/units/unit-wei-cavalry.png';
import weiInfantryActionSheetUrl from '../../assets/images/units/unit-wei-infantry-actions.png';
import weiInfantryUnitSheetUrl from '../../assets/images/units/unit-wei-infantry.png';
import weiOfficerActionSheetUrl from '../../assets/images/units/unit-wei-officer-actions.png';
import weiOfficerUnitSheetUrl from '../../assets/images/units/unit-wei-officer.png';
import weiStrategistActionSheetUrl from '../../assets/images/units/unit-wei-strategist-actions.png';
import weiStrategistUnitSheetUrl from '../../assets/images/units/unit-wei-strategist.png';
import weiYanActionSheetUrl from '../../assets/images/units/unit-wei-yan-actions.png';
import weiYanUnitSheetUrl from '../../assets/images/units/unit-wei-yan.png';
import wuArcherActionSheetUrl from '../../assets/images/units/unit-wu-archer-actions.png';
import wuArcherUnitSheetUrl from '../../assets/images/units/unit-wu-archer.png';
import wuCavalryActionSheetUrl from '../../assets/images/units/unit-wu-cavalry-actions.png';
import wuCavalryUnitSheetUrl from '../../assets/images/units/unit-wu-cavalry.png';
import wuInfantryActionSheetUrl from '../../assets/images/units/unit-wu-infantry-actions.png';
import wuInfantryUnitSheetUrl from '../../assets/images/units/unit-wu-infantry.png';
import wuOfficerActionSheetUrl from '../../assets/images/units/unit-wu-officer-actions.png';
import wuOfficerUnitSheetUrl from '../../assets/images/units/unit-wu-officer.png';
import wuStrategistActionSheetUrl from '../../assets/images/units/unit-wu-strategist-actions.png';
import wuStrategistUnitSheetUrl from '../../assets/images/units/unit-wu-strategist.png';
import wuYiActionSheetUrl from '../../assets/images/units/unit-wu-yi-actions.png';
import wuYiUnitSheetUrl from '../../assets/images/units/unit-wu-yi.png';
import yanYanActionSheetUrl from '../../assets/images/units/unit-yan-yan-actions.png';
@@ -69,15 +107,6 @@ import { storyBackgroundAssets } from '../data/storyAssets';
type UnitDirection = 'south' | 'east' | 'north' | 'west';
type GeneratedUnitSheet = {
key: string;
sourceKey: string;
actionKey: string;
sourceActionKey: string;
tint: number;
alpha: number;
};
const unitWalkFrameCount = 4;
const unitSheetFrameSize = 313;
const unitWalkFrameRate = 8;
@@ -118,32 +147,29 @@ const sourceUnitSheets = [
{ key: 'unit-huang-quan', url: huangQuanUnitSheetUrl, actionKey: 'unit-huang-quan-actions', actionUrl: huangQuanActionSheetUrl },
{ key: 'unit-ma-chao', url: maChaoUnitSheetUrl, actionKey: 'unit-ma-chao-actions', actionUrl: maChaoActionSheetUrl },
{ key: 'unit-ma-dai', url: maDaiUnitSheetUrl, actionKey: 'unit-ma-dai-actions', actionUrl: maDaiActionSheetUrl },
{ key: 'unit-wang-ping', url: wangPingUnitSheetUrl, actionKey: 'unit-wang-ping-actions', actionUrl: wangPingActionSheetUrl }
{ key: 'unit-wang-ping', url: wangPingUnitSheetUrl, actionKey: 'unit-wang-ping-actions', actionUrl: wangPingActionSheetUrl },
{ key: 'unit-shu-infantry', url: shuInfantryUnitSheetUrl, actionKey: 'unit-shu-infantry-actions', actionUrl: shuInfantryActionSheetUrl },
{ key: 'unit-shu-spearman', url: shuSpearmanUnitSheetUrl, actionKey: 'unit-shu-spearman-actions', actionUrl: shuSpearmanActionSheetUrl },
{ key: 'unit-shu-archer', url: shuArcherUnitSheetUrl, actionKey: 'unit-shu-archer-actions', actionUrl: shuArcherActionSheetUrl },
{ key: 'unit-shu-cavalry', url: shuCavalryUnitSheetUrl, actionKey: 'unit-shu-cavalry-actions', actionUrl: shuCavalryActionSheetUrl },
{ key: 'unit-shu-strategist', url: shuStrategistUnitSheetUrl, actionKey: 'unit-shu-strategist-actions', actionUrl: shuStrategistActionSheetUrl },
{ key: 'unit-shu-officer', url: shuOfficerUnitSheetUrl, actionKey: 'unit-shu-officer-actions', actionUrl: shuOfficerActionSheetUrl },
{ key: 'unit-wei-infantry', url: weiInfantryUnitSheetUrl, actionKey: 'unit-wei-infantry-actions', actionUrl: weiInfantryActionSheetUrl },
{ key: 'unit-wei-archer', url: weiArcherUnitSheetUrl, actionKey: 'unit-wei-archer-actions', actionUrl: weiArcherActionSheetUrl },
{ key: 'unit-wei-cavalry', url: weiCavalryUnitSheetUrl, actionKey: 'unit-wei-cavalry-actions', actionUrl: weiCavalryActionSheetUrl },
{ key: 'unit-wei-strategist', url: weiStrategistUnitSheetUrl, actionKey: 'unit-wei-strategist-actions', actionUrl: weiStrategistActionSheetUrl },
{ key: 'unit-wei-officer', url: weiOfficerUnitSheetUrl, actionKey: 'unit-wei-officer-actions', actionUrl: weiOfficerActionSheetUrl },
{ key: 'unit-wu-infantry', url: wuInfantryUnitSheetUrl, actionKey: 'unit-wu-infantry-actions', actionUrl: wuInfantryActionSheetUrl },
{ key: 'unit-wu-archer', url: wuArcherUnitSheetUrl, actionKey: 'unit-wu-archer-actions', actionUrl: wuArcherActionSheetUrl },
{ key: 'unit-wu-cavalry', url: wuCavalryUnitSheetUrl, actionKey: 'unit-wu-cavalry-actions', actionUrl: wuCavalryActionSheetUrl },
{ key: 'unit-wu-strategist', url: wuStrategistUnitSheetUrl, actionKey: 'unit-wu-strategist-actions', actionUrl: wuStrategistActionSheetUrl },
{ key: 'unit-wu-officer', url: wuOfficerUnitSheetUrl, actionKey: 'unit-wu-officer-actions', actionUrl: wuOfficerActionSheetUrl },
{ key: 'unit-nanman-infantry', url: nanmanInfantryUnitSheetUrl, actionKey: 'unit-nanman-infantry-actions', actionUrl: nanmanInfantryActionSheetUrl },
{ key: 'unit-nanman-shaman', url: nanmanShamanUnitSheetUrl, actionKey: 'unit-nanman-shaman-actions', actionUrl: nanmanShamanActionSheetUrl },
{ key: 'unit-nanman-officer', url: nanmanOfficerUnitSheetUrl, actionKey: 'unit-nanman-officer-actions', actionUrl: nanmanOfficerActionSheetUrl }
];
const generatedUnitSheets: GeneratedUnitSheet[] = [
{ key: 'unit-shu-infantry', sourceKey: 'unit-guan-yu', actionKey: 'unit-shu-infantry-actions', sourceActionKey: 'unit-guan-yu-actions', tint: 0x2f7fba, alpha: 0.32 },
{ key: 'unit-shu-spearman', sourceKey: 'unit-zhang-fei', actionKey: 'unit-shu-spearman-actions', sourceActionKey: 'unit-zhang-fei-actions', tint: 0x4aa878, alpha: 0.3 },
{ key: 'unit-shu-archer', sourceKey: 'unit-rebel-archer', actionKey: 'unit-shu-archer-actions', sourceActionKey: 'unit-rebel-archer-actions', tint: 0x4b9bd5, alpha: 0.36 },
{ key: 'unit-shu-cavalry', sourceKey: 'unit-rebel-cavalry', actionKey: 'unit-shu-cavalry-actions', sourceActionKey: 'unit-rebel-cavalry-actions', tint: 0x488fc4, alpha: 0.36 },
{ key: 'unit-shu-strategist', sourceKey: 'unit-liu-bei', actionKey: 'unit-shu-strategist-actions', sourceActionKey: 'unit-liu-bei-actions', tint: 0x75b7d8, alpha: 0.28 },
{ key: 'unit-shu-officer', sourceKey: 'unit-guan-yu', actionKey: 'unit-shu-officer-actions', sourceActionKey: 'unit-guan-yu-actions', tint: 0x3f91c8, alpha: 0.36 },
{ key: 'unit-wei-infantry', sourceKey: 'unit-rebel', actionKey: 'unit-wei-infantry-actions', sourceActionKey: 'unit-rebel-actions', tint: 0x8b3030, alpha: 0.42 },
{ key: 'unit-wei-archer', sourceKey: 'unit-rebel-archer', actionKey: 'unit-wei-archer-actions', sourceActionKey: 'unit-rebel-archer-actions', tint: 0x9d3c3c, alpha: 0.4 },
{ key: 'unit-wei-cavalry', sourceKey: 'unit-rebel-cavalry', actionKey: 'unit-wei-cavalry-actions', sourceActionKey: 'unit-rebel-cavalry-actions', tint: 0x7a3540, alpha: 0.44 },
{ key: 'unit-wei-strategist', sourceKey: 'unit-rebel-archer', actionKey: 'unit-wei-strategist-actions', sourceActionKey: 'unit-rebel-archer-actions', tint: 0x7f5a8e, alpha: 0.38 },
{ key: 'unit-wei-officer', sourceKey: 'unit-rebel-leader', actionKey: 'unit-wei-officer-actions', sourceActionKey: 'unit-rebel-leader-actions', tint: 0x8f3940, alpha: 0.44 },
{ key: 'unit-wu-infantry', sourceKey: 'unit-rebel', actionKey: 'unit-wu-infantry-actions', sourceActionKey: 'unit-rebel-actions', tint: 0x0b8b86, alpha: 0.42 },
{ key: 'unit-wu-archer', sourceKey: 'unit-rebel-archer', actionKey: 'unit-wu-archer-actions', sourceActionKey: 'unit-rebel-archer-actions', tint: 0x169b91, alpha: 0.4 },
{ key: 'unit-wu-cavalry', sourceKey: 'unit-rebel-cavalry', actionKey: 'unit-wu-cavalry-actions', sourceActionKey: 'unit-rebel-cavalry-actions', tint: 0x087b86, alpha: 0.44 },
{ key: 'unit-wu-strategist', sourceKey: 'unit-rebel-archer', actionKey: 'unit-wu-strategist-actions', sourceActionKey: 'unit-rebel-archer-actions', tint: 0x3aa57e, alpha: 0.38 },
{ key: 'unit-wu-officer', sourceKey: 'unit-rebel-leader', actionKey: 'unit-wu-officer-actions', sourceActionKey: 'unit-rebel-leader-actions', tint: 0x168f83, alpha: 0.44 },
{ key: 'unit-nanman-infantry', sourceKey: 'unit-rebel', actionKey: 'unit-nanman-infantry-actions', sourceActionKey: 'unit-rebel-actions', tint: 0x8b6c2f, alpha: 0.42 },
{ key: 'unit-nanman-shaman', sourceKey: 'unit-rebel-archer', actionKey: 'unit-nanman-shaman-actions', sourceActionKey: 'unit-rebel-archer-actions', tint: 0x6e8a3a, alpha: 0.42 },
{ key: 'unit-nanman-officer', sourceKey: 'unit-rebel-leader', actionKey: 'unit-nanman-officer-actions', sourceActionKey: 'unit-rebel-leader-actions', tint: 0x9a6a2e, alpha: 0.46 }
];
const animatedUnitSheets = [...sourceUnitSheets, ...generatedUnitSheets].map(({ key }) => ({ key }));
const animatedUnitSheets = sourceUnitSheets.map(({ key }) => ({ key }));
export class BootScene extends Phaser.Scene {
constructor() {
@@ -176,7 +202,6 @@ export class BootScene extends Phaser.Scene {
create() {
this.createPetalTexture();
this.createEquipmentIconTextures();
this.createGeneratedUnitSheets();
this.createUnitAnimations();
this.scene.start('TitleScene');
}
@@ -441,40 +466,6 @@ export class BootScene extends Phaser.Scene {
graphics.strokePath();
}
private createGeneratedUnitSheets() {
generatedUnitSheets.forEach((sheet) => {
this.createTintedUnitSheet(sheet.key, sheet.sourceKey, sheet.tint, sheet.alpha);
this.createTintedUnitSheet(sheet.actionKey, sheet.sourceActionKey, sheet.tint, sheet.alpha);
});
}
private createTintedUnitSheet(targetKey: string, sourceKey: string, tint: number, alpha: number) {
if (this.textures.exists(targetKey) || !this.textures.exists(sourceKey)) {
return;
}
const sourceImage = this.textures.get(sourceKey).getSourceImage() as CanvasImageSource & { width: number; height: number };
const canvas = document.createElement('canvas');
canvas.width = sourceImage.width;
canvas.height = sourceImage.height;
const context = canvas.getContext('2d');
if (!context) {
return;
}
context.drawImage(sourceImage, 0, 0);
context.globalCompositeOperation = 'source-atop';
context.globalAlpha = alpha;
context.fillStyle = `#${tint.toString(16).padStart(6, '0')}`;
context.fillRect(0, 0, canvas.width, canvas.height);
context.globalCompositeOperation = 'source-over';
context.globalAlpha = 1;
this.textures.addSpriteSheet(targetKey, canvas as unknown as HTMLImageElement, {
frameWidth: unitSheetFrameSize,
frameHeight: unitSheetFrameSize
});
}
private createUnitAnimations() {
animatedUnitSheets.forEach(({ key }) => {
(['south', 'east', 'north', 'west'] as UnitDirection[]).forEach((direction) => {