140 lines
4.3 KiB
TypeScript
140 lines
4.3 KiB
TypeScript
import Phaser from 'phaser';
|
|
import { firstBattleMap, firstBattleUnits, type TerrainType, type UnitData } from '../data/scenario';
|
|
import { palette } from '../ui/palette';
|
|
|
|
const tileSize = 48;
|
|
const mapOffset = { x: 64, y: 72 };
|
|
|
|
const terrainColor: Record<TerrainType, number> = {
|
|
plain: 0x6d8056,
|
|
forest: 0x355f42,
|
|
hill: 0x82724e,
|
|
fort: 0x8b6f48
|
|
};
|
|
|
|
export class BattleScene extends Phaser.Scene {
|
|
private selectedUnit?: UnitData;
|
|
private infoText?: Phaser.GameObjects.Text;
|
|
private turnText?: Phaser.GameObjects.Text;
|
|
private markers: Phaser.GameObjects.Rectangle[] = [];
|
|
|
|
constructor() {
|
|
super('BattleScene');
|
|
}
|
|
|
|
create() {
|
|
const { width, height } = this.scale;
|
|
this.add.rectangle(0, 0, width, height, 0x0f1418).setOrigin(0);
|
|
this.drawMap();
|
|
this.drawUnits();
|
|
this.drawSidePanel(width, height);
|
|
this.setInfo('아군 차례입니다. 유비를 선택해 이동 범위를 확인하세요.');
|
|
}
|
|
|
|
private drawMap() {
|
|
firstBattleMap.terrain.forEach((row, y) => {
|
|
row.forEach((terrain, x) => {
|
|
const tileX = mapOffset.x + x * tileSize;
|
|
const tileY = mapOffset.y + y * tileSize;
|
|
const tile = this.add.rectangle(tileX, tileY, tileSize, tileSize, terrainColor[terrain]).setOrigin(0);
|
|
tile.setStrokeStyle(1, 0x182028, 0.8);
|
|
});
|
|
});
|
|
}
|
|
|
|
private drawUnits() {
|
|
firstBattleUnits.forEach((unit) => {
|
|
const sprite = this.add.image(
|
|
mapOffset.x + unit.x * tileSize + tileSize / 2,
|
|
mapOffset.y + unit.y * tileSize + tileSize / 2,
|
|
unit.faction === 'ally' ? 'unit-ally' : 'unit-enemy'
|
|
);
|
|
sprite.setInteractive({ useHandCursor: unit.faction === 'ally' });
|
|
sprite.on('pointerdown', () => this.selectUnit(unit));
|
|
|
|
this.add.text(sprite.x - 22, sprite.y + 22, unit.name, {
|
|
fontSize: '13px',
|
|
color: unit.faction === 'ally' ? '#e7edf7' : '#ffebe7',
|
|
backgroundColor: '#10131a',
|
|
padding: { left: 4, right: 4, top: 2, bottom: 2 }
|
|
});
|
|
});
|
|
}
|
|
|
|
private drawSidePanel(width: number, height: number) {
|
|
const panelX = mapOffset.x + firstBattleMap.width * tileSize + 36;
|
|
this.add.rectangle(panelX, 72, width - panelX - 64, height - 128, palette.panel, 0.96).setOrigin(0);
|
|
this.add.text(panelX + 30, 108, '첫 전투', {
|
|
fontSize: '32px',
|
|
color: '#e8dfca',
|
|
fontStyle: '700'
|
|
});
|
|
this.turnText = this.add.text(panelX + 30, 164, '1턴 / 아군', {
|
|
fontSize: '22px',
|
|
color: '#d8b15f'
|
|
});
|
|
this.infoText = this.add.text(panelX + 30, 224, '', {
|
|
fontSize: '20px',
|
|
color: '#e8dfca',
|
|
wordWrap: { width: width - panelX - 124, useAdvancedWrap: true },
|
|
lineSpacing: 8
|
|
});
|
|
|
|
this.add.text(panelX + 30, height - 186, '승리 조건: 적 전멸', {
|
|
fontSize: '18px',
|
|
color: '#9aa3ad'
|
|
});
|
|
this.add.text(panelX + 30, height - 148, '패배 조건: 유비 퇴각', {
|
|
fontSize: '18px',
|
|
color: '#9aa3ad'
|
|
});
|
|
this.add.text(panelX + 30, height - 110, '다음 단계: 이동/공격/AI 구현', {
|
|
fontSize: '18px',
|
|
color: '#9aa3ad'
|
|
});
|
|
this.turnText.setText('1턴 / 아군');
|
|
}
|
|
|
|
private selectUnit(unit: UnitData) {
|
|
if (unit.faction !== 'ally') {
|
|
this.setInfo(`${unit.name}: ${unit.className}, HP ${unit.hp}/${unit.maxHp}`);
|
|
return;
|
|
}
|
|
|
|
this.selectedUnit = unit;
|
|
this.clearMarkers();
|
|
this.showMoveRange(unit);
|
|
this.setInfo(`${unit.name} (${unit.className})\nHP ${unit.hp}/${unit.maxHp}, 공격 ${unit.attack}, 이동 ${unit.move}`);
|
|
}
|
|
|
|
private showMoveRange(unit: UnitData) {
|
|
for (let y = 0; y < firstBattleMap.height; y += 1) {
|
|
for (let x = 0; x < firstBattleMap.width; x += 1) {
|
|
const distance = Math.abs(unit.x - x) + Math.abs(unit.y - y);
|
|
if (distance > 0 && distance <= unit.move) {
|
|
const marker = this.add.rectangle(
|
|
mapOffset.x + x * tileSize,
|
|
mapOffset.y + y * tileSize,
|
|
tileSize,
|
|
tileSize,
|
|
palette.blue,
|
|
0.24
|
|
);
|
|
marker.setOrigin(0);
|
|
marker.setStrokeStyle(1, palette.blue, 0.6);
|
|
this.markers.push(marker);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private clearMarkers() {
|
|
this.markers.forEach((marker) => marker.destroy());
|
|
this.markers = [];
|
|
}
|
|
|
|
private setInfo(text: string) {
|
|
this.infoText?.setText(text);
|
|
}
|
|
}
|