feat: complete bark battle draft publish flow

This commit is contained in:
2026-05-19 15:27:50 +08:00
parent 804f1e32be
commit 23fb895e82
24 changed files with 1710 additions and 159 deletions

View File

@@ -1,6 +1,7 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { BarkBattlePublishedConfig } from '../../../../packages/shared/src/contracts/barkBattle';
import { useResolvedAssetReadUrl } from '../../../hooks/useResolvedAssetReadUrl';
import {
type BarkBattleConfig,
DEFAULT_BARK_BATTLE_CONFIG,
@@ -113,11 +114,25 @@ export function BarkBattleRuntimeShell({
const [playerPulseKey, setPlayerPulseKey] = useState(0);
const [opponentPulseKey, setOpponentPulseKey] = useState(0);
const [debugEvents, setDebugEvents] = useState<DebugEvent[]>([]);
const barkAudioRef = useRef<HTMLAudioElement | null>(null);
const heldRef = useRef(false);
const lastPlayerBarkCountRef = useRef(0);
const lastOpponentPowerRef = useRef(0);
const debugEventIdRef = useRef(0);
const microphoneSamplerRef = useRef<BrowserMicrophoneSampler | null>(null);
const replacementConfig = publishedConfig ?? null;
const { resolvedUrl: resolvedBarkSoundSrc } = useResolvedAssetReadUrl(
replacementConfig?.barkSoundSrc ?? null,
);
const playBarkSound = useCallback(() => {
const audio = barkAudioRef.current;
if (!audio || !resolvedBarkSoundSrc) {
return;
}
audio.currentTime = 0;
void audio.play().catch(() => {});
}, [resolvedBarkSoundSrc]);
const appendDebugEvent = useCallback((text: string) => {
debugEventIdRef.current += 1;
@@ -129,16 +144,18 @@ export function BarkBattleRuntimeShell({
const nextSnapshot = controller.getSnapshot();
if (nextSnapshot.player.barkCount > lastPlayerBarkCountRef.current) {
setPlayerPulseKey((current) => current + 1);
playBarkSound();
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);
playBarkSound();
appendDebugEvent(`对手反击强度 ${(nextSnapshot.opponent.power * 100).toFixed(0)}%`);
}
lastPlayerBarkCountRef.current = nextSnapshot.player.barkCount;
lastOpponentPowerRef.current = nextSnapshot.opponent.power;
setSnapshot(nextSnapshot);
}, [appendDebugEvent, controller]);
}, [appendDebugEvent, controller, playBarkSound]);
const stopMicrophone = useCallback(() => {
microphoneSamplerRef.current?.stop();
@@ -237,10 +254,16 @@ export function BarkBattleRuntimeShell({
return (
<main className="bark-battle-runtime" aria-label={title}>
{resolvedBarkSoundSrc ? (
<audio ref={barkAudioRef} src={resolvedBarkSoundSrc} preload="auto" />
) : null}
<BarkBattleHud
snapshot={snapshot}
playerPulseKey={playerPulseKey}
opponentPulseKey={opponentPulseKey}
playerCharacterImageSrc={replacementConfig?.playerCharacterImageSrc}
opponentCharacterImageSrc={replacementConfig?.opponentCharacterImageSrc}
uiBackgroundImageSrc={replacementConfig?.uiBackgroundImageSrc}
onStartMicrophone={startMicrophone}
onMockBark={bark}
onMockQuiet={() => {