refactor(onitama): decouple rendering logic into dedicated renderers

Introduces a centralized configuration system and extracts visual
creation logic from spawners into specialized renderer classes:
- CardRenderer
- HighlightRenderer
- PawnRenderer
- SelectionRenderer

This refactor improves separation of concerns by moving Phaser-specific
drawing and animation code out of the spawner/container logic and into
reusable renderer components.
This commit is contained in:
2026-04-19 11:55:18 +08:00
parent c7ef992082
commit b28da1cad3
16 changed files with 1006 additions and 515 deletions
+8 -2
View File
@@ -1,2 +1,8 @@
export { createUIState, clearSelection, selectPiece, selectCard, createValidMoves } from './ui';
export type { OnitamaUIState, ValidMove } from './ui';
export {
createUIState,
clearSelection,
selectPiece,
selectCard,
createValidMoves,
} from "./ui";
export type { OnitamaUIState, ValidMove } from "./ui";
+26 -15
View File
@@ -1,5 +1,8 @@
import { MutableSignal, mutableSignal, computed, ReadonlySignal } from 'boardgame-core';
import {getAvailableMoves, OnitamaState} from "boardgame-core/samples/onitama";
import { mutableSignal, computed } from "boardgame-core";
import { getAvailableMoves } from "boardgame-core/samples/onitama";
import type { MutableSignal, ReadonlySignal } from "boardgame-core";
import type { OnitamaState } from "boardgame-core/samples/onitama";
export interface ValidMove {
card: string;
@@ -22,18 +25,26 @@ export function createUIState(): MutableSignal<OnitamaUIState> {
});
}
export function createValidMoves(state: ReadonlySignal<OnitamaState>, ui: ReadonlySignal<OnitamaUIState>){
export function createValidMoves(
state: ReadonlySignal<OnitamaState>,
ui: ReadonlySignal<OnitamaUIState>,
) {
return computed(() => {
return getAvailableMoves(state.value, state.value.currentPlayer)
.filter(move => {
const {selectedCard, selectedPiece} = ui.value;
return selectedPiece?.x === move.fromX && selectedPiece?.y === move.fromY && selectedCard === move.card;
})
return getAvailableMoves(state.value, state.value.currentPlayer).filter(
(move) => {
const { selectedCard, selectedPiece } = ui.value;
return (
selectedPiece?.x === move.fromX &&
selectedPiece?.y === move.fromY &&
selectedCard === move.card
);
},
);
});
}
export function clearSelection(uiState: MutableSignal<OnitamaUIState>): void {
uiState.produce(state => {
uiState.produce((state) => {
state.selectedPiece = null;
state.selectedCard = null;
});
@@ -42,13 +53,13 @@ export function clearSelection(uiState: MutableSignal<OnitamaUIState>): void {
export function selectPiece(
uiState: MutableSignal<OnitamaUIState>,
x: number,
y: number
y: number,
): void {
uiState.produce(state => {
uiState.produce((state) => {
// 如果点击已选中的棋子,取消选择
if(state.selectedPiece?.x === x && state.selectedPiece?.y === y){
if (state.selectedPiece?.x === x && state.selectedPiece?.y === y) {
state.selectedPiece = null;
}else{
} else {
state.selectedPiece = { x, y };
}
});
@@ -56,9 +67,9 @@ export function selectPiece(
export function selectCard(
uiState: MutableSignal<OnitamaUIState>,
card: string
card: string,
): void {
uiState.produce(state => {
uiState.produce((state) => {
// 如果点击已选中的卡牌,取消选择
if (state.selectedCard === card) {
state.selectedCard = null;