feat: improve early battle clarity and keyboard flow

This commit is contained in:
2026-07-28 03:15:40 +09:00
parent a41672bd0e
commit 1773bffbf2
9 changed files with 1945 additions and 477 deletions

View File

@@ -435,16 +435,16 @@ export type BattleScenarioDefinition = {
export const firstBattleScenario: BattleScenarioDefinition = {
id: 'first-battle-zhuo-commandery',
title: '탁현의 전투',
victoryConditionLabel: '두령 한석 격파',
victoryConditionLabel: '한석 선봉 격퇴',
defeatConditionLabel: '유비 퇴각',
openingObjectiveLines: [
'황건은 숲과 마을 어귀를 끼고 흩어져 있습니다. 두령 한석을 격파하면 전열이 무너집니다.',
'황건 두령 한석의 선봉은 숲과 마을 어귀를 끼고 흩어져 있습니다. 한석의 지휘선을 무너뜨려 퇴각시키면 마을을 지킬 수 있습니다.',
'유비가 퇴각하면 의용군은 전투를 지속할 수 없습니다.',
'관우는 중앙 숲길을 잡고, 장비는 남쪽 길을 흔들며, 유비는 진영과 마을 보급선을 지키십시오.'
],
tacticalGuide: {
summary: '중앙 길은 빠르지만 노출됩니다. 숲과 마을 어귀를 잡아 관우가 버티고, 장비가 아래 길로 궁병을 끊습니다.',
route: '아군 진영 → 숲 낀 중앙 길목 → 마을 어귀 → 두령 한석',
route: '아군 진영 → 숲 낀 중앙 길목 → 마을 어귀 → 한석 선봉 지휘선',
focus: '관우를 숲 옆 길목에 세워 첫 접전을 받고, 장비는 남쪽 길로 우회해 궁병과 기병의 진입로를 끊으십시오.',
roles: ['관우: 숲길 전열', '장비: 남쪽 길 압박', '유비: 진영/마을 보급 지휘'],
events: {
@@ -454,11 +454,11 @@ export const firstBattleScenario: BattleScenarioDefinition = {
},
'village-approach': {
title: '마을 어귀 접근',
lines: ['마을 어귀는 방어와 회복 보정이 있는 핵심 지형입니다.', '어귀에 전열을 세우면 보급선을 잡고 한석에 안전하게 압박을 모을 수 있습니다.']
lines: ['마을 어귀는 방어와 회복 보정이 있는 핵심 지형입니다.', '어귀에 전열을 세우면 보급선을 잡고 한석의 선봉 지휘선에 안전하게 압박을 모을 수 있습니다.']
},
'vanguard-broken': {
title: '초기 선봉 정리',
lines: ['앞쪽 황건 선봉이 무너졌습니다.', '관우는 중앙 길목을 계속 잡고, 장비는 남쪽 엄호선을 끊어 마을 쪽 퇴로를 좁히세요.']
lines: ['앞쪽 황건 선봉이 무너졌습니다.', '관우는 중앙 길목을 계속 잡고, 장비는 남쪽 엄호선을 끊어 한석이 물러날 북쪽 퇴로를 좁히세요.']
},
'cavalry-approach': {
title: '기병 접근',
@@ -497,7 +497,7 @@ export const firstBattleScenario: BattleScenarioDefinition = {
{
id: 'leader',
kind: 'defeat-leader',
label: '황건 두령',
label: '한석 선봉',
rewardGold: 200,
unitId: 'rebel-leader'
},
@@ -533,8 +533,8 @@ export const firstBattleScenario: BattleScenarioDefinition = {
reputation: ['의용군 명성 +1'],
recruits: ['jian-yong'],
unlockBattleId: 'second-battle-yellow-turban-pursuit',
unlockLabel: '황건 잔당 추격 개방',
note: '간옹이 합류합니다. 다음 전투부터 유비·관우·장비·간옹 4명 중 최대 3명을 골라 편성하며, 추천 편성은 세 형제입니다.'
unlockLabel: '한석 잔당 추격 개방',
note: '한석은 패잔병을 이끌고 북쪽으로 달아났습니다. 간옹이 합류하며, 다음 전투부터 유비·관우·장비·간옹 4명 중 최대 3명을 골라 편성니다.'
},
victoryPages: firstBattleVictoryPages,
nextCampScene: 'CampScene'
@@ -546,7 +546,7 @@ export const secondBattleScenario: BattleScenarioDefinition = {
victoryConditionLabel: '두령 한석 격파',
defeatConditionLabel: '유비 퇴각',
openingObjectiveLines: [
'황건 잔당이 강가 나루와 북쪽 마을을 끼고 물러났습니다. 두령 한석을 격파하면 적의 퇴로가 무너집니다.',
'탁현에서 선봉을 잃고 퇴각한 한석이 잔당을 이끌고 강가 나루와 북쪽 마을을 붙잡았습니다. 이번에는 한석을 격파 적의 퇴로를 끊어야 합니다.',
'나루 앞 숲길을 정리하지 않고 길을 넓히면 궁병과 기병에게 전열이 갈라집니다.',
'세 형제를 모두 출전시키고 24턴 이내에 승리하면 추격전 명성과 추가 보상이 붙습니다.'
],

View File

@@ -159,7 +159,7 @@ const sortieFlows: Record<string, SortieFlow> = {
afterBattleId: defaultBattleScenario.id,
eyebrow: '다음 전장',
title: secondBattleScenario.title,
description: '탁현 방어 직후 달아난 황건 잔당이 북쪽 나루와 마을을 붙잡고 있습니다. 첫 승리 보상으로 정비를 마친 뒤, 세 형제가 다시 전열을 나누어 추격합니다.',
description: '탁현에서 선봉을 잃고 퇴각한 한석이 잔당을 이끌고 북쪽 나루와 마을을 붙잡습니다. 첫 승리 보상으로 정비를 마친 뒤, 세 형제가 한석의 퇴로를 따라 추격합니다.',
rewardHint: '지난 보상: 콩, 탁주, 연습검 · 다음 보상: 소모품, 황건 부적, 의용군 명성',
nextBattleId: secondBattleScenario.id,
campaignStep: 'second-battle',

View File

@@ -255,7 +255,7 @@ export const prologueMilitiaCampNpcDefinitions: readonly PrologueMilitiaCampNpcD
{
speaker: '추정',
textureKey: 'unit-shu-officer',
text: '막사가 잘 갖춰졌군. 황건 두령 한석이 북동쪽에서 마을을 노린다. 그대들이 선봉을 되, 첫 승리를 어떤 군령으로 이끌지 직접 정하게.'
text: '막사가 잘 갖춰졌군. 황건 두령 한석의 선봉이 북동쪽에서 마을을 노린다. 그대들이 길목을 지켜 한석의 선봉을 물리치되, 첫 출전을 어떤 군령으로 이끌지 직접 정하게.'
}
],
lockedDialogue: [

View File

@@ -167,7 +167,7 @@ export const firstBattleVictoryPages: StoryPage[] = [
bgm: 'militia-theme',
chapter: '첫 승리',
background: 'story-yellow-pursuit',
text: '황건의 깃발이 쓰러지자, 숨어 있던 백성들이 하나둘 마을 어귀로 돌아다.'
text: '황건 선봉의 깃발은 꺾였지만, 한석은 남은 병력을 수습해 북쪽 나루로 달아났다. 숨어 있던 백성들 마을 어귀로 돌아오기 시작했다.'
},
{
id: 'first-victory-liu-bei',
@@ -201,7 +201,7 @@ export const firstBattleVictoryPages: StoryPage[] = [
bgm: 'battle-prep',
chapter: '다음 길',
background: 'story-yellow-pursuit',
text: '세 형제의 이름은 탁현 일대에 퍼지기 시작했다. 이제 더 넓은 전장과 더 많은 인연이 그들을 기다린다.'
text: '한석은 남은 잔당을 이끌고 북쪽 나루로 빠져나갔다. 그 퇴로를 살피던 유비의 옛 벗 간옹이 의용군 막사를 찾아왔다.'
}
];
@@ -232,7 +232,7 @@ const firstBattleStoryOverrides: Record<string, Partial<StoryPage>> = {
'first-sortie': {
chapter: '첫 출전',
speaker: '유비',
text: '마을 사람들을 먼저 들여보내자. 황건군이 근처에 진을 쳤다면, 오늘 여기서 물리쳐 다시는 이 길을 넘보지 못하게 해야 한다.',
text: '마을 사람들을 먼저 들여보내자. 한석의 황건 선봉이 근처에 진을 쳤다면, 오늘 그 전열을 물리쳐 이 길을 지켜야 한다.',
cutscene: {
kind: 'muster',
title: '황건군을 향해',
@@ -256,11 +256,11 @@ const firstBattleStoryOverrides: Record<string, Partial<StoryPage>> = {
chapter: '탁현 작전판',
speaker: '관우',
portrait: 'guanYu',
text: '작전판으로 보시지요. 황건군은 보병을 앞세워 길목을 막고, 뒤쪽 궁병으로 엄호하며, 두령은 북동쪽 높은 길에 머물고 있습니다.',
text: '작전판으로 보시지요. 한석의 선봉은 보병을 앞세워 길목을 막고 뒤쪽 궁병으로 엄호합니다. 한석은 북동쪽 높은 길에서 퇴로를 살피고 있습니다.',
cutscene: {
kind: 'operation',
title: '황건군 진형',
subtitle: '보병 전열을 걷어 내고, 궁병 엄호를 피한 뒤 두령을 포위하십시오.',
subtitle: '보병 전열을 걷어 내고, 궁병 엄호를 피한 뒤 한석의 선봉 지휘선을 무너뜨리십시오.',
actors: [
{ unitId: 'liu-bei', x: 0.24, y: 0.56, size: 104, direction: 'east', label: '지휘' },
{ unitId: 'guan-yu', x: 0.34, y: 0.54, size: 108, direction: 'east', label: '정면' },
@@ -271,12 +271,12 @@ const firstBattleStoryOverrides: Record<string, Partial<StoryPage>> = {
{ x: 0.2, y: 0.74, label: '아군 시작', side: 'ally' },
{ x: 0.42, y: 0.55, label: '보병 전열', side: 'enemy' },
{ x: 0.57, y: 0.43, label: '궁병 엄호', side: 'enemy' },
{ x: 0.74, y: 0.28, label: '황건 두령', side: 'enemy' },
{ x: 0.74, y: 0.28, label: '한석 선봉', side: 'enemy' },
{ x: 0.35, y: 0.64, label: '마을 입구', side: 'objective' }
],
briefing: {
title: '공략 방침',
lines: ['장비가 측면에서 앞줄을 흔든다.', '관우가 길목 보병을 정면 돌파한다.', '유비는 후방에서 생존과 회복을 맡는다.', '궁병 엄호를 피한 뒤 두령을 함께 포위한다.']
lines: ['장비가 측면에서 앞줄을 흔든다.', '관우가 길목 보병을 정면 돌파한다.', '유비는 후방에서 생존과 회복을 맡는다.', '궁병 엄호를 피한 뒤 한석의 선봉 지휘선을 함께 압박한다.']
},
rewards: [
{ label: '보상: 보급품 · 연습검 · 의용군 명성', tone: 'reward' }
@@ -286,20 +286,20 @@ const firstBattleStoryOverrides: Record<string, Partial<StoryPage>> = {
'first-victory-village': {
chapter: '첫 승리',
speaker: '전황',
text: '황건의 깃발이고, 흩어졌던 백성들 마을로 돌아오기 시작했다.',
text: '황건 선봉의 깃발였지만, 한석은 남은 병력을 수습해 북쪽 나루로 달아났다. 숨어 있던 백성들 마을로 돌아오기 시작했다.',
cutscene: {
kind: 'victory',
title: '탁현 방어 성공',
subtitle: '첫 출전을 함께 치른 세 형제가 승리의 순간을 나눕니다.',
title: '탁현 선봉 격퇴',
subtitle: '마을은 지켰지만 한석은 잔당을 이끌고 북쪽 나루로 퇴각했습니다.',
actors: [
{ unitId: 'liu-bei', x: 0.36, y: 0.53, size: 132, direction: 'east', label: '유비' },
{ unitId: 'guan-yu', x: 0.52, y: 0.52, size: 142, direction: 'west', label: '관우' },
{ unitId: 'zhang-fei', x: 0.67, y: 0.53, size: 142, direction: 'west', label: '장비' }
],
rewards: [
{ label: '승리 조건 달성', tone: 'reward' },
{ label: '한석 선봉 격퇴', tone: 'reward' },
{ label: '마을 방어', tone: 'bond' },
{ label: '다음: 황건 잔당 추격', tone: 'next' }
{ label: '다음: 한석 잔당 추격', tone: 'next' }
]
}
},
@@ -364,11 +364,11 @@ const firstBattleStoryOverrides: Record<string, Partial<StoryPage>> = {
'first-victory-next': {
chapter: '다음 길',
speaker: '전황',
text: '첫 승리 소식을 들은 유비의 옛 벗 간옹이 막사를 찾아와, 의용군의 군정과 책략을 맡겠다고 나섰다. 군량과 말이 넉넉지 않아 네 사람 가운데 셋이 선두를 맡고 한 사람은 후방을 돌보기로 했다.',
text: '한석은 패잔병을 이끌고 북쪽 나루로 달아났다. 그 퇴로를 살피던 유비의 옛 벗 간옹이 막사를 찾아와 의용군의 군정과 책략을 맡겠다고 나섰다.',
cutscene: {
kind: 'operation',
title: '간옹 합류 · 첫 편성 선택',
subtitle: '4명 중 최대 3명을 골라 황건 잔당 추격에 나섭니다.',
subtitle: '4명 중 최대 3명을 골라 북쪽으로 달아난 한석의 잔당 추격니다.',
actors: [
{ unitId: 'liu-bei', x: 0.22, y: 0.54, size: 108, direction: 'east', label: '유비' },
{ unitId: 'guan-yu', x: 0.32, y: 0.53, size: 112, direction: 'east', label: '관우' },
@@ -378,7 +378,7 @@ const firstBattleStoryOverrides: Record<string, Partial<StoryPage>> = {
markers: [
{ x: 0.24, y: 0.68, label: '탁현', side: 'ally' },
{ x: 0.58, y: 0.46, label: '북쪽 길목', side: 'objective' },
{ x: 0.76, y: 0.32, label: '황건 잔당', side: 'enemy' }
{ x: 0.76, y: 0.32, label: '한석 잔당', side: 'enemy' }
],
briefing: {
title: '정비 후 출전',
@@ -387,7 +387,7 @@ const firstBattleStoryOverrides: Record<string, Partial<StoryPage>> = {
rewards: [
{ label: '신규 무장: 간옹', tone: 'reward' },
{ label: '편성: 4명 중 최대 3명', tone: 'bond' },
{ label: '다음: 황건 잔당 추격', tone: 'next' }
{ label: '다음: 한석 잔당 추격', tone: 'next' }
]
}
}
@@ -411,7 +411,7 @@ export const secondBattleIntroPages: StoryPage[] = [
bgm: 'militia-theme',
chapter: '잔당의 그림자',
background: 'story-yellow-pursuit',
text: '첫 승리의 기쁨이 채 가시기도 전에, 황건 잔당이 북쪽 길목의 마을을 습격하고 있다는 급보가 도착했다.'
text: '탁현에서 선봉을 잃고 퇴각한 한석이 잔당을 이끌고 북쪽 나루로 달아나, 길목의 마을을 습격하고 있다는 급보가 도착했다.'
},
{
id: 'second-liu-bei-command',
@@ -420,7 +420,7 @@ export const secondBattleIntroPages: StoryPage[] = [
background: 'story-yellow-pursuit',
speaker: '유비',
portrait: 'liuBei',
text: '백성 다시 칼끝에 몰다면 우리가 쉬어 갈 곳은 없다. 길을 따라 추격하되, 마을을 먼저 지켜야 한다.'
text: '한석의 잔당이 백성 다시 칼끝에 몰다면 우리가 쉬어 갈 곳은 없다. 북쪽 퇴로를 따라 추격하되, 마을을 먼저 지켜야 한다.'
},
{
id: 'second-guan-yu-vow',
@@ -442,7 +442,7 @@ export const secondBattleIntroPages: StoryPage[] = [
cutscene: {
kind: 'operation',
title: '북쪽 나루 추격 작전',
subtitle: '첫 승리 직후 달아난 잔당을 나루와 마을 사이에서 끊어냅니다.',
subtitle: '탁현에서 퇴각한 한석의 잔당을 북쪽 나루와 마을 사이에서 끊어냅니다.',
actors: [
{ unitId: 'liu-bei', x: 0.25, y: 0.58, size: 104, direction: 'east', label: '후방 지휘' },
{ unitId: 'guan-yu', x: 0.35, y: 0.55, size: 112, direction: 'east', label: '나루 전열' },
@@ -460,7 +460,7 @@ export const secondBattleIntroPages: StoryPage[] = [
lines: ['관우가 나루 앞 전열을 잡는다.', '장비가 남쪽에서 잔당과 궁병을 압박한다.', '유비는 뒤에서 생존과 마을 확보를 맡는다.']
},
rewards: [
{ label: '목표: 두령 격파 · 북쪽 마을 확보', tone: 'next' },
{ label: '목표: 한석 격파 · 북쪽 마을 확보', tone: 'next' },
{ label: '보상: 상처약 · 황건 부적', tone: 'reward' }
]
}
@@ -473,7 +473,7 @@ export const secondBattleVictoryPages: StoryPage[] = [
bgm: 'militia-theme',
chapter: '마을의 안도',
background: 'story-yellow-pursuit',
text: '잔당의 불길은 꺼지고, 피난하던 백성들이 하나둘 마을로 돌아왔다. 의용군의 이름은 이제 길목의 장터마다 오르내렸다.'
text: '북쪽 나루에서 한석이 끝내 격파되자 잔당의 불길은 꺼졌다. 피난하던 백성들이 하나둘 마을로 돌아왔고, 의용군의 이름은 이제 길목의 장터마다 오르내렸다.'
},
{
id: 'second-victory-liu-bei',

File diff suppressed because it is too large Load Diff

View File

@@ -81,5 +81,8 @@ export async function startGameScene(scene: Phaser.Scene, key: string, data?: Re
export async function startLazySceneFromGame(game: Phaser.Game, key: LazySceneKey, data?: Record<string, unknown>) {
await ensureLazyScene(game, key);
game.scene.getScenes(true).forEach((activeScene) => {
game.scene.stop(activeScene.scene.key);
});
game.scene.start(key, data);
}