microduck-reachy-simulator / src /ui /MicroduckControls.jsx
laureen
Withdraw Code Training from published main pending fixes
99e34b4
Raw
History Blame Contribute Delete
4.78 kB
import { useState } from "react";
import { gameApi, useGame } from "../store.js";
import { Quickbar, SoundToggle } from "./Hud.jsx";
import SimulatorPanel from "./SimulatorPanel.jsx";
export default function MicroduckControls() {
const loco = useGame((s) => s.loco);
const mode = useGame((s) => s.modeLabel);
const locked = useGame((s) => s.emotionBusy || s.locoSwitching || s.scriptPlaying);
const recording = useGame((s) => s.scriptRecording);
const [head, setHead] = useState([0, 0, 0, 0]);
const release = () => gameApi.setDrive?.(0, 0);
const drive = (e, forward, turn) => {
e.currentTarget.setPointerCapture(e.pointerId);
if (recording) gameApi.script?.add(`${forward > 0 ? "Forward" : forward < 0 ? "Backwards" : turn > 0 ? "Turn left" : "Turn right"}`, async () => { gameApi.setDrive?.(forward, turn); await gameApi.script.wait(1); gameApi.setDrive?.(0, 0); });
else gameApi.setDrive?.(forward, turn);
};
const action = (label, fn) => recording ? gameApi.script?.add(label, async () => { await fn(); await gameApi.script.wait(0.4); }) : fn();
return <SimulatorPanel side="right" title="Microduck">
<p role="status">{mode} 路 {loco}</p>
<fieldset disabled={locked}>
<legend>Move 路 hold to drive</legend>
<div className="sim-buttons">{[["Forward", 1, 0], ["Backwards", -1, 0], ["Turn left", 0, 1], ["Turn right", 0, -1]].map(([label, forward, turn]) => <button key={label} onPointerDown={(e) => drive(e, forward, turn)} onPointerUp={release} onPointerCancel={release} onLostPointerCapture={release} onKeyDown={(e) => { if (e.key === " " || e.key === "Enter") { e.preventDefault(); gameApi.setDrive?.(forward, turn); } }} onKeyUp={release} onBlur={release}>{label}</button>)}</div>
<legend>Actions</legend>
<div className="sim-buttons">
<button disabled={loco !== "legs"} onClick={() => action("Kick left", () => gameApi.triggerKick?.("left"))}>Kick left</button>
<button disabled={loco !== "legs"} onClick={() => action("Kick right", () => gameApi.triggerKick?.("right"))}>Kick right</button>
<button disabled={loco !== "legs"} onClick={() => action("Roll", () => gameApi.triggerRoll?.())}>Roll</button>
<button disabled={loco !== "legs"} onClick={() => action("Sit", () => gameApi.setMode?.("sit"))}>Sit</button>
<button onClick={() => action("Stand / Walk", () => gameApi.setMode?.("walk"))}>Stand / Walk</button>
<button disabled={loco !== "rollers"} onClick={() => action("Crouch", () => gameApi.triggerCrouch?.())}>Crouch</button>
<button disabled={loco !== "legs"} onClick={() => action("Ground pick", () => gameApi.triggerGroundPick?.())}>Ground pick</button>
<button onClick={() => action("Microduck quack", () => gameApi.quack?.())}>Quack</button>
<button onClick={() => action("Ball", () => gameApi.spawnBall?.())}>Ball</button>
<button onClick={() => action("Reset Microduck", () => gameApi.resetSim?.())}>Reset</button>
</div>
<legend>Emotions</legend>
<div className="sim-buttons">{[["joy", "Happy"], ["sadness", "Sad"], ["anger", "Angry"]].map(([id, label]) => <button key={id} onClick={() => action(`Microduck ${label}`, () => gameApi.playMicroduckEmotion?.(id))}>{label}</button>)}</div>
<legend>Head</legend>
<button onClick={() => action("Toggle head control", () => gameApi.toggleHeadMode?.())}>Toggle gamepad head control</button>
{["Neck pitch", "Head pitch", "Head yaw", "Head roll"].map((label, i) => <label className="sim-field" key={label}><span>{label}</span><input type="range" min="-0.6" max="0.6" step="0.02" value={head[i]} onChange={(e) => { const next = [...head]; next[i] = Number(e.target.value); setHead(next); gameApi.setHead?.(next); }} /></label>)}
<button onClick={() => action("Neutral head", () => { setHead([0, 0, 0, 0]); gameApi.setHead?.([0, 0, 0, 0]); })}>Neutral head</button>
<legend>Appearance & locomotion</legend>
<Quickbar />
<legend>Camera</legend>
<div className="sim-buttons"><button onClick={() => action("Chase camera", () => gameApi.toggleCamera?.())}>Chase camera</button><button onClick={() => action("Both robots camera", () => gameApi.sharedCamera?.())}>Both robots</button></div>
</fieldset>
<SoundToggle />
<details className="sim-help"><summary>Keyboard & gamepad</summary><p>Arrows / WASD / ZQSD: walk and turn. Q/E: kicks. F: alternate kicks. R: sit/stand or crouch. G: ground pick. M: legs/rollers. Space: reset. C: chase camera. Drag: orbit. Scroll: zoom.</p><p>Gamepad: left stick moves; right stick orbits; Y controls head; LB/RB kick; X or D-pad down sits; A picks; hold D-pad up switches mode; RT quacks; LT wheee; R3 chase; Menu pauses.</p></details>
</SimulatorPanel>;
}