refactor(onitama): centralize visual constants in config
Introduce `TEXT_POSITION` and `VISUAL` objects to the configuration to manage magic numbers for positioning, radii, stroke widths, and alphas. Update renderers and spawners to use these constants instead of hardcoded values or direct calculations.
This commit is contained in:
parent
22817945cc
commit
368d9942d2
|
|
@ -113,6 +113,13 @@ export const MENU_BUTTON = {
|
|||
height: 40,
|
||||
} as const;
|
||||
|
||||
// Text positioning
|
||||
export const TEXT_POSITION = {
|
||||
titleX: 40,
|
||||
titleY: 40,
|
||||
infoX: 40,
|
||||
} as const;
|
||||
|
||||
// Animation durations (in ms)
|
||||
export const ANIMATIONS = {
|
||||
pawnSpawn: 300,
|
||||
|
|
@ -139,6 +146,23 @@ export const CARD_GRID = {
|
|||
gridSize: 5,
|
||||
} as const;
|
||||
|
||||
// Visual style constants
|
||||
export const VISUAL = {
|
||||
pawnRadius: CELL_SIZE / 3,
|
||||
pawnStrokeWidth: 2,
|
||||
selectionRingOffset: 5,
|
||||
selectionRingStrokeWidth: 3,
|
||||
highlightOuterRadius: CELL_SIZE / 3,
|
||||
highlightInnerRadius: CELL_SIZE / 4,
|
||||
highlightHitAreaRadius: CELL_SIZE / 3,
|
||||
cardStrokeWidth: 2,
|
||||
cardTitleOffset: 16,
|
||||
cardPlayerOffset: 16,
|
||||
cardDisabledAlpha: 0.8,
|
||||
overlayAlpha: 0.6,
|
||||
cardBackgroundDepth: -1,
|
||||
} as const;
|
||||
|
||||
// Helper function to convert board coordinates to screen coordinates
|
||||
export function boardToScreen(
|
||||
boardX: number,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
import type { Card } from "@/game/onitama";
|
||||
import type { OnitamaScene } from "@/scenes/OnitamaScene";
|
||||
|
||||
import { CARD_WIDTH, CARD_HEIGHT, COLORS, FONTS, CARD_GRID } from "@/config";
|
||||
import {
|
||||
CARD_WIDTH,
|
||||
CARD_HEIGHT,
|
||||
COLORS,
|
||||
FONTS,
|
||||
CARD_GRID,
|
||||
VISUAL,
|
||||
} from "@/config";
|
||||
|
||||
export interface CardRenderOptions {
|
||||
card: Card;
|
||||
|
|
@ -28,12 +35,17 @@ export class CardRenderer {
|
|||
// Create background rectangle
|
||||
const bg = this.scene.add
|
||||
.rectangle(0, 0, CARD_WIDTH, CARD_HEIGHT, COLORS.cardBg, 1)
|
||||
.setStrokeStyle(2, COLORS.cardStroke);
|
||||
.setStrokeStyle(VISUAL.cardStrokeWidth, COLORS.cardStroke);
|
||||
container.add(bg);
|
||||
|
||||
// Create title text
|
||||
const title = this.scene.add
|
||||
.text(0, -CARD_HEIGHT / 2 + 16, card.id, FONTS.cardTitle)
|
||||
.text(
|
||||
0,
|
||||
-CARD_HEIGHT / 2 + VISUAL.cardTitleOffset,
|
||||
card.id,
|
||||
FONTS.cardTitle,
|
||||
)
|
||||
.setOrigin(0.5);
|
||||
container.add(title);
|
||||
|
||||
|
|
@ -42,7 +54,12 @@ export class CardRenderer {
|
|||
|
||||
// Create starting player text
|
||||
const playerText = this.scene.add
|
||||
.text(0, CARD_HEIGHT / 2 - 16, card.startingPlayer, FONTS.cardPlayer)
|
||||
.text(
|
||||
0,
|
||||
CARD_HEIGHT / 2 - VISUAL.cardPlayerOffset,
|
||||
card.startingPlayer,
|
||||
FONTS.cardPlayer,
|
||||
)
|
||||
.setOrigin(0.5);
|
||||
container.add(playerText);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import type { OnitamaScene } from "@/scenes/OnitamaScene";
|
||||
|
||||
import {
|
||||
CELL_SIZE,
|
||||
COLORS,
|
||||
VISUAL,
|
||||
createHighlightInnerPulseTween,
|
||||
createHighlightOuterPulseTween,
|
||||
} from "@/config";
|
||||
|
|
@ -37,7 +37,7 @@ export class HighlightRenderer {
|
|||
const outerCircle = this.scene.add.circle(
|
||||
0,
|
||||
0,
|
||||
CELL_SIZE / 3,
|
||||
VISUAL.highlightOuterRadius,
|
||||
COLORS.black,
|
||||
0.2,
|
||||
);
|
||||
|
|
@ -47,7 +47,7 @@ export class HighlightRenderer {
|
|||
const innerCircle = this.scene.add.circle(
|
||||
0,
|
||||
0,
|
||||
CELL_SIZE / 4,
|
||||
VISUAL.highlightInnerRadius,
|
||||
COLORS.black,
|
||||
0.4,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { OnitamaScene } from "@/scenes/OnitamaScene";
|
||||
|
||||
import { CELL_SIZE, COLORS, FONTS } from "@/config";
|
||||
import { CELL_SIZE, COLORS, FONTS, VISUAL } from "@/config";
|
||||
|
||||
export type PawnType = "master" | "student";
|
||||
export type PawnOwner = "red" | "black";
|
||||
|
|
@ -31,8 +31,8 @@ export class PawnRenderer {
|
|||
// Create background circle
|
||||
const bgColor = owner === "red" ? COLORS.red : COLORS.black;
|
||||
const circle = this.scene.add
|
||||
.circle(0, 0, CELL_SIZE / 3, bgColor, 1)
|
||||
.setStrokeStyle(2, COLORS.pawnStroke);
|
||||
.circle(0, 0, VISUAL.pawnRadius, bgColor, 1)
|
||||
.setStrokeStyle(VISUAL.pawnStrokeWidth, COLORS.pawnStroke);
|
||||
container.add(circle);
|
||||
|
||||
// Create label text
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import type { OnitamaScene } from "@/scenes/OnitamaScene";
|
|||
import {
|
||||
CELL_SIZE,
|
||||
COLORS,
|
||||
VISUAL,
|
||||
createSelectionShowTween,
|
||||
createSelectionRingPulseTween,
|
||||
createSelectionHideTween,
|
||||
|
|
@ -31,8 +32,21 @@ export class SelectionRenderer {
|
|||
parent: Phaser.GameObjects.Container | Phaser.GameObjects.GameObject,
|
||||
): Phaser.GameObjects.Arc {
|
||||
const ring = this.scene.add
|
||||
.arc(0, 0, CELL_SIZE / 3 + 5, 0, 360, false, COLORS.highlight, 0)
|
||||
.setStrokeStyle(3, COLORS.highlightStroke, 1)
|
||||
.arc(
|
||||
0,
|
||||
0,
|
||||
VISUAL.pawnRadius + VISUAL.selectionRingOffset,
|
||||
0,
|
||||
360,
|
||||
false,
|
||||
COLORS.highlight,
|
||||
0,
|
||||
)
|
||||
.setStrokeStyle(
|
||||
VISUAL.selectionRingStrokeWidth,
|
||||
COLORS.highlightStroke,
|
||||
1,
|
||||
)
|
||||
.setAlpha(0);
|
||||
|
||||
// Add to parent at index 0 (behind other visuals)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import {
|
|||
COLORS,
|
||||
FONTS,
|
||||
MENU_BUTTON,
|
||||
TEXT_POSITION,
|
||||
VISUAL,
|
||||
getBoardCenter,
|
||||
getCardLabelPosition,
|
||||
colorToStr,
|
||||
|
|
@ -79,7 +81,12 @@ export class OnitamaScene extends GameHostScene<OnitamaState> {
|
|||
});
|
||||
|
||||
// Info text
|
||||
this.infoText = this.add.text(40, BOARD_OFFSET.y, "", FONTS.info);
|
||||
this.infoText = this.add.text(
|
||||
TEXT_POSITION.infoX,
|
||||
BOARD_OFFSET.y,
|
||||
"",
|
||||
FONTS.info,
|
||||
);
|
||||
|
||||
// Update info text when UI state changes
|
||||
this.addEffect(() => {
|
||||
|
|
@ -149,7 +156,12 @@ export class OnitamaScene extends GameHostScene<OnitamaState> {
|
|||
|
||||
g.strokePath();
|
||||
|
||||
this.add.text(40, 40, "Onitama", FONTS.title);
|
||||
this.add.text(
|
||||
TEXT_POSITION.titleX,
|
||||
TEXT_POSITION.titleY,
|
||||
"Onitama",
|
||||
FONTS.title,
|
||||
);
|
||||
}
|
||||
|
||||
private setupInput(): void {
|
||||
|
|
@ -288,7 +300,7 @@ export class OnitamaScene extends GameHostScene<OnitamaState> {
|
|||
boardWidth,
|
||||
boardHeight,
|
||||
COLORS.overlayBg,
|
||||
0.6,
|
||||
VISUAL.overlayAlpha,
|
||||
)
|
||||
.setInteractive({ useHandCursor: true });
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import type { Spawner } from "boardgame-phaser";
|
|||
import {
|
||||
CARD_WIDTH,
|
||||
CARD_HEIGHT,
|
||||
COLORS,
|
||||
VISUAL,
|
||||
getCardPosition,
|
||||
createCardMoveTween,
|
||||
createCardRotateTween,
|
||||
|
|
@ -102,7 +104,7 @@ export class CardContainer extends Phaser.GameObjects.Container {
|
|||
.rectangle(0, 0, CARD_WIDTH + 8, CARD_HEIGHT + 8, color, 0)
|
||||
.setStrokeStyle(lineWidth, color)
|
||||
.setAlpha(0)
|
||||
.setDepth(-1);
|
||||
.setDepth(VISUAL.cardBackgroundDepth);
|
||||
this.highlightRect = rect;
|
||||
this.addAt(this.highlightRect, 0);
|
||||
|
||||
|
|
@ -161,7 +163,7 @@ export class CardContainer extends Phaser.GameObjects.Container {
|
|||
// 创建一个 effect 来持续监听高亮状态变化
|
||||
const dispose = effect(() => {
|
||||
if (scene.uiState.value.selectedCard === this._cardId) {
|
||||
this.highlight(0xfbbf24, 3);
|
||||
this.highlight(COLORS.highlight, VISUAL.selectionRingStrokeWidth);
|
||||
} else {
|
||||
this.unhighlight();
|
||||
}
|
||||
|
|
@ -276,7 +278,7 @@ export class CardSpawner implements Spawner<CardSpawnData, CardContainer> {
|
|||
// 设置悬停效果
|
||||
container.on("pointerover", () => {
|
||||
if (this.scene.uiState.value.selectedCard !== data.cardId) {
|
||||
container.setAlpha(0.8);
|
||||
container.setAlpha(VISUAL.cardDisabledAlpha);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import type { Spawner } from "boardgame-phaser";
|
|||
import {
|
||||
boardToScreen,
|
||||
CELL_SIZE,
|
||||
VISUAL,
|
||||
createHighlightSpawnTween,
|
||||
createHighlightDespawnTween,
|
||||
createHighlightClickFeedbackTween,
|
||||
|
|
@ -83,7 +84,7 @@ export class HighlightSpawner implements Spawner<
|
|||
this.renderer.render(container, { x: data.x, y: data.y });
|
||||
|
||||
// 设置交互区域
|
||||
const hitArea = new Geom.Circle(0, 0, CELL_SIZE / 3);
|
||||
const hitArea = new Geom.Circle(0, 0, VISUAL.highlightHitAreaRadius);
|
||||
container.setInteractive(hitArea, Phaser.Geom.Circle.Contains);
|
||||
if (container.input) {
|
||||
container.input.cursor = "pointer";
|
||||
|
|
|
|||
|
|
@ -17,6 +17,19 @@ export class ShapeViewerScene extends ReactiveScene {
|
|||
super.create();
|
||||
this.drawShapeViewer();
|
||||
this.createControls();
|
||||
this.createBackButton();
|
||||
}
|
||||
|
||||
private createBackButton(): void {
|
||||
createButton({
|
||||
scene: this,
|
||||
label: "← Back",
|
||||
x: 80,
|
||||
y: 30,
|
||||
onClick: async () => {
|
||||
await this.sceneController.launch(SceneKey.IndexScene);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private drawShapeViewer(): void {
|
||||
|
|
|
|||
Loading…
Reference in New Issue