diff --git a/docs/roadmap.md b/docs/roadmap.md index 063c242..9b04641 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -14,9 +14,9 @@ Build a small complete tactical RPG loop: - Vite, TypeScript, and Phaser project foundation - Cinematic title scene with an original Taoyuan Oath background, subtle motion, menu UI, and first-input audio -- Cinematic prologue pages with high-resolution story backgrounds, character portraits, and scenario data +- Cinematic prologue pages with high-resolution story backgrounds, character portraits, non-black scene transitions, and scenario data - File-based original orchestral BGM loops for title, prologue, oath, militia, and battle-prep scenes -- First battle scene with a high-resolution battlefield background, large 12x12 tactical grid, denser pixel-style unit sprites, movement range preview, and click-to-move unit movement +- First battle scene with a high-resolution battlefield background, large 12x12 tactical grid, high-detail pixel-style unit sprites, movement range preview, and click-to-move unit movement - Flow verification script from title to first battle ## Next Steps diff --git a/src/game/scenes/BattleScene.ts b/src/game/scenes/BattleScene.ts index 0d64a7b..e7dbff9 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -141,7 +141,7 @@ export class BattleScene extends Phaser.Scene { private drawUnits() { firstBattleUnits.forEach((unit) => { - const sizeRatio = unit.faction === 'ally' ? 0.9 : 0.84; + const sizeRatio = unit.faction === 'ally' ? 0.98 : 0.92; const sprite = this.add.image(this.tileCenterX(unit.x), this.tileCenterY(unit.y), unitTexture[unit.id] ?? 'unit-rebel'); sprite.setName(`unit-${unit.id}`); sprite.setDisplaySize(this.layout.tileSize * sizeRatio, this.layout.tileSize * sizeRatio); diff --git a/src/game/scenes/BootScene.ts b/src/game/scenes/BootScene.ts index cfec790..11d47e9 100644 --- a/src/game/scenes/BootScene.ts +++ b/src/game/scenes/BootScene.ts @@ -148,103 +148,132 @@ export class BootScene extends Phaser.Scene { this.drawBody(graphics, colors); this.drawHead(graphics, colors); - graphics.generateTexture(key, 48, 48); + graphics.generateTexture(key, 64, 64); graphics.destroy(); this.textures.get(key).setFilter(Phaser.Textures.FilterMode.NEAREST); } private drawPixelShadow(graphics: Phaser.GameObjects.Graphics) { graphics.fillStyle(0x07090c, 0.45); - graphics.fillRect(10, 41, 30, 3); - graphics.fillRect(14, 39, 23, 2); + graphics.fillRect(12, 56, 40, 4); + graphics.fillRect(18, 53, 30, 3); } private drawHorse(graphics: Phaser.GameObjects.Graphics, colors: UnitPixelPalette) { graphics.fillStyle(0x5b3824, 1); - graphics.fillRect(10, 28, 28, 8); - graphics.fillRect(8, 31, 6, 4); - graphics.fillRect(34, 25, 5, 7); + graphics.fillRect(11, 39, 38, 10); + graphics.fillRect(8, 43, 8, 5); + graphics.fillRect(45, 35, 6, 9); graphics.fillStyle(0x3a2418, 1); - graphics.fillRect(12, 36, 4, 7); - graphics.fillRect(20, 35, 4, 8); - graphics.fillRect(30, 35, 4, 8); + graphics.fillRect(14, 48, 5, 10); + graphics.fillRect(25, 47, 5, 11); + graphics.fillRect(40, 47, 5, 11); graphics.fillStyle(0x8c5a36, 1); - graphics.fillRect(13, 27, 19, 3); + graphics.fillRect(16, 37, 24, 3); + graphics.fillRect(47, 37, 4, 3); + graphics.fillStyle(0x1f1510, 1); + graphics.fillRect(46, 32, 5, 4); graphics.fillStyle(colors.accent, 1); - graphics.fillRect(18, 29, 10, 2); + graphics.fillRect(22, 40, 14, 2); + graphics.fillRect(48, 35, 3, 2); } private drawWeapon(graphics: Phaser.GameObjects.Graphics, colors: UnitPixelPalette) { graphics.fillStyle(colors.weapon, 1); if (colors.spear) { - graphics.fillRect(36, 8, 2, 31); - graphics.fillRect(35, 6, 4, 3); - graphics.fillRect(34, 9, 6, 2); + graphics.fillRect(49, 8, 3, 45); + graphics.fillRect(48, 5, 5, 4); + graphics.fillRect(46, 9, 9, 2); + graphics.fillStyle(0x7f8f8f, 1); + graphics.fillRect(50, 12, 1, 32); return; } if (colors.halberd) { - graphics.fillRect(36, 6, 2, 34); - graphics.fillRect(32, 7, 9, 2); - graphics.fillRect(37, 9, 6, 4); - graphics.fillRect(34, 13, 3, 3); + graphics.fillRect(49, 6, 3, 48); + graphics.fillRect(44, 8, 13, 3); + graphics.fillRect(51, 11, 8, 5); + graphics.fillRect(46, 16, 4, 4); + graphics.fillStyle(0x89999a, 1); + graphics.fillRect(50, 12, 1, 37); return; } - graphics.fillRect(36, 15, 2, 22); - graphics.fillRect(34, 14, 6, 2); - graphics.fillRect(37, 12, 2, 4); + graphics.fillRect(50, 20, 3, 31); + graphics.fillRect(47, 19, 9, 3); + graphics.fillRect(51, 16, 3, 5); } private drawBody(graphics: Phaser.GameObjects.Graphics, colors: UnitPixelPalette) { graphics.fillStyle(colors.shade, 1); - graphics.fillRect(17, 21, 16, 18); - graphics.fillRect(14, 26, 6, 10); - graphics.fillRect(31, 26, 5, 10); + graphics.fillRect(22, 27, 22, 24); + graphics.fillRect(18, 33, 8, 14); + graphics.fillRect(42, 33, 7, 14); graphics.fillStyle(colors.robe, 1); - graphics.fillRect(18, 20, 14, 16); - graphics.fillRect(15, 25, 6, 9); - graphics.fillRect(31, 25, 5, 9); - graphics.fillRect(19, 36, 5, 6); - graphics.fillRect(27, 36, 5, 6); + graphics.fillRect(23, 26, 20, 22); + graphics.fillRect(19, 32, 8, 13); + graphics.fillRect(42, 32, 7, 13); + graphics.fillRect(24, 48, 8, 8); + graphics.fillRect(35, 48, 8, 8); graphics.fillStyle(colors.trim, 1); - graphics.fillRect(19, 21, 12, 2); - graphics.fillRect(20, 27, 10, 2); - graphics.fillRect(18, 34, 15, 2); - graphics.fillRect(22, 23, 2, 12); + graphics.fillRect(24, 27, 18, 2); + graphics.fillRect(25, 35, 16, 2); + graphics.fillRect(23, 45, 22, 2); + graphics.fillRect(29, 29, 3, 17); + graphics.fillRect(36, 29, 2, 17); + graphics.fillStyle(0xf8e6a8, 0.9); + graphics.fillRect(25, 30, 4, 2); + graphics.fillRect(39, 30, 3, 2); + graphics.fillRect(26, 39, 14, 2); + graphics.fillStyle(colors.accent, 1); + graphics.fillRect(30, 24, 8, 3); + graphics.fillRect(28, 37, 12, 2); graphics.fillStyle(0x0a0c10, 1); - graphics.fillRect(19, 42, 6, 2); - graphics.fillRect(27, 42, 6, 2); + graphics.fillRect(24, 56, 9, 3); + graphics.fillRect(35, 56, 9, 3); graphics.fillStyle(colors.skin, 1); - graphics.fillRect(13, 28, 3, 5); - graphics.fillRect(35, 28, 3, 5); + graphics.fillRect(17, 36, 4, 7); + graphics.fillRect(48, 36, 4, 7); + graphics.fillStyle(0x000000, 0.22); + graphics.fillRect(22, 50, 4, 5); + graphics.fillRect(39, 50, 4, 5); } private drawHead(graphics: Phaser.GameObjects.Graphics, colors: UnitPixelPalette) { graphics.fillStyle(colors.skin, 1); - graphics.fillRect(18, 10, 12, 10); - graphics.fillRect(16, 13, 3, 5); - graphics.fillRect(29, 13, 3, 5); + graphics.fillRect(24, 11, 17, 15); + graphics.fillRect(21, 16, 4, 7); + graphics.fillRect(40, 16, 4, 7); + graphics.fillStyle(0xd79a70, 1); + graphics.fillRect(25, 24, 15, 2); graphics.fillStyle(colors.hair, 1); - graphics.fillRect(17, 8, 14, 4); - graphics.fillRect(16, 11, 4, 7); - graphics.fillRect(29, 11, 3, 7); + graphics.fillRect(23, 8, 20, 5); + graphics.fillRect(22, 12, 6, 10); + graphics.fillRect(39, 12, 5, 10); + graphics.fillRect(26, 6, 13, 3); graphics.fillStyle(colors.accent, 1); - graphics.fillRect(19, 7, 9, 2); - graphics.fillRect(21, 5, 5, 2); + graphics.fillRect(25, 7, 14, 2); + graphics.fillRect(29, 4, 7, 3); + graphics.fillRect(30, 2, 5, 2); graphics.fillStyle(0x050609, 1); - graphics.fillRect(20, 14, 2, 1); - graphics.fillRect(27, 14, 2, 1); - graphics.fillRect(23, 18, 4, 1); + graphics.fillRect(27, 17, 2, 1); + graphics.fillRect(36, 17, 2, 1); + graphics.fillRect(31, 22, 5, 1); + graphics.fillStyle(0xffffff, 0.55); + graphics.fillRect(29, 13, 5, 1); + graphics.fillRect(36, 13, 4, 1); if (colors.beard) { graphics.fillStyle(0x2a1712, 1); - graphics.fillRect(20, 19, 9, 6); - graphics.fillRect(22, 25, 5, 5); + graphics.fillRect(27, 24, 12, 8); + graphics.fillRect(29, 32, 8, 7); graphics.fillStyle(0x160d0a, 1); - graphics.fillRect(23, 28, 3, 4); + graphics.fillRect(31, 38, 5, 6); + graphics.fillStyle(0x4b281d, 1); + graphics.fillRect(28, 25, 3, 5); + graphics.fillRect(36, 25, 2, 6); } } } diff --git a/src/game/scenes/StoryScene.ts b/src/game/scenes/StoryScene.ts index 784d616..8aa92d8 100644 --- a/src/game/scenes/StoryScene.ts +++ b/src/game/scenes/StoryScene.ts @@ -9,8 +9,11 @@ const portraitKeys: Record = { zhangFei: 'portrait-zhang-fei' }; +type AlphaObject = Phaser.GameObjects.Image | Phaser.GameObjects.Rectangle | Phaser.GameObjects.Text; + export class StoryScene extends Phaser.Scene { private pageIndex = 0; + private transitioning = false; private background?: Phaser.GameObjects.Image; private chapterText?: Phaser.GameObjects.Text; private portrait?: Phaser.GameObjects.Image; @@ -107,10 +110,39 @@ export class StoryScene extends Phaser.Scene { return; } - this.cameras.main.fadeOut(120, 0, 0, 0); - this.time.delayedCall(130, () => { + this.transitioning = true; + this.tweens.killTweensOf([this.background, ...this.dialogueObjects()]); + this.tweens.add({ + targets: this.dialogueObjects(), + alpha: 0.18, + duration: 90, + ease: 'Sine.easeIn' + }); + this.tweens.add({ + targets: this.background, + alpha: 0.68, + duration: 120, + ease: 'Sine.easeIn', + onComplete: () => { this.applyPage(page); - this.cameras.main.fadeIn(180, 0, 0, 0); + this.background?.setAlpha(0.68); + this.setDialogueAlpha(0.18); + this.tweens.add({ + targets: this.dialogueObjects(), + alpha: 1, + duration: 170, + ease: 'Sine.easeOut' + }); + this.tweens.add({ + targets: this.background, + alpha: 1, + duration: 220, + ease: 'Sine.easeOut', + onComplete: () => { + this.transitioning = false; + } + }); + } }); } @@ -172,7 +204,7 @@ export class StoryScene extends Phaser.Scene { } private advance() { - if (this.cameras.main.fadeEffect?.isRunning) { + if (this.transitioning) { return; } @@ -184,4 +216,24 @@ export class StoryScene extends Phaser.Scene { this.pageIndex += 1; this.showPage(this.pageIndex); } + + private dialogueObjects() { + const objects: Array = [ + this.chapterText, + this.portrait, + this.portraitFrame, + this.portraitDivider, + this.nameText, + this.bodyText, + this.progressText + ]; + + return objects.filter((object): object is AlphaObject => object !== undefined); + } + + private setDialogueAlpha(alpha: number) { + this.dialogueObjects().forEach((object) => { + object.setAlpha(alpha); + }); + } }