feat: add error display & upgrade pick
This commit is contained in:
parent
df1c0cbb81
commit
c678974489
|
|
@ -241,7 +241,10 @@ async function turn(game: BoopGame, turnPlayer: PlayerType) {
|
|||
}
|
||||
const turnCommand = registry.register('turn <player>', turn);
|
||||
export const commands = {
|
||||
play: (player: PlayerType, row: number, col: number, type: PieceType) => {
|
||||
return `play ${player} ${row} ${col} ${type}`;
|
||||
}
|
||||
play: (player: PlayerType, row: number, col: number, type?: PieceType) => {
|
||||
if (type) {
|
||||
return `play ${player} ${row} ${col} ${type}`;
|
||||
}
|
||||
return `play ${player} ${row} ${col}`;
|
||||
},
|
||||
};
|
||||
|
|
@ -111,6 +111,40 @@ export class BoardRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置棋盘上棋子的点击处理(用于选择要升级的棋子)
|
||||
*/
|
||||
setupPieceInput(
|
||||
getState: () => BoopState,
|
||||
onPieceClick: (row: number, col: number) => void,
|
||||
checkWinner: () => boolean
|
||||
): void {
|
||||
for (let row = 0; row < BOARD_SIZE; row++) {
|
||||
for (let col = 0; col < BOARD_SIZE; col++) {
|
||||
const x = BOARD_OFFSET.x + col * CELL_SIZE + CELL_SIZE / 2;
|
||||
const y = BOARD_OFFSET.y + row * CELL_SIZE + CELL_SIZE / 2;
|
||||
|
||||
const zone = this.scene.add.zone(x, y, CELL_SIZE, CELL_SIZE).setInteractive();
|
||||
|
||||
zone.on('pointerdown', () => {
|
||||
const state = getState();
|
||||
const isOccupied = !!state.regions.board.partMap[`${row},${col}`];
|
||||
if (isOccupied && !checkWinner()) {
|
||||
onPieceClick(row, col);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新棋盘上棋子的交互状态
|
||||
*/
|
||||
updatePieceInteraction(enabled: boolean): void {
|
||||
// 可以通过此方法启用/禁用棋子点击
|
||||
// 暂时通过重新设置 zone 的 interactive 状态来实现
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.container.destroy();
|
||||
this.gridGraphics.destroy();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
import Phaser from 'phaser';
|
||||
import { BOARD_OFFSET, CELL_SIZE, BOARD_SIZE } from './BoardRenderer';
|
||||
|
||||
export class ErrorOverlay {
|
||||
private overlay?: Phaser.GameObjects.Container;
|
||||
private hideTimeout?: Phaser.Time.TimerEvent;
|
||||
|
||||
constructor(private scene: Phaser.Scene) {
|
||||
// 初始时不显示
|
||||
}
|
||||
|
||||
show(message: string, duration: number = 2000): void {
|
||||
// 清除之前的定时器
|
||||
this.hideTimeout?.remove();
|
||||
|
||||
// 销毁之前的 overlay
|
||||
if (this.overlay) {
|
||||
this.overlay.destroy();
|
||||
}
|
||||
|
||||
this.overlay = this.scene.add.container();
|
||||
|
||||
// 半透明背景(可点击关闭)
|
||||
const bg = this.scene.add.rectangle(
|
||||
BOARD_OFFSET.x + (BOARD_SIZE * CELL_SIZE) / 2,
|
||||
BOARD_OFFSET.y + (BOARD_SIZE * CELL_SIZE) / 2,
|
||||
BOARD_SIZE * CELL_SIZE + 200,
|
||||
BOARD_SIZE * CELL_SIZE + 200,
|
||||
0x000000,
|
||||
0.4,
|
||||
).setInteractive();
|
||||
|
||||
bg.on('pointerdown', () => {
|
||||
this.hide();
|
||||
});
|
||||
|
||||
this.overlay.add(bg);
|
||||
|
||||
// 错误提示框
|
||||
const errorBoxY = BOARD_OFFSET.y + (BOARD_SIZE * CELL_SIZE) / 2 - 60;
|
||||
const errorBox = this.scene.add.container(
|
||||
BOARD_OFFSET.x + (BOARD_SIZE * CELL_SIZE) / 2,
|
||||
errorBoxY
|
||||
);
|
||||
|
||||
// 错误框背景
|
||||
const boxBg = this.scene.add.rectangle(0, 0, 450, 100, 0xef4444, 0.95)
|
||||
.setStrokeStyle(4, 0xdc2626);
|
||||
|
||||
// 错误图标
|
||||
const iconText = this.scene.add.text(-180, 0, '❌', {
|
||||
fontSize: '36px',
|
||||
}).setOrigin(0.5);
|
||||
|
||||
// 错误文本
|
||||
const errorText = this.scene.add.text(30, 0, message, {
|
||||
fontSize: '22px',
|
||||
fontFamily: 'Arial',
|
||||
color: '#ffffff',
|
||||
align: 'center',
|
||||
wordWrap: { width: 380 },
|
||||
}).setOrigin(0, 0.5);
|
||||
|
||||
errorBox.add([boxBg, iconText, errorText]);
|
||||
this.overlay.add(errorBox);
|
||||
|
||||
// 出现动画
|
||||
errorBox.setScale(0);
|
||||
errorBox.setAlpha(0);
|
||||
this.scene.tweens.add({
|
||||
targets: errorBox,
|
||||
scale: 1,
|
||||
alpha: 1,
|
||||
duration: 300,
|
||||
ease: 'Back.easeOut',
|
||||
});
|
||||
|
||||
// 自动隐藏
|
||||
this.hideTimeout = this.scene.time.delayedCall(duration, () => {
|
||||
this.hide();
|
||||
});
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.hideTimeout?.remove();
|
||||
this.hideTimeout = undefined;
|
||||
|
||||
if (this.overlay) {
|
||||
const overlay = this.overlay;
|
||||
// 消失动画
|
||||
this.scene.tweens.add({
|
||||
targets: overlay,
|
||||
alpha: 0,
|
||||
duration: 200,
|
||||
onComplete: () => {
|
||||
overlay.destroy();
|
||||
this.overlay = undefined;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.hideTimeout?.remove();
|
||||
this.overlay?.destroy();
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import { SupplyUI } from './SupplyUI';
|
|||
import { PieceTypeSelector } from './PieceTypeSelector';
|
||||
import { WinnerOverlay } from './WinnerOverlay';
|
||||
import { StartOverlay } from './StartOverlay';
|
||||
import { ErrorOverlay } from './ErrorOverlay';
|
||||
|
||||
export class GameScene extends GameHostScene<BoopState> {
|
||||
private boardRenderer!: BoardRenderer;
|
||||
|
|
@ -14,6 +15,7 @@ export class GameScene extends GameHostScene<BoopState> {
|
|||
private pieceTypeSelector!: PieceTypeSelector;
|
||||
private winnerOverlay!: WinnerOverlay;
|
||||
private startOverlay!: StartOverlay;
|
||||
private errorOverlay!: ErrorOverlay;
|
||||
|
||||
constructor() {
|
||||
super('GameScene');
|
||||
|
|
@ -28,6 +30,7 @@ export class GameScene extends GameHostScene<BoopState> {
|
|||
this.pieceTypeSelector = new PieceTypeSelector(this);
|
||||
this.winnerOverlay = new WinnerOverlay(this, () => this.restartGame());
|
||||
this.startOverlay = new StartOverlay(this, () => this.startGame());
|
||||
this.errorOverlay = new ErrorOverlay(this);
|
||||
|
||||
// 设置棋子生成器
|
||||
this.disposables.add(createPieceSpawner(this));
|
||||
|
|
@ -39,6 +42,13 @@ export class GameScene extends GameHostScene<BoopState> {
|
|||
() => this.gameHost.status.value !== 'running' || !!this.state.winner
|
||||
);
|
||||
|
||||
// 设置棋子点击处理(用于棋盘满时选择要升级的棋子)
|
||||
this.boardRenderer.setupPieceInput(
|
||||
() => this.state,
|
||||
(row, col) => this.handlePieceClick(row, col),
|
||||
() => this.gameHost.status.value !== 'running' || !!this.state.winner
|
||||
);
|
||||
|
||||
// 监听游戏状态变化
|
||||
this.addEffect(() => {
|
||||
const status = this.gameHost.status.value;
|
||||
|
|
@ -72,7 +82,16 @@ export class GameScene extends GameHostScene<BoopState> {
|
|||
const cmd = commands.play(this.state.currentPlayer, row, col, selectedType);
|
||||
const error = this.gameHost.onInput(cmd);
|
||||
if (error) {
|
||||
console.warn('Invalid move:', error);
|
||||
this.errorOverlay.show(error);
|
||||
}
|
||||
}
|
||||
|
||||
private handlePieceClick(row: number, col: number): void {
|
||||
// 棋盘满时,点击棋子触发升级
|
||||
const cmd = commands.play(this.state.currentPlayer, row, col);
|
||||
const error = this.gameHost.onInput(cmd);
|
||||
if (error) {
|
||||
this.errorOverlay.show(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue