diff --git a/src/assets/images/units/unit-guan-yu.png b/src/assets/images/units/unit-guan-yu.png new file mode 100644 index 0000000..2bd45f1 Binary files /dev/null and b/src/assets/images/units/unit-guan-yu.png differ diff --git a/src/assets/images/units/unit-liu-bei.png b/src/assets/images/units/unit-liu-bei.png new file mode 100644 index 0000000..b4b7397 Binary files /dev/null and b/src/assets/images/units/unit-liu-bei.png differ diff --git a/src/assets/images/units/unit-rebel-archer.png b/src/assets/images/units/unit-rebel-archer.png new file mode 100644 index 0000000..1b86915 Binary files /dev/null and b/src/assets/images/units/unit-rebel-archer.png differ diff --git a/src/assets/images/units/unit-rebel-cavalry.png b/src/assets/images/units/unit-rebel-cavalry.png new file mode 100644 index 0000000..b3c1179 Binary files /dev/null and b/src/assets/images/units/unit-rebel-cavalry.png differ diff --git a/src/assets/images/units/unit-rebel-leader.png b/src/assets/images/units/unit-rebel-leader.png new file mode 100644 index 0000000..7d4eb45 Binary files /dev/null and b/src/assets/images/units/unit-rebel-leader.png differ diff --git a/src/assets/images/units/unit-rebel.png b/src/assets/images/units/unit-rebel.png new file mode 100644 index 0000000..3c11141 Binary files /dev/null and b/src/assets/images/units/unit-rebel.png differ diff --git a/src/assets/images/units/unit-zhang-fei.png b/src/assets/images/units/unit-zhang-fei.png new file mode 100644 index 0000000..d632051 Binary files /dev/null and b/src/assets/images/units/unit-zhang-fei.png differ diff --git a/src/game/scenes/BattleScene.ts b/src/game/scenes/BattleScene.ts index 8cdc68a..bb289d9 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -28,6 +28,13 @@ const unitTexture: Record = { 'rebel-leader': 'unit-rebel-leader' }; +const unitFrameRows: Record = { + south: 0, + east: 1, + north: 2, + west: 3 +}; + type BattleLayout = { mapX: number; mapY: number; @@ -248,16 +255,16 @@ export class BattleScene extends Phaser.Scene { private drawUnits() { firstBattleUnits.forEach((unit) => { - const sizeRatio = unit.faction === 'ally' ? 1.08 : 1.02; + const sizeRatio = unit.faction === 'ally' ? 1.16 : 1.1; const textureBase = unitTexture[unit.id] ?? 'unit-rebel'; - const sprite = this.add.sprite(this.tileCenterX(unit.x), this.tileCenterY(unit.y), `${textureBase}-south-0`); + const sprite = this.add.sprite(this.tileCenterX(unit.x), this.tileCenterY(unit.y), textureBase, this.unitFrameIndex('south')); sprite.setName(`unit-${unit.id}`); sprite.setDisplaySize(this.layout.tileSize * sizeRatio, this.layout.tileSize * sizeRatio); sprite.setDepth(6); sprite.setInteractive({ useHandCursor: unit.faction === 'ally' }); sprite.on('pointerdown', () => this.selectUnit(unit)); - const label = this.add.text(sprite.x, sprite.y + this.layout.tileSize * 0.47, unit.name, { + const label = this.add.text(sprite.x, sprite.y + this.layout.tileSize * 0.52, unit.name, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '11px', color: unit.faction === 'ally' ? '#e7edf7' : '#ffebe7', @@ -960,7 +967,7 @@ export class BattleScene extends Phaser.Scene { this.tweens.add({ targets: view.label, x: targetX, - y: targetY + this.layout.tileSize * 0.47, + y: targetY + this.layout.tileSize * 0.52, duration, ease: 'Sine.easeInOut' }); @@ -975,7 +982,7 @@ export class BattleScene extends Phaser.Scene { private playUnitWalk(view: UnitView, direction: UnitDirection) { view.direction = direction; - view.sprite.setFlipX(direction === 'west'); + view.sprite.setFlipX(false); view.sprite.play(`${view.textureBase}-walk-${direction}`, true); } @@ -986,9 +993,12 @@ export class BattleScene extends Phaser.Scene { } private setUnitDirectionFrame(view: UnitView, direction: UnitDirection) { - const sourceDirection = direction === 'west' ? 'east' : direction; - view.sprite.setTexture(`${view.textureBase}-${sourceDirection}-0`); - view.sprite.setFlipX(direction === 'west'); + view.sprite.setTexture(view.textureBase, this.unitFrameIndex(direction)); + view.sprite.setFlipX(false); + } + + private unitFrameIndex(direction: UnitDirection, frame = 0) { + return unitFrameRows[direction] * 4 + frame; } private applyActedStyle(unit: UnitData) { diff --git a/src/game/scenes/BootScene.ts b/src/game/scenes/BootScene.ts index 17a6502..1942f66 100644 --- a/src/game/scenes/BootScene.ts +++ b/src/game/scenes/BootScene.ts @@ -1,53 +1,41 @@ import Phaser from 'phaser'; import firstBattleMapUrl from '../../assets/images/battle/first-battle-map.png'; -import titleBackgroundUrl from '../../assets/images/taoyuan-oath-title.png'; +import guanYuPortraitUrl from '../../assets/images/portraits/guan-yu.png'; +import liuBeiPortraitUrl from '../../assets/images/portraits/liu-bei.png'; +import zhangFeiPortraitUrl from '../../assets/images/portraits/zhang-fei.png'; import storyChaosUrl from '../../assets/images/story/01-yellow-turban-chaos.png'; import storyLiuBeiUrl from '../../assets/images/story/02-liu-bei-resolve.png'; import storyThreeHeroesUrl from '../../assets/images/story/03-three-heroes-meet.png'; import storyMilitiaUrl from '../../assets/images/story/04-volunteer-militia.png'; import storySortieUrl from '../../assets/images/story/05-first-sortie.png'; -import guanYuPortraitUrl from '../../assets/images/portraits/guan-yu.png'; -import liuBeiPortraitUrl from '../../assets/images/portraits/liu-bei.png'; -import zhangFeiPortraitUrl from '../../assets/images/portraits/zhang-fei.png'; -import { palette } from '../ui/palette'; - -type UnitPixelPalette = { - robe: number; - trim: number; - shade: number; - highlight?: number; - skin: number; - skinShade?: number; - hair: number; - weapon: number; - metal?: number; - accent: number; - outline?: number; - boot?: number; - beard?: boolean; - beardStyle?: 'short' | 'long' | 'wild'; - weaponKind?: 'dual' | 'sword' | 'spear' | 'glaive' | 'bow' | 'axe'; - headgear?: 'topknot' | 'turban' | 'cap' | 'helmet'; - mounted?: boolean; - cape?: boolean; -}; +import titleBackgroundUrl from '../../assets/images/taoyuan-oath-title.png'; +import guanYuUnitSheetUrl from '../../assets/images/units/unit-guan-yu.png'; +import liuBeiUnitSheetUrl from '../../assets/images/units/unit-liu-bei.png'; +import rebelArcherUnitSheetUrl from '../../assets/images/units/unit-rebel-archer.png'; +import rebelCavalryUnitSheetUrl from '../../assets/images/units/unit-rebel-cavalry.png'; +import rebelLeaderUnitSheetUrl from '../../assets/images/units/unit-rebel-leader.png'; +import rebelUnitSheetUrl from '../../assets/images/units/unit-rebel.png'; +import zhangFeiUnitSheetUrl from '../../assets/images/units/unit-zhang-fei.png'; type UnitDirection = 'south' | 'east' | 'north' | 'west'; -type DrawDirection = Exclude; -type UnitPixelPose = { - direction: DrawDirection; - frame: number; - bob: number; - step: number; - arm: number; +const unitWalkFrameCount = 4; +const unitSheetFrameSize = 313; +const unitSheetRows: Record = { + south: 0, + east: 1, + north: 2, + west: 3 }; -const unitWalkFrames = [ - { bob: 0, step: 0, arm: 0 }, - { bob: -1, step: -2, arm: 2 }, - { bob: 0, step: 0, arm: 0 }, - { bob: -1, step: 2, arm: -2 } +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 } ]; export class BootScene extends Phaser.Scene { @@ -66,16 +54,19 @@ export class BootScene extends Phaser.Scene { this.load.image('portrait-liu-bei', liuBeiPortraitUrl); this.load.image('portrait-guan-yu', guanYuPortraitUrl); this.load.image('portrait-zhang-fei', zhangFeiPortraitUrl); + + unitSheets.forEach(({ key, url }) => { + this.load.spritesheet(key, url, { + frameWidth: unitSheetFrameSize, + frameHeight: unitSheetFrameSize + }); + }); } create() { - this.createGeneratedTextures(); - this.scene.start('TitleScene'); - } - - private createGeneratedTextures() { this.createPetalTexture(); - this.createPixelUnitTextures(); + this.createUnitAnimations(); + this.scene.start('TitleScene'); } private createPetalTexture() { @@ -88,452 +79,24 @@ export class BootScene extends Phaser.Scene { graphics.destroy(); } - private createPixelUnitTextures() { - this.createPixelUnitTexture('unit-liu-bei', { - robe: palette.blue, - trim: 0xe7edf7, - shade: 0x1d3552, - highlight: 0x74a8df, - skin: 0xf0c79a, - skinShade: 0xc9895f, - hair: 0x1e1b17, - weapon: 0xd8d0ba, - metal: 0xf0f4e9, - accent: 0xd8b15f, - outline: 0x10151b, - boot: 0x172033, - weaponKind: 'dual', - headgear: 'topknot' - }); - this.createPixelUnitTexture('unit-guan-yu', { - robe: 0x2f7d45, - trim: 0xd7e6c5, - shade: 0x1f5131, - highlight: 0x65b36d, - skin: 0xd59b6e, - skinShade: 0xa86945, - hair: 0x241611, - weapon: 0xc7d2d8, - metal: 0xf0f5ee, - accent: 0xb64232, - outline: 0x0d1410, - boot: 0x15130e, - beard: true, - beardStyle: 'long', - weaponKind: 'glaive', - headgear: 'cap' - }); - this.createPixelUnitTexture('unit-zhang-fei', { - robe: 0x5b2b33, - trim: 0xefc67c, - shade: 0x351820, - highlight: 0x8a4552, - skin: 0xb97955, - skinShade: 0x7d4937, - hair: 0x16110f, - weapon: 0xcfd8d8, - metal: 0xf1f5ed, - accent: 0x15191f, - outline: 0x0b0909, - boot: 0x141010, - beard: true, - beardStyle: 'wild', - weaponKind: 'spear', - headgear: 'topknot' - }); - this.createPixelUnitTexture('unit-rebel', { - robe: 0xc8a032, - trim: 0x6e4f24, - shade: 0x7b6221, - highlight: 0xf0cf5a, - skin: 0xbc8053, - skinShade: 0x845237, - hair: 0x242015, - weapon: 0xbfc5bd, - metal: 0xe4e8d8, - accent: 0xf1d157, - outline: 0x17120c, - boot: 0x2d2115, - weaponKind: 'spear', - headgear: 'turban' - }); - this.createPixelUnitTexture('unit-rebel-archer', { - robe: 0xd0aa3f, - trim: 0x754a28, - shade: 0x765a22, - highlight: 0xe4c565, - skin: 0xbc8053, - skinShade: 0x845237, - hair: 0x262017, - weapon: 0x8f5b31, - metal: 0xd8d7bf, - accent: 0xf1d157, - outline: 0x17120c, - boot: 0x2d2115, - weaponKind: 'bow', - headgear: 'turban' - }); - this.createPixelUnitTexture('unit-rebel-cavalry', { - robe: 0xd0aa3f, - trim: 0x7a4b2a, - shade: 0x7a6428, - highlight: 0xf0cf5a, - skin: 0xbc8053, - skinShade: 0x845237, - hair: 0x262017, - weapon: 0xbfc5bd, - metal: 0xe4e8d8, - accent: 0x56351f, - outline: 0x17120c, - boot: 0x2d2115, - mounted: true, - weaponKind: 'spear', - headgear: 'turban' - }); - this.createPixelUnitTexture('unit-rebel-leader', { - robe: 0x9e7130, - trim: 0xf4d66f, - shade: 0x60421f, - highlight: 0xd59b45, - skin: 0xc98b5d, - skinShade: 0x8d5a3b, - hair: 0x1e1710, - weapon: 0xd4d7c6, - metal: 0xf1f1df, - accent: 0xe4c43d, - outline: 0x120e0a, - boot: 0x1d1710, - weaponKind: 'axe', - headgear: 'helmet', - cape: true, - beard: true, - beardStyle: 'short' - }); - } + private createUnitAnimations() { + unitSheets.forEach(({ key }) => { + (['south', 'east', 'north', 'west'] as UnitDirection[]).forEach((direction) => { + const animationKey = `${key}-walk-${direction}`; + if (this.anims.exists(animationKey)) { + return; + } - private createPixelUnitTexture(key: string, colors: UnitPixelPalette) { - (['south', 'east', 'north'] as DrawDirection[]).forEach((direction) => { - unitWalkFrames.forEach((frame, frameIndex) => { - this.createPixelUnitFrameTexture(`${key}-${direction}-${frameIndex}`, colors, { - direction, - frame: frameIndex, - ...frame + this.anims.create({ + key: animationKey, + frames: Array.from({ length: unitWalkFrameCount }, (_, frameIndex) => ({ + key, + frame: unitSheetRows[direction] * unitWalkFrameCount + frameIndex + })), + frameRate: 8, + repeat: -1 }); }); }); - this.createPixelUnitAnimations(key); - } - - private createPixelUnitAnimations(key: string) { - (['south', 'east', 'north', 'west'] as UnitDirection[]).forEach((direction) => { - const animationKey = `${key}-walk-${direction}`; - if (this.anims.exists(animationKey)) { - return; - } - - const sourceDirection = direction === 'west' ? 'east' : direction; - this.anims.create({ - key: animationKey, - frames: unitWalkFrames.map((_, frameIndex) => ({ - key: `${key}-${sourceDirection}-${frameIndex}` - })), - frameRate: 8, - repeat: -1 - }); - }); - } - - private createPixelUnitFrameTexture(key: string, colors: UnitPixelPalette, pose: UnitPixelPose) { - const graphics = this.make.graphics({ x: 0, y: 0 }); - this.drawPixelShadow(graphics, colors, pose); - - if (colors.mounted) { - this.drawHorse(graphics, colors, pose); - } - - if (colors.cape) { - this.drawCape(graphics, colors, pose); - } - - this.drawWeapon(graphics, colors, pose, 'back'); - this.drawBody(graphics, colors, pose); - this.drawHead(graphics, colors, pose); - this.drawWeapon(graphics, colors, pose, 'front'); - - graphics.generateTexture(key, 64, 64); - graphics.destroy(); - this.textures.get(key).setFilter(Phaser.Textures.FilterMode.NEAREST); - } - - private px(graphics: Phaser.GameObjects.Graphics, color: number, x: number, y: number, width: number, height: number, alpha = 1) { - graphics.fillStyle(color, alpha); - graphics.fillRect(x, y, width, height); - } - - private drawPixelShadow(graphics: Phaser.GameObjects.Graphics, colors: UnitPixelPalette, pose: UnitPixelPose) { - const left = colors.mounted ? 7 : 15; - const width = colors.mounted ? 48 : 36; - const shift = pose.direction === 'east' ? 2 : 0; - this.px(graphics, 0x050609, left + shift, 58, width, 3, 0.5); - this.px(graphics, 0x050609, left + 5 + shift, 56, width - 10, 2, 0.38); - this.px(graphics, 0x050609, left + 12 + shift, 54, width - 24, 2, 0.28); - } - - private drawHorse(graphics: Phaser.GameObjects.Graphics, colors: UnitPixelPalette, pose: UnitPixelPose) { - const outline = colors.outline ?? 0x15100d; - const y = pose.bob; - this.px(graphics, outline, 9, 38 + y, 40, 13); - this.px(graphics, outline, 44, 30 + y, 9, 13); - this.px(graphics, outline, 6, 43, 8, 6); - this.px(graphics, 0x5b3824, 11, 39 + y, 36, 10); - this.px(graphics, 0x70472c, 13, 37 + y, 25, 4); - this.px(graphics, 0x8c5a36, 17, 38 + y, 19, 2); - this.px(graphics, 0x3a2418, 13 + Math.max(0, -pose.step), 49, 6, 9); - this.px(graphics, 0x3a2418, 25, 48, 5, 10); - this.px(graphics, 0x3a2418, 39 + Math.max(0, pose.step), 48, 6, 10); - this.px(graphics, outline, 12, 57, 8, 2); - this.px(graphics, outline, 24, 57, 8, 2); - this.px(graphics, outline, 38, 57, 9, 2); - this.px(graphics, 0x6a4328, 45, 32, 7, 9); - this.px(graphics, 0x21150f, 46, 29, 5, 4); - this.px(graphics, 0x21150f, 49, 34, 4, 2); - this.px(graphics, colors.accent, 21, 41, 17, 3); - this.px(graphics, colors.trim, 24, 38, 10, 2); - this.px(graphics, 0xd6b05a, 47, 36, 4, 2); - this.px(graphics, 0x0b0b0b, 49, 32, 1, 1); - } - - private drawCape(graphics: Phaser.GameObjects.Graphics, colors: UnitPixelPalette, pose: UnitPixelPose) { - const outline = colors.outline ?? 0x17100c; - const y = pose.bob; - const side = pose.direction === 'east' ? 4 : 0; - this.px(graphics, outline, 16 + side, 20 + y, 22, 24); - this.px(graphics, outline, 13 + side, 28 + y, 9, 13); - this.px(graphics, 0x6c2722, 17 + side, 21 + y, 20, 21); - this.px(graphics, 0x8f392c, 18 + side, 23 + y, 8, 16); - this.px(graphics, 0x3f1715, 14 + side, 35 + y, 18, 4); - this.px(graphics, 0xc46146, 20 + side, 24 + y, 3, 12); - } - - private drawWeapon(graphics: Phaser.GameObjects.Graphics, colors: UnitPixelPalette, pose: UnitPixelPose, layer: 'back' | 'front') { - const kind = colors.weaponKind ?? 'sword'; - const metal = colors.metal ?? colors.weapon; - const outline = colors.outline ?? 0x101010; - const y = pose.bob; - - if (layer === 'back' && kind === 'spear') { - const x = pose.direction === 'east' ? 44 : pose.direction === 'north' ? 13 : 50; - this.px(graphics, outline, x, 8 + y, 4, 47); - this.px(graphics, colors.weapon, x + 1, 10 + y, 2, 44); - this.px(graphics, outline, x - 2, 5 + y, 7, 5); - this.px(graphics, metal, x - 1, 3 + y, 5, 6); - this.px(graphics, metal, x - 4, 9 + y, 10, 2); - this.px(graphics, 0xffffff, x, 4 + y, 2, 3, 0.55); - return; - } - - if (layer === 'back' && kind === 'glaive') { - const x = pose.direction === 'north' ? 12 : 50; - this.px(graphics, outline, x, 5 + y, 4, 50); - this.px(graphics, colors.weapon, x + 1, 8 + y, 2, 46); - this.px(graphics, outline, x - 6, 6 + y, 13, 5); - this.px(graphics, metal, x - 5, 5 + y, 10, 5); - this.px(graphics, metal, x + 4, 9 + y, 5, 5); - this.px(graphics, metal, x - 3, 12 + y, 4, 6); - this.px(graphics, 0xffffff, x - 3, 6 + y, 6, 1, 0.48); - return; - } - - if (layer === 'back' && kind === 'bow') { - const x = pose.direction === 'east' ? 47 : 15; - this.px(graphics, outline, x, 17 + y, 3, 27); - this.px(graphics, outline, x + 3, 14 + y, 3, 6); - this.px(graphics, outline, x + 3, 41 + y, 3, 6); - this.px(graphics, colors.weapon, x + 2, 18 + y, 2, 25); - this.px(graphics, colors.weapon, x + 4, 15 + y, 2, 5); - this.px(graphics, colors.weapon, x + 4, 41 + y, 2, 5); - this.px(graphics, metal, x + 7, 19 + y, 1, 24, 0.8); - this.px(graphics, metal, x - 4, 32 + y, 12, 1); - this.px(graphics, metal, x - 4, 30 + y, 3, 5); - return; - } - - if (layer === 'back' && kind === 'axe') { - const x = pose.direction === 'north' ? 12 : 50; - this.px(graphics, outline, x, 13 + y, 4, 43); - this.px(graphics, colors.weapon, x + 1, 15 + y, 2, 40); - this.px(graphics, outline, x - 7, 11 + y, 16, 9); - this.px(graphics, metal, x - 6, 10 + y, 9, 8); - this.px(graphics, metal, x + 2, 13 + y, 7, 6); - this.px(graphics, 0xffffff, x - 5, 11 + y, 5, 1, 0.5); - return; - } - - if (layer === 'front' && kind === 'dual') { - if (pose.direction === 'north') { - this.px(graphics, outline, 18, 30 + y, 3, 15); - this.px(graphics, metal, 17, 27 + y, 2, 16); - this.px(graphics, outline, 45, 30 + y, 3, 15); - this.px(graphics, metal, 46, 27 + y, 2, 16); - return; - } - const side = pose.direction === 'east' ? 3 : 0; - this.px(graphics, outline, 46 + side, 26 + y, 3, 20); - this.px(graphics, metal, 47 + side, 22 + y, 2, 20); - this.px(graphics, metal, 49 + side, 20 + y, 3, 4); - this.px(graphics, colors.accent, 44 + side, 39 + y, 6, 2); - this.px(graphics, outline, 17 + pose.arm, 31 + y, 3, 17); - this.px(graphics, metal, 16 + pose.arm, 28 + y, 2, 18); - this.px(graphics, metal, 14 + pose.arm, 27 + y, 3, 4); - this.px(graphics, colors.accent, 16 + pose.arm, 42 + y, 6, 2); - return; - } - - if (layer === 'front' && kind === 'sword') { - this.px(graphics, outline, 48, 22 + y, 4, 28); - this.px(graphics, metal, 49, 18 + y, 2, 29); - this.px(graphics, metal, 47, 20 + y, 6, 3); - this.px(graphics, colors.accent, 46, 42 + y, 7, 2); - } - } - - private drawBody(graphics: Phaser.GameObjects.Graphics, colors: UnitPixelPalette, pose: UnitPixelPose) { - const outline = colors.outline ?? 0x111318; - const highlight = colors.highlight ?? colors.robe; - const boot = colors.boot ?? 0x16120e; - const bodyY = (colors.mounted ? 21 : 26) + pose.bob; - const legY = colors.mounted ? 40 : 46; - const side = pose.direction === 'east' ? 3 : 0; - - this.px(graphics, outline, 23 + side + Math.min(0, pose.step), legY, 10, 12); - this.px(graphics, outline, 35 + side + Math.max(0, pose.step), legY - 2, 10, 14); - this.px(graphics, colors.shade, 25 + side + Math.min(0, pose.step), legY, 7, 10); - this.px(graphics, colors.robe, 36 + side + Math.max(0, pose.step), legY - 1, 7, 11); - this.px(graphics, boot, 22, legY + 10, 12, 4); - this.px(graphics, boot, 35 + side, legY + 10, 12, 4); - this.px(graphics, 0xffffff, 27 + side, legY + 1, 3, 1, 0.3); - - if (pose.direction === 'east') { - this.px(graphics, outline, 25, bodyY + 2, 21, 23); - this.px(graphics, outline, 22 + pose.arm, bodyY + 9, 8, 14); - this.px(graphics, outline, 42 - pose.arm, bodyY + 8, 8, 14); - this.px(graphics, colors.shade, 27, bodyY + 4, 17, 20); - this.px(graphics, colors.robe, 28, bodyY + 3, 14, 20); - this.px(graphics, highlight, 30, bodyY + 5, 4, 14); - this.px(graphics, colors.shade, 39, bodyY + 6, 4, 16); - this.px(graphics, colors.robe, 23 + pose.arm, bodyY + 10, 7, 12); - this.px(graphics, colors.robe, 42 - pose.arm, bodyY + 9, 7, 11); - } else { - const backShade = pose.direction === 'north' ? colors.robe : colors.shade; - this.px(graphics, outline, 21, bodyY + 2, 25, 23); - this.px(graphics, outline, 18, bodyY + 8, 8, 16); - this.px(graphics, outline, 42, bodyY + 6, 8, 16); - this.px(graphics, backShade, 23, bodyY + 4, 21, 21); - this.px(graphics, colors.robe, 24, bodyY + 3, 18, 20); - this.px(graphics, highlight, 26, bodyY + 5, 5, 15); - this.px(graphics, colors.shade, 37, bodyY + 5, 5, 17); - this.px(graphics, colors.robe, 19 + pose.arm, bodyY + 9, 7, 13); - this.px(graphics, colors.robe, 42 - pose.arm, bodyY + 8, 7, 12); - this.px(graphics, highlight, 20 + pose.arm, bodyY + 10, 3, 9); - } - - this.px(graphics, colors.trim, 24, bodyY + 4, 18, 2); - this.px(graphics, colors.trim, 25, bodyY + 12, 17, 2); - this.px(graphics, colors.trim, 23, bodyY + 20, 22, 2); - this.px(graphics, colors.trim, 29, bodyY + 6, 3, 16); - this.px(graphics, colors.accent, 31, bodyY, 8, 4); - this.px(graphics, colors.accent, 27, bodyY + 15, 14, 2); - this.px(graphics, 0xf8e6a8, 26, bodyY + 7, 4, 2, 0.8); - this.px(graphics, 0xf8e6a8, 38, bodyY + 7, 3, 2, 0.78); - this.px(graphics, 0xf8e6a8, 27, bodyY + 18, 13, 1, 0.55); - - if (pose.direction !== 'north') { - this.px(graphics, colors.skin, 17 + pose.arm, bodyY + 15, 4, 6); - this.px(graphics, colors.skin, 48 - pose.arm, bodyY + 13, 4, 6); - this.px(graphics, colors.skinShade ?? 0x9a6747, 18 + pose.arm, bodyY + 20, 3, 2); - this.px(graphics, colors.skinShade ?? 0x9a6747, 49 - pose.arm, bodyY + 18, 3, 2); - } - - if (colors.weaponKind === 'bow') { - this.px(graphics, colors.accent, 39, bodyY + 14, 7, 3); - this.px(graphics, colors.metal ?? colors.weapon, 44, bodyY + 11, 9, 1); - } - } - - private drawHead(graphics: Phaser.GameObjects.Graphics, colors: UnitPixelPalette, pose: UnitPixelPose) { - const outline = colors.outline ?? 0x111318; - const skinShade = colors.skinShade ?? 0xb17450; - const headY = (colors.mounted ? 7 : 10) + pose.bob; - const side = pose.direction === 'east' ? 3 : 0; - - this.px(graphics, outline, 24 + side, headY + 1, pose.direction === 'east' ? 16 : 18, 16); - this.px(graphics, outline, 21 + side, headY + 6, 5, 7); - this.px(graphics, outline, 40 + side, headY + 6, 5, 7); - this.px(graphics, colors.skin, 25 + side, headY + 2, pose.direction === 'east' ? 14 : 16, 14); - if (pose.direction !== 'north') { - this.px(graphics, colors.skin, 22 + side, headY + 7, 4, 5); - this.px(graphics, colors.skin, 40 + side, headY + 7, 4, 5); - this.px(graphics, skinShade, 34 + side, headY + 9, 5, 5); - this.px(graphics, skinShade, 26 + side, headY + 14, 14, 2); - } - - if (colors.headgear === 'turban') { - this.px(graphics, colors.accent, 23 + side, headY - 1, 20, 6); - this.px(graphics, colors.trim, 25 + side, headY, 15, 2); - this.px(graphics, colors.accent, 31 + side, headY - 4, 8, 4); - this.px(graphics, colors.shade, 40 + side, headY + 1, 5, 5); - } else if (colors.headgear === 'helmet') { - this.px(graphics, outline, 23 + side, headY - 2, 20, 8); - this.px(graphics, colors.trim, 25 + side, headY - 3, 16, 6); - this.px(graphics, colors.accent, 29 + side, headY - 7, 8, 5); - this.px(graphics, colors.metal ?? colors.trim, 26 + side, headY + 3, 15, 2); - } else if (colors.headgear === 'cap') { - this.px(graphics, colors.hair, 23 + side, headY - 1, 20, 6); - this.px(graphics, colors.accent, 25 + side, headY - 2, 15, 3); - this.px(graphics, colors.accent, 30 + side, headY - 5, 8, 4); - } else { - this.px(graphics, colors.hair, 23 + side, headY - 2, 20, 6); - this.px(graphics, colors.hair, 22 + side, headY + 2, 6, 9); - this.px(graphics, colors.hair, 39 + side, headY + 2, 5, 9); - this.px(graphics, colors.accent, 26 + side, headY - 4, 13, 3); - this.px(graphics, colors.accent, 29 + side, headY - 7, 8, 4); - this.px(graphics, colors.hair, 31 + side, headY - 9, 5, 3); - } - - if (pose.direction !== 'north') { - this.px(graphics, 0x050609, 27 + side, headY + 8, 2, 1); - this.px(graphics, 0x050609, 36 + side, headY + 8, 2, 1); - this.px(graphics, 0x3a1d18, 31 + side, headY + 13, 5, 1); - this.px(graphics, 0xffffff, 29 + side, headY + 4, 5, 1, 0.5); - this.px(graphics, 0xffffff, 36 + side, headY + 4, 4, 1, 0.42); - if (pose.direction === 'east') { - this.px(graphics, skinShade, 41 + side, headY + 10, 3, 2); - } - } else { - this.px(graphics, colors.shade, 26, headY + 7, 14, 6, 0.55); - this.px(graphics, colors.trim, 28, headY + 12, 10, 2, 0.75); - } - - if (colors.beard && pose.direction !== 'north') { - const beardY = headY + 15; - this.px(graphics, outline, 26 + side, beardY - 1, 15, 8); - this.px(graphics, colors.hair, 27 + side, beardY, 13, 7); - this.px(graphics, 0x4b281d, 28 + side, beardY + 1, 3, 5); - this.px(graphics, 0x4b281d, 36 + side, beardY + 1, 2, 5); - - if (colors.beardStyle === 'long') { - this.px(graphics, outline, 28 + side, beardY + 6, 11, 12); - this.px(graphics, colors.hair, 29 + side, beardY + 6, 9, 11); - this.px(graphics, 0x160d0a, 31 + side, beardY + 14, 5, 5); - this.px(graphics, 0x6a3a2a, 31 + side, beardY + 7, 3, 9); - } else if (colors.beardStyle === 'wild') { - this.px(graphics, outline, 23 + side, beardY + 2, 20, 10); - this.px(graphics, colors.hair, 24 + side, beardY + 2, 18, 9); - this.px(graphics, outline, 27 + side, beardY + 10, 13, 8); - this.px(graphics, colors.hair, 28 + side, beardY + 10, 11, 7); - this.px(graphics, 0x4b281d, 26 + side, beardY + 5, 4, 4); - this.px(graphics, 0x4b281d, 37 + side, beardY + 5, 4, 4); - } - } } }