diff --git a/src/games/bark-battle/ui/BarkBattleHud.css b/src/games/bark-battle/ui/BarkBattleHud.css
index c59ee3e8..b4763410 100644
--- a/src/games/bark-battle/ui/BarkBattleHud.css
+++ b/src/games/bark-battle/ui/BarkBattleHud.css
@@ -46,9 +46,11 @@
}
.bark-battle-dog {
+ position: relative;
display: grid;
place-items: center;
gap: 8px;
+ animation: barkBattleDogPulse 420ms ease-out;
}
.bark-battle-dog__body {
@@ -61,11 +63,29 @@
}
.bark-battle-dog__label,
+.bark-battle-dog__burst,
.bark-battle-vs {
font-weight: 900;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
}
+.bark-battle-dog__burst {
+ position: absolute;
+ top: -18px;
+ border-radius: 999px;
+ padding: 5px 10px;
+ color: #1f1147;
+ background: #facc15;
+ box-shadow: 0 0 22px rgba(250, 204, 21, 0.72);
+ animation: barkBattleBurst 640ms ease-out both;
+}
+
+.bark-battle-dog--opponent .bark-battle-dog__burst {
+ color: #fff7ed;
+ background: #7c3aed;
+ box-shadow: 0 0 22px rgba(124, 58, 237, 0.72);
+}
+
.bark-battle-vs {
border-radius: 999px;
padding: 10px 18px;
@@ -176,6 +196,27 @@
margin-top: 10px;
}
+.bark-battle-debug-metrics,
+.bark-battle-debug-events {
+ margin: 10px 0 0;
+ padding: 10px;
+ border-radius: 16px;
+ background: rgba(255, 255, 255, 0.1);
+ font-size: 12px;
+}
+
+.bark-battle-debug-metrics {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 6px;
+}
+
+.bark-battle-debug-events {
+ display: grid;
+ gap: 4px;
+ padding-left: 28px;
+}
+
.bark-battle-debug-panel__controls button {
flex: 1;
border: 0;
@@ -186,6 +227,18 @@
font-weight: 800;
}
+@keyframes barkBattleDogPulse {
+ from { transform: scale(1); }
+ 45% { transform: scale(1.08); }
+ to { transform: scale(1); }
+}
+
+@keyframes barkBattleBurst {
+ from { transform: translateY(18px) scale(0.72); opacity: 0; }
+ 35% { opacity: 1; }
+ to { transform: translateY(-38px) scale(1.16); opacity: 0; }
+}
+
@keyframes barkBattleParticlePop {
from { transform: translateY(28px) scale(0.7); opacity: 0; }
42% { opacity: 1; }
diff --git a/src/games/bark-battle/ui/BarkBattleHud.tsx b/src/games/bark-battle/ui/BarkBattleHud.tsx
index 8391da87..72210648 100644
--- a/src/games/bark-battle/ui/BarkBattleHud.tsx
+++ b/src/games/bark-battle/ui/BarkBattleHud.tsx
@@ -4,6 +4,8 @@ import type { BarkBattleSnapshot } from '../domain/BarkBattleTypes';
type BarkBattleHudProps = {
snapshot: BarkBattleSnapshot;
+ playerPulseKey?: number;
+ opponentPulseKey?: number;
onStartMicrophone?: () => void;
onMockBark?: () => void;
onMockQuiet?: () => void;
@@ -24,6 +26,8 @@ const failureText = {
export function BarkBattleHud({
snapshot,
+ playerPulseKey = 0,
+ opponentPulseKey = 0,
onStartMicrophone,
onMockBark,
onMockQuiet,
@@ -61,12 +65,14 @@ export function BarkBattleHud({
) : (
-
+
+ 汪
🐕
你 · {snapshot.player.barkCount}
VS
-
+
+ 反击
🐶
对手 · {snapshot.opponent.barkCount}
diff --git a/src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx b/src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx
index 2ef9d227..add39648 100644
--- a/src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx
+++ b/src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useRef, useState } from 'react';
+import { useCallback, useEffect, useRef, useState } from 'react';
import {
type BarkBattleConfig,
@@ -12,6 +12,11 @@ type BarkBattleRuntimeShellProps = {
title?: string;
};
+type DebugEvent = {
+ id: number;
+ text: string;
+};
+
const DEBUG_CONFIG_FIELDS: Array<{
key: keyof Pick<
BarkBattleConfig,
@@ -46,12 +51,39 @@ export function BarkBattleRuntimeShell({ title = '汪汪声浪大作战' }: Bark
const controller = controllerRef.current;
const [snapshot, setSnapshot] = useState(() => controller.getSnapshot());
const [particleText, setParticleText] = useState('');
+ const [playerPulseKey, setPlayerPulseKey] = useState(0);
+ const [opponentPulseKey, setOpponentPulseKey] = useState(0);
+ const [debugEvents, setDebugEvents] = useState
([]);
const heldRef = useRef(false);
+ const lastPlayerBarkCountRef = useRef(0);
+ const lastOpponentPowerRef = useRef(0);
+ const debugEventIdRef = useRef(0);
+
+ const appendDebugEvent = useCallback((text: string) => {
+ debugEventIdRef.current += 1;
+ const event = { id: debugEventIdRef.current, text };
+ setDebugEvents((current) => [event, ...current].slice(0, 5));
+ }, []);
+
+ const syncSnapshot = useCallback(() => {
+ const nextSnapshot = controller.getSnapshot();
+ if (nextSnapshot.player.barkCount > lastPlayerBarkCountRef.current) {
+ setPlayerPulseKey((current) => current + 1);
+ appendDebugEvent(`玩家叫声触发 #${nextSnapshot.player.barkCount} · 能量 ${Math.round(nextSnapshot.energy)}`);
+ }
+ if (nextSnapshot.phase === 'playing' && Math.abs(nextSnapshot.opponent.power - lastOpponentPowerRef.current) >= 0.08) {
+ setOpponentPulseKey((current) => current + 1);
+ appendDebugEvent(`对手反击强度 ${(nextSnapshot.opponent.power * 100).toFixed(0)}%`);
+ }
+ lastPlayerBarkCountRef.current = nextSnapshot.player.barkCount;
+ lastOpponentPowerRef.current = nextSnapshot.opponent.power;
+ setSnapshot(nextSnapshot);
+ }, [appendDebugEvent, controller]);
useEffect(() => {
controller.updateConfig(config);
- setSnapshot(controller.getSnapshot());
- }, [config, controller]);
+ syncSnapshot();
+ }, [config, controller, syncSnapshot]);
useEffect(() => {
const timer = window.setInterval(() => {
@@ -61,31 +93,38 @@ export function BarkBattleRuntimeShell({ title = '汪汪声浪大作战' }: Bark
} else {
controller.submitMockSample(0.12);
}
- setSnapshot(controller.getSnapshot());
+ syncSnapshot();
}, 100);
return () => window.clearInterval(timer);
- }, [controller]);
+ }, [controller, syncSnapshot]);
const restart = () => {
heldRef.current = false;
controller.restart();
setParticleText('');
- setSnapshot(controller.getSnapshot());
+ setDebugEvents([]);
+ lastPlayerBarkCountRef.current = 0;
+ lastOpponentPowerRef.current = 0;
+ syncSnapshot();
};
const startMock = () => {
controller.startWithMockInput();
- setSnapshot(controller.getSnapshot());
+ appendDebugEvent('开始 mock 对局');
+ syncSnapshot();
};
const finishNow = () => {
heldRef.current = false;
controller.finishNow();
- setSnapshot(controller.getSnapshot());
+ appendDebugEvent('人工结束对局');
+ syncSnapshot();
};
const bark = () => {
heldRef.current = true;
+ setPlayerPulseKey((current) => current + 1);
+ appendDebugEvent('按下模拟叫声按钮');
setParticleText('汪!');
window.setTimeout(() => setParticleText(''), 680);
};
@@ -94,6 +133,8 @@ export function BarkBattleRuntimeShell({ title = '汪汪声浪大作战' }: Bark
{
@@ -111,6 +152,15 @@ export function BarkBattleRuntimeShell({ title = '汪汪声浪大作战' }: Bark
+
+ 玩家触发:{snapshot.player.barkCount}
+ 玩家强度:{(snapshot.player.power * 100).toFixed(0)}%
+ 对手强度:{(snapshot.opponent.power * 100).toFixed(0)}%
+ 能量:{Math.round(snapshot.energy)}
+
+
+ {debugEvents.length ? debugEvents.map((event) => - {event.text}
) : - 等待输入触发
}
+
{DEBUG_CONFIG_FIELDS.map((field) => (