Replace unit sprites with tactical RPG sheets

This commit is contained in:
2026-06-22 03:10:11 +09:00
parent 40a0e4343f
commit 00f7af66e0
9 changed files with 68 additions and 495 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

@@ -28,6 +28,13 @@ const unitTexture: Record<string, string> = {
'rebel-leader': 'unit-rebel-leader'
};
const unitFrameRows: Record<UnitDirection, number> = {
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) {

View File

@@ -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<UnitDirection, 'west'>;
type UnitPixelPose = {
direction: DrawDirection;
frame: number;
bob: number;
step: number;
arm: number;
const unitWalkFrameCount = 4;
const unitSheetFrameSize = 313;
const unitSheetRows: Record<UnitDirection, number> = {
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);
}
}
}
}