Improve battle action menu readability
This commit is contained in:
@@ -4346,15 +4346,78 @@ export class BattleScene extends Phaser.Scene {
|
||||
return 0x9e3e58;
|
||||
}
|
||||
|
||||
private commandAccentColor(command: BattleCommand) {
|
||||
if (command === 'attack') {
|
||||
return 0xd8b15f;
|
||||
}
|
||||
if (command === 'strategy') {
|
||||
return palette.blue;
|
||||
}
|
||||
if (command === 'item') {
|
||||
return palette.green;
|
||||
}
|
||||
return 0x9fb0bf;
|
||||
}
|
||||
|
||||
private commandMenuInfo(unit: UnitData, command: BattleCommand) {
|
||||
if (command === 'attack') {
|
||||
const targets = this.damageableTargets(unit, 'attack');
|
||||
const target = targets[0];
|
||||
if (!target) {
|
||||
return {
|
||||
available: false,
|
||||
status: '대상 없음',
|
||||
detail: '현재 사거리 안 적 없음'
|
||||
};
|
||||
}
|
||||
|
||||
const preview = this.combatPreview(unit, target, 'attack');
|
||||
return {
|
||||
available: true,
|
||||
status: `대상 ${targets.length}`,
|
||||
detail: `${target.name} · 피해 ${preview.damage} · ${preview.hitRate}%`
|
||||
};
|
||||
}
|
||||
|
||||
if (command === 'strategy' || command === 'item') {
|
||||
const usables = this.availableUsables(unit, command);
|
||||
if (usables.length === 0) {
|
||||
return {
|
||||
available: false,
|
||||
status: command === 'strategy' ? '책략 없음' : '도구 없음',
|
||||
detail: command === 'strategy' ? '사용 가능한 책략이 없습니다.' : '보유한 도구가 없습니다.'
|
||||
};
|
||||
}
|
||||
|
||||
const forecasts = usables.map((usable) => ({ usable, forecast: this.usableMenuForecast(unit, usable) }));
|
||||
const availableCount = forecasts.filter(({ forecast }) => forecast.available).length;
|
||||
const firstReady = forecasts.find(({ forecast }) => forecast.available) ?? forecasts[0];
|
||||
return {
|
||||
available: availableCount > 0,
|
||||
status: availableCount > 0 ? `가능 ${availableCount}` : '대상 없음',
|
||||
detail:
|
||||
availableCount > 0
|
||||
? `${firstReady.usable.name} · ${firstReady.forecast.valueLabel} ${firstReady.forecast.value}`
|
||||
: `${firstReady.usable.name} · ${firstReady.forecast.status}`
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
available: true,
|
||||
status: '종료',
|
||||
detail: '이 유닛 행동 종료'
|
||||
};
|
||||
}
|
||||
|
||||
private showCommandMenu(unit: UnitData, pointerX: number, pointerY: number) {
|
||||
this.hideCommandMenu();
|
||||
|
||||
const commands: BattleCommand[] = ['attack', 'strategy', 'item', 'wait'];
|
||||
const menuWidth = 152;
|
||||
const titleHeight = 31;
|
||||
const buttonHeight = 34;
|
||||
const gap = 5;
|
||||
const padding = 8;
|
||||
const menuWidth = 300;
|
||||
const titleHeight = 46;
|
||||
const buttonHeight = 58;
|
||||
const gap = 7;
|
||||
const padding = 10;
|
||||
const menuHeight = padding * 2 + titleHeight + commands.length * buttonHeight + (commands.length - 1) * gap;
|
||||
const { left, top } = this.commandMenuPosition(pointerX, pointerY, menuWidth, menuHeight);
|
||||
|
||||
@@ -4364,43 +4427,83 @@ export class BattleScene extends Phaser.Scene {
|
||||
panel.setDepth(14);
|
||||
this.commandMenuObjects.push(panel);
|
||||
|
||||
const title = this.add.text(left + menuWidth / 2, top + padding + 2, '명령', {
|
||||
const title = this.add.text(left + 16, top + padding + 2, `${unit.name} 명령`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '16px',
|
||||
fontSize: '18px',
|
||||
color: '#d8b15f',
|
||||
fontStyle: '700'
|
||||
});
|
||||
title.setOrigin(0.5, 0);
|
||||
title.setDepth(15);
|
||||
this.commandMenuObjects.push(title);
|
||||
|
||||
const subtitle = this.add.text(left + 16, top + padding + 26, `HP ${unit.hp}/${unit.maxHp} · ${getUnitClass(unit.classKey).name}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '12px',
|
||||
color: '#aeb7c2',
|
||||
fontStyle: '700'
|
||||
});
|
||||
subtitle.setDepth(15);
|
||||
this.commandMenuObjects.push(subtitle);
|
||||
|
||||
commands.forEach((command, index) => {
|
||||
const buttonTop = top + padding + titleHeight + index * (buttonHeight + gap);
|
||||
const centerX = left + menuWidth / 2;
|
||||
const accent = this.commandAccentColor(command);
|
||||
const info = this.commandMenuInfo(unit, command);
|
||||
const baseFill = info.available ? 0x1a2630 : 0x151c23;
|
||||
const centerY = buttonTop + buttonHeight / 2;
|
||||
const background = this.add.rectangle(centerX, centerY, menuWidth - padding * 2, buttonHeight, 0x1a2630, 0.94);
|
||||
background.setStrokeStyle(1, command === 'wait' ? palette.gold : palette.blue, 0.7);
|
||||
const buttonLeft = left + padding;
|
||||
const background = this.add.rectangle(buttonLeft, buttonTop, menuWidth - padding * 2, buttonHeight, baseFill, 0.95);
|
||||
background.setOrigin(0);
|
||||
background.setStrokeStyle(1, accent, info.available ? 0.74 : 0.46);
|
||||
background.setInteractive({ useHandCursor: true });
|
||||
background.setDepth(15);
|
||||
|
||||
const iconFrame = this.add.rectangle(left + 28, centerY, 30, 30, 0x0a0f14, 0.86);
|
||||
iconFrame.setStrokeStyle(1, command === 'wait' ? palette.gold : palette.blue, 0.58);
|
||||
const iconFrame = this.add.rectangle(left + 40, centerY, 42, 42, 0x0a0f14, 0.9);
|
||||
iconFrame.setStrokeStyle(1, accent, info.available ? 0.72 : 0.44);
|
||||
iconFrame.setDepth(16);
|
||||
this.commandMenuObjects.push(iconFrame);
|
||||
|
||||
const icon = this.createBattleUiIcon(left + 28, centerY, this.commandIcon(command, unit), 26);
|
||||
const icon = this.createBattleUiIcon(left + 40, centerY, this.commandIcon(command, unit), 38);
|
||||
icon.setDepth(17);
|
||||
this.commandMenuObjects.push(icon);
|
||||
|
||||
const text = this.add.text(centerX + 13, centerY, commandLabels[command], {
|
||||
const text = this.add.text(left + 68, buttonTop + 8, commandLabels[command], {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '18px',
|
||||
color: '#f2e3bf',
|
||||
fontStyle: '700'
|
||||
fontSize: '19px',
|
||||
color: info.available ? '#f2e3bf' : '#c8ced6',
|
||||
fontStyle: '700',
|
||||
fixedWidth: 86
|
||||
});
|
||||
text.setOrigin(0.5);
|
||||
text.setDepth(16);
|
||||
|
||||
const detail = this.add.text(left + 68, buttonTop + 34, info.detail, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '11px',
|
||||
color: info.available ? '#aeb7c2' : '#ffcf9a',
|
||||
fixedWidth: menuWidth - 164
|
||||
});
|
||||
detail.setDepth(16);
|
||||
this.commandMenuObjects.push(detail);
|
||||
|
||||
const badgeLeft = left + menuWidth - 92;
|
||||
const badge = this.add.rectangle(badgeLeft, buttonTop + 15, 74, 28, 0x0b1118, 0.94);
|
||||
badge.setOrigin(0);
|
||||
badge.setDepth(16);
|
||||
badge.setStrokeStyle(1, info.available ? accent : 0xb64a45, info.available ? 0.62 : 0.74);
|
||||
this.commandMenuObjects.push(badge);
|
||||
|
||||
const badgeText = this.add.text(badgeLeft + 37, buttonTop + 21, info.status, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '10px',
|
||||
color: info.available ? '#d4dce6' : '#ffcf9a',
|
||||
fontStyle: '700',
|
||||
fixedWidth: 70,
|
||||
align: 'center'
|
||||
});
|
||||
badgeText.setOrigin(0.5, 0);
|
||||
badgeText.setDepth(17);
|
||||
this.commandMenuObjects.push(badgeText);
|
||||
|
||||
const chooseCommand = (choicePointer: Phaser.Input.Pointer) => {
|
||||
if (choicePointer.rightButtonDown()) {
|
||||
this.cancelPendingMove();
|
||||
@@ -4422,10 +4525,17 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.completeUnitAction(unit, command);
|
||||
}
|
||||
};
|
||||
background.on('pointerover', () => background.setFillStyle(0x283947, 0.98));
|
||||
background.on('pointerout', () => background.setFillStyle(0x1a2630, 0.94));
|
||||
const showDetail = () => {
|
||||
background.setFillStyle(info.available ? 0x283947 : 0x27303a, 0.98);
|
||||
this.renderUnitDetail(unit, `${commandLabels[command]}\n${info.detail}`);
|
||||
};
|
||||
const clearHover = () => background.setFillStyle(baseFill, 0.95);
|
||||
background.on('pointerover', showDetail);
|
||||
background.on('pointerout', clearHover);
|
||||
background.on('pointerdown', chooseCommand);
|
||||
text.setInteractive({ useHandCursor: true });
|
||||
text.on('pointerover', showDetail);
|
||||
text.on('pointerout', clearHover);
|
||||
text.on('pointerdown', chooseCommand);
|
||||
|
||||
this.commandButtons.push({ command, background, text });
|
||||
@@ -4640,22 +4750,22 @@ export class BattleScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private renderUsableMenuPill(x: number, y: number, width: number, icon: BattleUiIconKey, label: string, tone: number) {
|
||||
const pill = this.add.rectangle(x, y, width, 28, 0x0b1118, 0.92);
|
||||
const pill = this.add.rectangle(x, y, width, 30, 0x0b1118, 0.92);
|
||||
pill.setOrigin(0);
|
||||
pill.setDepth(16);
|
||||
pill.setStrokeStyle(1, tone, 0.54);
|
||||
this.commandMenuObjects.push(pill);
|
||||
|
||||
const iconImage = this.createBattleUiIcon(x + 15, y + 14, icon, 22);
|
||||
const iconImage = this.createBattleUiIcon(x + 16, y + 15, icon, 24);
|
||||
iconImage.setDepth(17);
|
||||
this.commandMenuObjects.push(iconImage);
|
||||
|
||||
const text = this.add.text(x + 33, y + 6, label, {
|
||||
const text = this.add.text(x + 35, y + 6, label, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '12px',
|
||||
color: '#d4dce6',
|
||||
fontStyle: '700',
|
||||
fixedWidth: width - 38
|
||||
fixedWidth: width - 40
|
||||
});
|
||||
text.setDepth(17);
|
||||
this.commandMenuObjects.push(text);
|
||||
@@ -4680,56 +4790,71 @@ export class BattleScene extends Phaser.Scene {
|
||||
|
||||
this.hideCommandMenu();
|
||||
|
||||
const menuWidth = 384;
|
||||
const titleHeight = 38;
|
||||
const rowHeight = 96;
|
||||
const padding = 9;
|
||||
const menuWidth = 432;
|
||||
const titleHeight = 52;
|
||||
const rowHeight = 112;
|
||||
const padding = 10;
|
||||
const menuHeight = padding * 2 + titleHeight + usables.length * rowHeight;
|
||||
const { left, top } = this.commandMenuPosition(pointerX, pointerY, menuWidth, menuHeight);
|
||||
const titleLabel = command === 'strategy' ? `책략 선택 ${usables.length}` : `도구 선택 ${usables.length}`;
|
||||
const accentColor = command === 'strategy' ? palette.blue : palette.gold;
|
||||
const titleLabel = `${unit.name} · ${commandLabels[command]}`;
|
||||
|
||||
const panel = this.add.rectangle(left, top, menuWidth, menuHeight, 0x101821, 0.98);
|
||||
panel.setOrigin(0);
|
||||
panel.setStrokeStyle(2, command === 'strategy' ? palette.blue : palette.gold, 0.78);
|
||||
panel.setStrokeStyle(2, accentColor, 0.78);
|
||||
panel.setDepth(14);
|
||||
this.commandMenuObjects.push(panel);
|
||||
|
||||
const title = this.add.text(left + menuWidth / 2, top + padding + 2, titleLabel, {
|
||||
const title = this.add.text(left + 16, top + padding + 2, titleLabel, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '17px',
|
||||
fontSize: '19px',
|
||||
color: '#d8b15f',
|
||||
fontStyle: '700'
|
||||
});
|
||||
title.setOrigin(0.5, 0);
|
||||
title.setDepth(15);
|
||||
this.commandMenuObjects.push(title);
|
||||
|
||||
const subtitle = this.add.text(left + 16, top + padding + 29, `목록 ${usables.length} · HP ${unit.hp}/${unit.maxHp} · ${getUnitClass(unit.classKey).name}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '12px',
|
||||
color: '#aeb7c2',
|
||||
fontStyle: '700'
|
||||
});
|
||||
subtitle.setDepth(15);
|
||||
this.commandMenuObjects.push(subtitle);
|
||||
|
||||
usables.forEach((usable, index) => {
|
||||
const rowTop = top + padding + titleHeight + index * rowHeight;
|
||||
const accent = this.usableAccentColor(usable);
|
||||
const forecast = this.usableMenuForecast(unit, usable);
|
||||
const bg = this.add.rectangle(left + 8, rowTop, menuWidth - 16, rowHeight - 8, 0x1a2630, 0.95);
|
||||
const rowLeft = left + padding;
|
||||
const rowWidth = menuWidth - padding * 2;
|
||||
const contentLeft = left + 92;
|
||||
const sideBoxLeft = left + menuWidth - 112;
|
||||
const baseFill = forecast.available ? 0x1a2630 : 0x151c23;
|
||||
const bg = this.add.rectangle(rowLeft, rowTop, rowWidth, rowHeight - 8, baseFill, 0.95);
|
||||
bg.setOrigin(0);
|
||||
bg.setDepth(15);
|
||||
bg.setStrokeStyle(1, forecast.available ? accent : 0x6c7480, forecast.available ? 0.72 : 0.52);
|
||||
bg.setInteractive({ useHandCursor: true });
|
||||
this.commandMenuObjects.push(bg);
|
||||
|
||||
const iconFrame = this.add.rectangle(left + 42, rowTop + 44, 58, 58, 0x0a0f14, 0.9);
|
||||
const iconFrame = this.add.rectangle(left + 52, rowTop + 52, 66, 66, 0x0a0f14, 0.9);
|
||||
iconFrame.setStrokeStyle(1, accent, forecast.available ? 0.72 : 0.42);
|
||||
iconFrame.setDepth(16);
|
||||
this.commandMenuObjects.push(iconFrame);
|
||||
|
||||
const icon = this.createBattleUiIcon(left + 42, rowTop + 44, this.usableIcon(usable), 52);
|
||||
const icon = this.createBattleUiIcon(left + 52, rowTop + 52, this.usableIcon(usable), 58);
|
||||
icon.setDepth(17);
|
||||
this.commandMenuObjects.push(icon);
|
||||
|
||||
const stock = this.usableStockLabel(unit, usable);
|
||||
const name = this.add.text(left + 78, rowTop + 9, usable.name, {
|
||||
const name = this.add.text(contentLeft, rowTop + 9, usable.name, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '20px',
|
||||
fontSize: '21px',
|
||||
color: forecast.available ? '#f2e3bf' : '#c8ced6',
|
||||
fontStyle: '700'
|
||||
fontStyle: '700',
|
||||
fixedWidth: sideBoxLeft - contentLeft - 10
|
||||
});
|
||||
name.setDepth(16);
|
||||
this.commandMenuObjects.push(name);
|
||||
@@ -4745,51 +4870,52 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.commandMenuObjects.push(stockText);
|
||||
|
||||
const statusTone = forecast.available ? accent : 0xb64a45;
|
||||
const sideBoxLeft = left + menuWidth - 94;
|
||||
const statusBg = this.add.rectangle(sideBoxLeft, rowTop + 34, 74, 24, 0x0b1118, 0.94);
|
||||
const statusBg = this.add.rectangle(sideBoxLeft, rowTop + 36, 92, 28, 0x0b1118, 0.94);
|
||||
statusBg.setOrigin(0);
|
||||
statusBg.setDepth(16);
|
||||
statusBg.setStrokeStyle(1, statusTone, forecast.available ? 0.62 : 0.76);
|
||||
this.commandMenuObjects.push(statusBg);
|
||||
|
||||
const statusText = this.add.text(sideBoxLeft + 37, rowTop + 40, forecast.status, {
|
||||
const statusText = this.add.text(sideBoxLeft + 46, rowTop + 42, forecast.status, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '11px',
|
||||
fontSize: '12px',
|
||||
color: forecast.available ? '#d4dce6' : '#ffcf9a',
|
||||
fontStyle: '700'
|
||||
fontStyle: '700',
|
||||
fixedWidth: 88,
|
||||
align: 'center'
|
||||
});
|
||||
statusText.setOrigin(0.5, 0);
|
||||
statusText.setDepth(17);
|
||||
this.commandMenuObjects.push(statusText);
|
||||
|
||||
const summary = this.add.text(
|
||||
left + 78,
|
||||
rowTop + 36,
|
||||
contentLeft,
|
||||
rowTop + 39,
|
||||
forecast.detail,
|
||||
{
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '13px',
|
||||
color: forecast.available ? '#aeb7c2' : '#ffcf9a',
|
||||
fixedWidth: menuWidth - 186
|
||||
fixedWidth: sideBoxLeft - contentLeft - 12
|
||||
}
|
||||
);
|
||||
summary.setDepth(16);
|
||||
this.commandMenuObjects.push(summary);
|
||||
|
||||
this.renderUsableMenuPill(left + 78, rowTop + 60, 90, this.usableIcon(usable), this.usableEffectLabel(usable), accent);
|
||||
this.renderUsableMenuPill(left + 174, rowTop + 60, 104, this.usableTargetIcon(usable), `${this.usableTargetLabel(usable)} ${usable.range}칸`, accent);
|
||||
this.renderUsableMenuPill(contentLeft, rowTop + 70, 96, this.usableIcon(usable), this.usableEffectLabel(usable), accent);
|
||||
this.renderUsableMenuPill(contentLeft + 104, rowTop + 70, 116, this.usableTargetIcon(usable), `${this.usableTargetLabel(usable)} ${usable.range}칸`, accent);
|
||||
|
||||
const valueBox = this.add.rectangle(sideBoxLeft, rowTop + 58, 74, 30, 0x0b1118, 0.94);
|
||||
const valueBox = this.add.rectangle(sideBoxLeft, rowTop + 68, 92, 34, 0x0b1118, 0.94);
|
||||
valueBox.setOrigin(0);
|
||||
valueBox.setDepth(16);
|
||||
valueBox.setStrokeStyle(1, accent, forecast.available ? 0.72 : 0.44);
|
||||
this.commandMenuObjects.push(valueBox);
|
||||
|
||||
const valueIcon = this.createBattleUiIcon(sideBoxLeft + 14, rowTop + 73, this.usablePowerIcon(usable), 22);
|
||||
const valueIcon = this.createBattleUiIcon(sideBoxLeft + 16, rowTop + 85, this.usablePowerIcon(usable), 24);
|
||||
valueIcon.setDepth(17);
|
||||
this.commandMenuObjects.push(valueIcon);
|
||||
|
||||
const valueText = this.add.text(sideBoxLeft + 70, rowTop + 71, forecast.value, {
|
||||
const valueText = this.add.text(sideBoxLeft + 86, rowTop + 83, forecast.value, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '14px',
|
||||
color: forecast.available ? '#f2e3bf' : '#c8ced6',
|
||||
@@ -4799,7 +4925,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
valueText.setDepth(17);
|
||||
this.commandMenuObjects.push(valueText);
|
||||
|
||||
const valueLabel = this.add.text(sideBoxLeft + 29, rowTop + 62, forecast.valueLabel, {
|
||||
const valueLabel = this.add.text(sideBoxLeft + 34, rowTop + 73, forecast.valueLabel, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '10px',
|
||||
color: '#9fb0bf',
|
||||
@@ -4818,13 +4944,21 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.beginUsableTargeting(unit, usable);
|
||||
}
|
||||
};
|
||||
bg.on('pointerover', () => {
|
||||
const showDetail = () => {
|
||||
bg.setFillStyle(forecast.available ? 0x283947 : 0x27303a, 0.98);
|
||||
this.renderUnitDetail(unit, this.usableDetailText(unit, usable));
|
||||
};
|
||||
const clearHover = () => bg.setFillStyle(baseFill, 0.95);
|
||||
bg.on('pointerover', () => {
|
||||
showDetail();
|
||||
});
|
||||
bg.on('pointerout', () => {
|
||||
clearHover();
|
||||
});
|
||||
bg.on('pointerout', () => bg.setFillStyle(0x1a2630, 0.94));
|
||||
bg.on('pointerdown', choose);
|
||||
name.setInteractive({ useHandCursor: true });
|
||||
name.on('pointerover', showDetail);
|
||||
name.on('pointerout', clearHover);
|
||||
name.on('pointerdown', choose);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user