Add dialogue character portraits
This commit is contained in:
@@ -14,7 +14,7 @@ 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 and scenario data
|
||||
- Cinematic prologue pages with high-resolution story backgrounds, character portraits, and scenario data
|
||||
- First battle scene with a 12x12 map, terrain colors, unit placement, and movement range preview
|
||||
- Flow verification script from title to first battle
|
||||
|
||||
|
||||
BIN
src/assets/images/portraits/guan-yu.png
Normal file
BIN
src/assets/images/portraits/guan-yu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 MiB |
BIN
src/assets/images/portraits/liu-bei.png
Normal file
BIN
src/assets/images/portraits/liu-bei.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
BIN
src/assets/images/portraits/zhang-fei.png
Normal file
BIN
src/assets/images/portraits/zhang-fei.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 MiB |
@@ -5,6 +5,9 @@ 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';
|
||||
|
||||
export class BootScene extends Phaser.Scene {
|
||||
@@ -19,6 +22,9 @@ export class BootScene extends Phaser.Scene {
|
||||
this.load.image('story-three-heroes', storyThreeHeroesUrl);
|
||||
this.load.image('story-militia', storyMilitiaUrl);
|
||||
this.load.image('story-sortie', storySortieUrl);
|
||||
this.load.image('portrait-liu-bei', liuBeiPortraitUrl);
|
||||
this.load.image('portrait-guan-yu', guanYuPortraitUrl);
|
||||
this.load.image('portrait-zhang-fei', zhangFeiPortraitUrl);
|
||||
}
|
||||
|
||||
create() {
|
||||
@@ -27,29 +33,11 @@ export class BootScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private createGeneratedTextures() {
|
||||
this.createPortrait('portrait-liu-bei', 0x445f92, 0xd8b15f);
|
||||
this.createPortrait('portrait-guan-yu', 0x1f5f43, 0xd8b15f);
|
||||
this.createPortrait('portrait-zhang-fei', 0x8a4f3d, 0xe8dfca);
|
||||
this.createPetalTexture();
|
||||
this.createUnitTexture('unit-ally', palette.blue, 0xe7edf7);
|
||||
this.createUnitTexture('unit-enemy', palette.red, 0xffebe7);
|
||||
}
|
||||
|
||||
private createPortrait(key: string, primary: number, accent: number) {
|
||||
const graphics = this.make.graphics({ x: 0, y: 0 });
|
||||
graphics.fillStyle(0x141820, 1);
|
||||
graphics.fillRoundedRect(0, 0, 128, 128, 8);
|
||||
graphics.fillStyle(primary, 1);
|
||||
graphics.fillCircle(64, 46, 26);
|
||||
graphics.fillRoundedRect(31, 71, 66, 42, 12);
|
||||
graphics.fillStyle(accent, 1);
|
||||
graphics.fillTriangle(38, 29, 64, 8, 90, 29);
|
||||
graphics.lineStyle(4, 0x0b0d12, 0.45);
|
||||
graphics.strokeRoundedRect(2, 2, 124, 124, 8);
|
||||
graphics.generateTexture(key, 128, 128);
|
||||
graphics.destroy();
|
||||
}
|
||||
|
||||
private createPetalTexture() {
|
||||
const graphics = this.make.graphics({ x: 0, y: 0 });
|
||||
graphics.fillStyle(0xf0a6a4, 1);
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import Phaser from 'phaser';
|
||||
import { prologuePages, type StoryPage } from '../data/scenario';
|
||||
import { prologuePages, type PortraitKey, type StoryPage } from '../data/scenario';
|
||||
import { palette } from '../ui/palette';
|
||||
|
||||
const portraitKeys: Record<PortraitKey, string> = {
|
||||
liuBei: 'portrait-liu-bei',
|
||||
guanYu: 'portrait-guan-yu',
|
||||
zhangFei: 'portrait-zhang-fei'
|
||||
};
|
||||
|
||||
export class StoryScene extends Phaser.Scene {
|
||||
private pageIndex = 0;
|
||||
private background?: Phaser.GameObjects.Image;
|
||||
private chapterText?: Phaser.GameObjects.Text;
|
||||
private portrait?: Phaser.GameObjects.Image;
|
||||
private portraitFrame?: Phaser.GameObjects.Rectangle;
|
||||
private portraitDivider?: Phaser.GameObjects.Rectangle;
|
||||
private nameText?: Phaser.GameObjects.Text;
|
||||
private bodyText?: Phaser.GameObjects.Text;
|
||||
private progressText?: Phaser.GameObjects.Text;
|
||||
@@ -43,7 +52,7 @@ export class StoryScene extends Phaser.Scene {
|
||||
const panelW = width - 144;
|
||||
const panelH = 172;
|
||||
|
||||
this.add.rectangle(panelX, panelY, panelW, panelH, palette.panelDark, 0.86).setOrigin(0);
|
||||
this.add.rectangle(panelX, panelY, panelW, panelH, palette.panelDark, 0.88).setOrigin(0);
|
||||
this.add.rectangle(panelX, panelY, panelW, 3, palette.gold, 0.9).setOrigin(0);
|
||||
this.add.rectangle(panelX, panelY + panelH, panelW, 1, palette.gold, 0.35).setOrigin(0);
|
||||
|
||||
@@ -56,7 +65,15 @@ export class StoryScene extends Phaser.Scene {
|
||||
shadow: { offsetX: 0, offsetY: 2, color: '#05070a', blur: 6, fill: true }
|
||||
});
|
||||
|
||||
this.nameText = this.add.text(panelX + 24, panelY + 28, '', {
|
||||
this.portraitFrame = this.add.rectangle(panelX + 86, panelY + 86, 136, 136, 0x090d12, 0.88);
|
||||
this.portraitFrame.setStrokeStyle(2, palette.gold, 0.58);
|
||||
|
||||
this.portrait = this.add.image(panelX + 86, panelY + 86, 'portrait-liu-bei');
|
||||
this.portrait.setDisplaySize(128, 128);
|
||||
|
||||
this.portraitDivider = this.add.rectangle(panelX + 166, panelY + 28, 1, panelH - 56, palette.gold, 0.22).setOrigin(0);
|
||||
|
||||
this.nameText = this.add.text(panelX + 198, panelY + 28, '', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", serif',
|
||||
fontSize: '24px',
|
||||
color: '#f1e3c2',
|
||||
@@ -64,11 +81,11 @@ export class StoryScene extends Phaser.Scene {
|
||||
padding: { top: 4, bottom: 4 }
|
||||
});
|
||||
|
||||
this.bodyText = this.add.text(panelX + 24, panelY + 78, '', {
|
||||
this.bodyText = this.add.text(panelX + 198, panelY + 78, '', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", serif',
|
||||
fontSize: '25px',
|
||||
color: '#e8dfca',
|
||||
wordWrap: { width: panelW - 56, useAdvancedWrap: true },
|
||||
wordWrap: { width: panelW - 236, useAdvancedWrap: true },
|
||||
lineSpacing: 10,
|
||||
padding: { top: 4, bottom: 6 },
|
||||
shadow: { offsetX: 0, offsetY: 2, color: '#05070a', blur: 5, fill: true }
|
||||
@@ -99,9 +116,25 @@ export class StoryScene extends Phaser.Scene {
|
||||
private applyPage(page: StoryPage) {
|
||||
this.applyBackground(page.background);
|
||||
this.chapterText?.setText(page.chapter);
|
||||
this.nameText?.setPosition(96, this.scale.height - 210);
|
||||
this.bodyText?.setPosition(96, this.scale.height - 160);
|
||||
this.bodyText?.setWordWrapWidth(this.scale.width - 200, true);
|
||||
|
||||
if (page.portrait) {
|
||||
this.portraitFrame?.setVisible(true);
|
||||
this.portrait?.setVisible(true);
|
||||
this.portraitDivider?.setVisible(true);
|
||||
this.portrait?.setTexture(portraitKeys[page.portrait]);
|
||||
this.portrait?.setDisplaySize(128, 128);
|
||||
this.nameText?.setPosition(270, this.scale.height - 210);
|
||||
this.bodyText?.setPosition(270, this.scale.height - 160);
|
||||
this.bodyText?.setWordWrapWidth(this.scale.width - 374, true);
|
||||
} else {
|
||||
this.portraitFrame?.setVisible(false);
|
||||
this.portrait?.setVisible(false);
|
||||
this.portraitDivider?.setVisible(false);
|
||||
this.nameText?.setPosition(96, this.scale.height - 210);
|
||||
this.bodyText?.setPosition(96, this.scale.height - 160);
|
||||
this.bodyText?.setWordWrapWidth(this.scale.width - 200, true);
|
||||
}
|
||||
|
||||
this.nameText?.setText(page.speaker ?? '나레이션');
|
||||
this.bodyText?.setText(page.text);
|
||||
this.progressText?.setText(`${this.pageIndex + 1} / ${prologuePages.length} SPACE / ENTER`);
|
||||
|
||||
Reference in New Issue
Block a user