import Phaser from 'phaser'; import fifthBattleMapUrl from '../../assets/images/battle/fifth-battle-map.svg'; import firstBattleMapUrl from '../../assets/images/battle/first-battle-map.png'; import fourthBattleMapUrl from '../../assets/images/battle/fourth-battle-map.svg'; import secondBattleMapUrl from '../../assets/images/battle/second-battle-map.svg'; import thirdBattleMapUrl from '../../assets/images/battle/third-battle-map.svg'; 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 titleBackgroundUrl from '../../assets/images/taoyuan-oath-title.png'; import guanYuActionSheetUrl from '../../assets/images/units/unit-guan-yu-actions.png'; import guanYuUnitSheetUrl from '../../assets/images/units/unit-guan-yu.png'; import liuBeiActionSheetUrl from '../../assets/images/units/unit-liu-bei-actions.png'; import liuBeiUnitSheetUrl from '../../assets/images/units/unit-liu-bei.png'; import rebelArcherActionSheetUrl from '../../assets/images/units/unit-rebel-archer-actions.png'; import rebelArcherUnitSheetUrl from '../../assets/images/units/unit-rebel-archer.png'; import rebelCavalryActionSheetUrl from '../../assets/images/units/unit-rebel-cavalry-actions.png'; import rebelCavalryUnitSheetUrl from '../../assets/images/units/unit-rebel-cavalry.png'; import rebelLeaderActionSheetUrl from '../../assets/images/units/unit-rebel-leader-actions.png'; 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 zhangFeiActionSheetUrl from '../../assets/images/units/unit-zhang-fei-actions.png'; import zhangFeiUnitSheetUrl from '../../assets/images/units/unit-zhang-fei.png'; type UnitDirection = 'south' | 'east' | 'north' | 'west'; const unitWalkFrameCount = 4; const unitSheetFrameSize = 313; const unitWalkFrameRate = 8; const unitIdleFrameRate = 4; const unitSheetRows: Record = { south: 0, east: 1, north: 2, west: 3 }; const unitSheets = [ { key: 'unit-liu-bei', url: liuBeiUnitSheetUrl, actionKey: 'unit-liu-bei-actions', actionUrl: liuBeiActionSheetUrl }, { key: 'unit-guan-yu', url: guanYuUnitSheetUrl, actionKey: 'unit-guan-yu-actions', actionUrl: guanYuActionSheetUrl }, { key: 'unit-zhang-fei', url: zhangFeiUnitSheetUrl, actionKey: 'unit-zhang-fei-actions', actionUrl: zhangFeiActionSheetUrl }, { key: 'unit-rebel', url: rebelUnitSheetUrl, actionKey: 'unit-rebel-actions', actionUrl: rebelActionSheetUrl }, { key: 'unit-rebel-archer', url: rebelArcherUnitSheetUrl, actionKey: 'unit-rebel-archer-actions', actionUrl: rebelArcherActionSheetUrl }, { key: 'unit-rebel-cavalry', url: rebelCavalryUnitSheetUrl, actionKey: 'unit-rebel-cavalry-actions', actionUrl: rebelCavalryActionSheetUrl }, { key: 'unit-rebel-leader', url: rebelLeaderUnitSheetUrl, actionKey: 'unit-rebel-leader-actions', actionUrl: rebelLeaderActionSheetUrl } ]; export class BootScene extends Phaser.Scene { constructor() { super('BootScene'); } preload() { this.load.image('title-taoyuan', titleBackgroundUrl); this.load.image('story-chaos', storyChaosUrl); this.load.image('story-liu-bei', storyLiuBeiUrl); this.load.image('story-three-heroes', storyThreeHeroesUrl); this.load.image('story-militia', storyMilitiaUrl); this.load.image('story-sortie', storySortieUrl); this.load.image('battle-map-first', firstBattleMapUrl); this.load.image('battle-map-second', secondBattleMapUrl); this.load.image('battle-map-third', thirdBattleMapUrl); this.load.image('battle-map-fourth', fourthBattleMapUrl); this.load.image('battle-map-fifth', fifthBattleMapUrl); 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, actionKey, actionUrl }) => { this.load.spritesheet(key, url, { frameWidth: unitSheetFrameSize, frameHeight: unitSheetFrameSize }); this.load.spritesheet(actionKey, actionUrl, { frameWidth: unitSheetFrameSize, frameHeight: unitSheetFrameSize }); }); } create() { this.createPetalTexture(); this.createEquipmentIconTextures(); this.createUnitAnimations(); this.scene.start('TitleScene'); } private createPetalTexture() { const graphics = this.make.graphics({ x: 0, y: 0 }); graphics.fillStyle(0xf0a6a4, 1); graphics.fillEllipse(12, 10, 20, 9); graphics.fillStyle(0xf7c7bd, 0.75); graphics.fillEllipse(15, 8, 10, 4); graphics.generateTexture('petal', 24, 18); graphics.destroy(); } private createEquipmentIconTextures() { this.createItemIcon('item-training-sword', (graphics) => this.drawSwordIcon(graphics, 0xbfd6e6, 0x6b4a2c)); this.createItemIcon('item-twin-oath-blades', (graphics) => { this.drawSwordIcon(graphics, 0xe8edf7, 0x8a5a32, -4); this.drawSwordIcon(graphics, 0xd8b15f, 0x8a5a32, 4); }); this.createItemIcon('item-green-dragon-glaive', (graphics) => this.drawPolearmIcon(graphics, 0x5fbf8f, 0xd8b15f)); this.createItemIcon('item-serpent-spear', (graphics) => this.drawSpearIcon(graphics, 0xe0e8ef, 0xb86b55)); this.createItemIcon('item-yellow-turban-saber', (graphics) => this.drawSaberIcon(graphics, 0xd8b15f, 0x9c6b2f)); this.createItemIcon('item-short-bow', (graphics) => this.drawBowIcon(graphics, 0xc58a4c, 0xe8dfca)); this.createItemIcon('item-leader-axe', (graphics) => this.drawAxeIcon(graphics, 0xd8dfe6, 0x8d5a3a)); this.createItemIcon('item-cloth-armor', (graphics) => this.drawRobeIcon(graphics, 0x91a8bc, 0xe8dfca)); this.createItemIcon('item-lamellar-armor', (graphics) => this.drawArmorIcon(graphics, 0x7d8792, 0xd8b15f)); this.createItemIcon('item-oath-robe', (graphics) => this.drawRobeIcon(graphics, 0x4e7aa8, 0xf0c0a8)); this.createItemIcon('item-reinforced-lamellar', (graphics) => this.drawArmorIcon(graphics, 0xb8c4ce, 0x59a6d6)); this.createItemIcon('item-rebel-vest', (graphics) => this.drawVestIcon(graphics, 0xb88b4a, 0xd8b15f)); this.createItemIcon('item-peach-charm', (graphics) => this.drawCharmIcon(graphics, 0xf0a6a4, 0xd8b15f)); this.createItemIcon('item-war-manual', (graphics) => this.drawBookIcon(graphics, 0x5b78a0, 0xe8dfca)); this.createItemIcon('item-bravery-token', (graphics) => this.drawTokenIcon(graphics, 0xd8b15f, 0x9b4c3a)); this.createItemIcon('item-grain-pouch', (graphics) => this.drawPouchIcon(graphics, 0xa7794a, 0xe8dfca)); this.createItemIcon('item-yellow-scarf-charm', (graphics) => this.drawCharmIcon(graphics, 0xd8b15f, 0x8b6f2a)); this.createItemIcon('item-wind-quiver', (graphics) => this.drawQuiverIcon(graphics, 0x8bb3c7, 0xe8dfca)); } private createItemIcon(key: string, draw: (graphics: Phaser.GameObjects.Graphics) => void) { if (this.textures.exists(key)) { return; } const graphics = this.make.graphics({ x: 0, y: 0 }); graphics.fillStyle(0x101820, 0); graphics.fillRect(0, 0, 28, 28); graphics.lineStyle(1, 0x05070a, 0.7); draw(graphics); graphics.generateTexture(key, 28, 28); graphics.destroy(); } private drawSwordIcon(graphics: Phaser.GameObjects.Graphics, bladeColor: number, gripColor: number, offsetX = 0) { graphics.lineStyle(4, bladeColor, 1); graphics.beginPath(); graphics.moveTo(8 + offsetX, 22); graphics.lineTo(20 + offsetX, 6); graphics.strokePath(); graphics.lineStyle(2, 0xeff7ff, 0.8); graphics.beginPath(); graphics.moveTo(11 + offsetX, 18); graphics.lineTo(18 + offsetX, 8); graphics.strokePath(); graphics.lineStyle(3, 0xd8b15f, 1); graphics.beginPath(); graphics.moveTo(6 + offsetX, 18); graphics.lineTo(12 + offsetX, 23); graphics.strokePath(); graphics.fillStyle(gripColor, 1); graphics.fillCircle(8 + offsetX, 23, 2); } private drawPolearmIcon(graphics: Phaser.GameObjects.Graphics, bladeColor: number, accentColor: number) { graphics.lineStyle(3, 0x8d5a3a, 1); graphics.beginPath(); graphics.moveTo(8, 24); graphics.lineTo(18, 5); graphics.strokePath(); graphics.fillStyle(bladeColor, 1); graphics.fillTriangle(17, 4, 25, 8, 18, 15); graphics.lineStyle(2, accentColor, 1); graphics.beginPath(); graphics.moveTo(16, 12); graphics.lineTo(23, 15); graphics.strokePath(); } private drawSpearIcon(graphics: Phaser.GameObjects.Graphics, bladeColor: number, accentColor: number) { graphics.lineStyle(3, 0x8d5a3a, 1); graphics.beginPath(); graphics.moveTo(7, 24); graphics.lineTo(20, 4); graphics.strokePath(); graphics.fillStyle(bladeColor, 1); graphics.fillTriangle(18, 4, 24, 2, 22, 9); graphics.fillStyle(accentColor, 1); graphics.fillTriangle(15, 11, 20, 9, 17, 15); } private drawSaberIcon(graphics: Phaser.GameObjects.Graphics, bladeColor: number, gripColor: number) { graphics.lineStyle(4, bladeColor, 1); graphics.beginPath(); graphics.moveTo(8, 22); graphics.lineTo(13, 15); graphics.lineTo(19, 8); graphics.lineTo(23, 6); graphics.strokePath(); graphics.lineStyle(3, gripColor, 1); graphics.beginPath(); graphics.moveTo(6, 21); graphics.lineTo(12, 23); graphics.strokePath(); } private drawBowIcon(graphics: Phaser.GameObjects.Graphics, bowColor: number, stringColor: number) { graphics.lineStyle(3, bowColor, 1); graphics.beginPath(); graphics.moveTo(20, 4); graphics.lineTo(14, 9); graphics.lineTo(11, 14); graphics.lineTo(14, 19); graphics.lineTo(20, 24); graphics.strokePath(); graphics.lineStyle(1, stringColor, 1); graphics.beginPath(); graphics.moveTo(20, 4); graphics.lineTo(20, 24); graphics.strokePath(); graphics.lineStyle(2, 0xd8dfe6, 1); graphics.beginPath(); graphics.moveTo(7, 14); graphics.lineTo(22, 14); graphics.strokePath(); } private drawAxeIcon(graphics: Phaser.GameObjects.Graphics, bladeColor: number, gripColor: number) { graphics.lineStyle(3, gripColor, 1); graphics.beginPath(); graphics.moveTo(9, 24); graphics.lineTo(18, 5); graphics.strokePath(); graphics.fillStyle(bladeColor, 1); graphics.fillTriangle(16, 5, 25, 8, 16, 15); graphics.fillTriangle(16, 5, 9, 9, 15, 14); } private drawArmorIcon(graphics: Phaser.GameObjects.Graphics, baseColor: number, accentColor: number) { graphics.fillStyle(baseColor, 1); graphics.fillTriangle(14, 4, 24, 11, 20, 24); graphics.fillTriangle(14, 4, 4, 11, 8, 24); graphics.fillRect(8, 10, 12, 14); graphics.lineStyle(2, accentColor, 0.9); graphics.strokeRect(9, 11, 10, 12); graphics.lineStyle(1, 0x05070a, 0.6); graphics.beginPath(); graphics.moveTo(10, 16); graphics.lineTo(18, 16); graphics.moveTo(11, 20); graphics.lineTo(17, 20); graphics.strokePath(); } private drawRobeIcon(graphics: Phaser.GameObjects.Graphics, baseColor: number, accentColor: number) { graphics.fillStyle(baseColor, 1); graphics.fillTriangle(14, 4, 24, 24, 4, 24); graphics.fillStyle(0x101820, 0.45); graphics.fillTriangle(14, 8, 18, 24, 10, 24); graphics.lineStyle(2, accentColor, 1); graphics.beginPath(); graphics.moveTo(14, 6); graphics.lineTo(14, 23); graphics.strokePath(); } private drawVestIcon(graphics: Phaser.GameObjects.Graphics, baseColor: number, accentColor: number) { graphics.fillStyle(baseColor, 1); graphics.fillRect(7, 7, 14, 18); graphics.fillStyle(0x101820, 0.7); graphics.fillTriangle(11, 7, 17, 7, 14, 14); graphics.lineStyle(2, accentColor, 0.9); graphics.strokeRect(8, 8, 12, 16); } private drawCharmIcon(graphics: Phaser.GameObjects.Graphics, baseColor: number, accentColor: number) { graphics.fillStyle(baseColor, 1); graphics.fillRoundedRect(8, 7, 12, 16, 2); graphics.lineStyle(2, accentColor, 1); graphics.strokeRoundedRect(8, 7, 12, 16, 2); graphics.fillStyle(accentColor, 1); graphics.fillCircle(14, 15, 3); } private drawBookIcon(graphics: Phaser.GameObjects.Graphics, baseColor: number, pageColor: number) { graphics.fillStyle(baseColor, 1); graphics.fillRect(6, 6, 16, 18); graphics.fillStyle(pageColor, 1); graphics.fillRect(9, 8, 10, 14); graphics.lineStyle(1, 0x101820, 0.65); graphics.beginPath(); graphics.moveTo(14, 8); graphics.lineTo(14, 22); graphics.strokePath(); } private drawTokenIcon(graphics: Phaser.GameObjects.Graphics, baseColor: number, accentColor: number) { graphics.fillStyle(baseColor, 1); graphics.fillCircle(14, 14, 9); graphics.lineStyle(2, accentColor, 0.9); graphics.strokeCircle(14, 14, 7); graphics.fillStyle(accentColor, 1); graphics.fillTriangle(14, 8, 18, 18, 10, 18); } private drawPouchIcon(graphics: Phaser.GameObjects.Graphics, baseColor: number, accentColor: number) { graphics.fillStyle(baseColor, 1); graphics.fillRoundedRect(7, 10, 14, 13, 4); graphics.fillTriangle(10, 11, 18, 11, 14, 5); graphics.lineStyle(2, accentColor, 1); graphics.beginPath(); graphics.moveTo(8, 13); graphics.lineTo(20, 13); graphics.strokePath(); } private drawQuiverIcon(graphics: Phaser.GameObjects.Graphics, baseColor: number, arrowColor: number) { graphics.fillStyle(baseColor, 1); graphics.fillRoundedRect(10, 8, 9, 16, 2); graphics.lineStyle(2, arrowColor, 1); graphics.beginPath(); graphics.moveTo(7, 6); graphics.lineTo(18, 20); graphics.moveTo(13, 5); graphics.lineTo(20, 19); graphics.strokePath(); } private createUnitAnimations() { unitSheets.forEach(({ key }) => { (['south', 'east', 'north', 'west'] as UnitDirection[]).forEach((direction) => { const frames = Array.from({ length: unitWalkFrameCount }, (_, frameIndex) => ({ key, frame: unitSheetRows[direction] * unitWalkFrameCount + frameIndex })); const walkAnimationKey = `${key}-walk-${direction}`; const idleAnimationKey = `${key}-idle-${direction}`; if (!this.anims.exists(walkAnimationKey)) { this.anims.create({ key: walkAnimationKey, frames, frameRate: unitWalkFrameRate, repeat: -1 }); } if (!this.anims.exists(idleAnimationKey)) { this.anims.create({ key: idleAnimationKey, frames, frameRate: unitIdleFrameRate, repeat: -1 }); } }); }); } }