Add unit movement and denser pixel sprites
This commit is contained in:
@@ -35,12 +35,18 @@ type BattleLayout = {
|
||||
panelHeight: number;
|
||||
};
|
||||
|
||||
type UnitView = {
|
||||
sprite: Phaser.GameObjects.Image;
|
||||
label: Phaser.GameObjects.Text;
|
||||
};
|
||||
|
||||
export class BattleScene extends Phaser.Scene {
|
||||
private layout!: BattleLayout;
|
||||
private selectedUnit?: UnitData;
|
||||
private infoText?: Phaser.GameObjects.Text;
|
||||
private turnText?: Phaser.GameObjects.Text;
|
||||
private markers: Phaser.GameObjects.Rectangle[] = [];
|
||||
private unitViews = new Map<string, UnitView>();
|
||||
|
||||
constructor() {
|
||||
super('BattleScene');
|
||||
@@ -55,7 +61,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.drawMap();
|
||||
this.drawUnits();
|
||||
this.drawSidePanel();
|
||||
this.setInfo('아군 차례입니다.\n유비를 선택해 이동 범위를 확인하세요.');
|
||||
this.setInfo('아군 차례입니다.\n유비를 선택한 뒤 파란 칸을 눌러 이동하세요.');
|
||||
}
|
||||
|
||||
private createLayout(width: number, height: number): BattleLayout {
|
||||
@@ -112,10 +118,14 @@ export class BattleScene extends Phaser.Scene {
|
||||
|
||||
firstBattleMap.terrain.forEach((row, y) => {
|
||||
row.forEach((terrain, x) => {
|
||||
const tileX = layout.gridX + x * layout.tileSize;
|
||||
const tileY = layout.gridY + y * layout.tileSize;
|
||||
const terrainAlpha = terrain === 'plain' ? 0.03 : terrain === 'forest' ? 0.16 : terrain === 'hill' ? 0.13 : 0.18;
|
||||
const tile = this.add.rectangle(tileX, tileY, layout.tileSize, layout.tileSize, terrainColor[terrain], terrainAlpha);
|
||||
const tile = this.add.rectangle(
|
||||
layout.gridX + x * layout.tileSize,
|
||||
layout.gridY + y * layout.tileSize,
|
||||
layout.tileSize,
|
||||
layout.tileSize,
|
||||
terrainColor[terrain],
|
||||
this.terrainAlpha(terrain)
|
||||
);
|
||||
tile.setOrigin(0);
|
||||
tile.setStrokeStyle(1, 0x0a1014, 0.5);
|
||||
tile.setDepth(2);
|
||||
@@ -130,21 +140,16 @@ export class BattleScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private drawUnits() {
|
||||
const layout = this.layout;
|
||||
|
||||
firstBattleUnits.forEach((unit) => {
|
||||
const unitSize = unit.faction === 'ally' ? layout.tileSize * 0.78 : layout.tileSize * 0.72;
|
||||
const sprite = this.add.image(
|
||||
layout.gridX + unit.x * layout.tileSize + layout.tileSize / 2,
|
||||
layout.gridY + unit.y * layout.tileSize + layout.tileSize / 2,
|
||||
unitTexture[unit.id] ?? 'unit-rebel'
|
||||
);
|
||||
sprite.setDisplaySize(unitSize, unitSize);
|
||||
const sizeRatio = unit.faction === 'ally' ? 0.9 : 0.84;
|
||||
const sprite = this.add.image(this.tileCenterX(unit.x), this.tileCenterY(unit.y), unitTexture[unit.id] ?? 'unit-rebel');
|
||||
sprite.setName(`unit-${unit.id}`);
|
||||
sprite.setDisplaySize(this.layout.tileSize * sizeRatio, this.layout.tileSize * sizeRatio);
|
||||
sprite.setDepth(6);
|
||||
sprite.setInteractive({ useHandCursor: unit.faction === 'ally' });
|
||||
sprite.on('pointerdown', () => this.selectUnit(unit));
|
||||
|
||||
const label = this.add.text(sprite.x, sprite.y + layout.tileSize * 0.35, unit.name, {
|
||||
const label = this.add.text(sprite.x, sprite.y + this.layout.tileSize * 0.36, unit.name, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '13px',
|
||||
color: unit.faction === 'ally' ? '#e7edf7' : '#ffebe7',
|
||||
@@ -153,6 +158,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
});
|
||||
label.setOrigin(0.5, 0);
|
||||
label.setDepth(7);
|
||||
this.unitViews.set(unit.id, { sprite, label });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -191,7 +197,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
fontSize: '18px',
|
||||
color: '#9aa3ad'
|
||||
});
|
||||
this.add.text(panelX + 24, panelY + panelHeight - 86, '다음 단계: 이동 / 공격 / AI', {
|
||||
this.add.text(panelX + 24, panelY + panelHeight - 86, '다음 단계: 공격 / 대기 / AI', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '18px',
|
||||
color: '#9aa3ad'
|
||||
@@ -207,33 +213,110 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.selectedUnit = unit;
|
||||
this.clearMarkers();
|
||||
this.showMoveRange(unit);
|
||||
this.setInfo(`${unit.name} (${unit.className})\nHP ${unit.hp}/${unit.maxHp}\n공격 ${unit.attack} / 이동 ${unit.move}`);
|
||||
this.setInfo(`${unit.name} (${unit.className})\nHP ${unit.hp}/${unit.maxHp}\n공격 ${unit.attack} / 이동 ${unit.move}\n이동할 파란 칸을 선택하세요.`);
|
||||
}
|
||||
|
||||
private showMoveRange(unit: UnitData) {
|
||||
const layout = this.layout;
|
||||
|
||||
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(
|
||||
layout.gridX + x * layout.tileSize,
|
||||
layout.gridY + y * layout.tileSize,
|
||||
layout.tileSize,
|
||||
layout.tileSize,
|
||||
palette.blue,
|
||||
0.24
|
||||
);
|
||||
marker.setOrigin(0);
|
||||
marker.setStrokeStyle(1, palette.blue, 0.68);
|
||||
marker.setDepth(4);
|
||||
this.markers.push(marker);
|
||||
if (!this.canMoveTo(unit, x, y)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const marker = this.add.rectangle(
|
||||
this.layout.gridX + x * this.layout.tileSize,
|
||||
this.layout.gridY + y * this.layout.tileSize,
|
||||
this.layout.tileSize,
|
||||
this.layout.tileSize,
|
||||
palette.blue,
|
||||
0.24
|
||||
);
|
||||
marker.setOrigin(0);
|
||||
marker.setStrokeStyle(1, palette.blue, 0.68);
|
||||
marker.setDepth(4);
|
||||
marker.setInteractive({ useHandCursor: true });
|
||||
marker.on('pointerover', () => marker.setFillStyle(palette.blue, 0.36));
|
||||
marker.on('pointerout', () => marker.setFillStyle(palette.blue, 0.24));
|
||||
marker.on('pointerdown', () => this.moveSelectedUnit(x, y));
|
||||
this.markers.push(marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private moveSelectedUnit(x: number, y: number) {
|
||||
if (!this.selectedUnit || !this.canMoveTo(this.selectedUnit, x, y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const unit = this.selectedUnit;
|
||||
const view = this.unitViews.get(unit.id);
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
unit.x = x;
|
||||
unit.y = y;
|
||||
this.selectedUnit = undefined;
|
||||
this.clearMarkers();
|
||||
soundDirector.playSelect();
|
||||
|
||||
const targetX = this.tileCenterX(x);
|
||||
const targetY = this.tileCenterY(y);
|
||||
this.tweens.add({
|
||||
targets: view.sprite,
|
||||
x: targetX,
|
||||
y: targetY,
|
||||
duration: 260,
|
||||
ease: 'Sine.easeInOut'
|
||||
});
|
||||
this.tweens.add({
|
||||
targets: view.label,
|
||||
x: targetX,
|
||||
y: targetY + this.layout.tileSize * 0.36,
|
||||
duration: 260,
|
||||
ease: 'Sine.easeInOut'
|
||||
});
|
||||
this.setInfo(`${unit.name} 이동 완료\n위치: ${x + 1}, ${y + 1}\n다음 단계에서 공격/대기 명령을 붙일 예정입니다.`);
|
||||
}
|
||||
|
||||
private canMoveTo(unit: UnitData, x: number, y: number) {
|
||||
const distance = Math.abs(unit.x - x) + Math.abs(unit.y - y);
|
||||
return (
|
||||
distance > 0 &&
|
||||
distance <= unit.move &&
|
||||
x >= 0 &&
|
||||
y >= 0 &&
|
||||
x < firstBattleMap.width &&
|
||||
y < firstBattleMap.height &&
|
||||
!this.isOccupied(x, y, unit.id)
|
||||
);
|
||||
}
|
||||
|
||||
private isOccupied(x: number, y: number, exceptUnitId?: string) {
|
||||
return firstBattleUnits.some((unit) => unit.id !== exceptUnitId && unit.x === x && unit.y === y);
|
||||
}
|
||||
|
||||
private terrainAlpha(terrain: TerrainType) {
|
||||
if (terrain === 'forest') {
|
||||
return 0.16;
|
||||
}
|
||||
if (terrain === 'hill') {
|
||||
return 0.13;
|
||||
}
|
||||
if (terrain === 'fort') {
|
||||
return 0.18;
|
||||
}
|
||||
return 0.03;
|
||||
}
|
||||
|
||||
private tileCenterX(x: number) {
|
||||
return this.layout.gridX + x * this.layout.tileSize + this.layout.tileSize / 2;
|
||||
}
|
||||
|
||||
private tileCenterY(y: number) {
|
||||
return this.layout.gridY + y * this.layout.tileSize + this.layout.tileSize / 2;
|
||||
}
|
||||
|
||||
private clearMarkers() {
|
||||
this.markers.forEach((marker) => marker.destroy());
|
||||
this.markers = [];
|
||||
|
||||
Reference in New Issue
Block a user