Add dialogue character portraits

This commit is contained in:
2026-06-21 23:59:16 +09:00
parent 591141c492
commit 674fbe6659
6 changed files with 48 additions and 27 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

View File

@@ -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);

View File

@@ -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`);