Replace unit sprites with tactical RPG sheets
BIN
src/assets/images/units/unit-guan-yu.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
src/assets/images/units/unit-liu-bei.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
src/assets/images/units/unit-rebel-archer.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
src/assets/images/units/unit-rebel-cavalry.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
src/assets/images/units/unit-rebel-leader.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
src/assets/images/units/unit-rebel.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
src/assets/images/units/unit-zhang-fei.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||