A webcam-controlled boxing/combat game. Punch the air toward the camera to attack an AI boss; hold an open palm steady to block its telegraphed attacks. No game engine — just OpenCV + MediaPipe for input and Pygame for rendering.
pip install -r requirements.txt
python hadouken.py- Punch — thrust your hand quickly (any direction toward the camera works well)
- Block — hold an open palm (fingers spread) steady for ~0.2s
C— toggle the small camera preview (bottom-right)R— restart after game overESC— quit
The camera capture and MediaPipe hand-tracking run in a separate background thread. The pygame render loop never waits on the camera — it just reads whatever the latest gesture snapshot is, every frame, at a fixed 60 FPS. So even if the camera thread has a slow frame (bad lighting, motion blur, laptop under load), the game itself never stutters.
Other speed choices:
- MediaPipe processes a downscaled 320×240 frame, not full camera resolution
model_complexity=0(the lightest hand-tracking model)- Punch detection uses a simple wrist-velocity spike with hysteresis (arm/disarm thresholds) — cheap to compute, no ML classifier needed
- Camera preview image is only rebuilt every 2nd frame
Lighting and camera angle vary a lot between rooms. Do a 2-minute calibration pass on-site:
- Run the game, watch the on-screen Cam FPS counter — if it's below
~15, move to better lighting or lower
CAM_CAPTURE_W/Hfurther. - If punches don't register: lower
PUNCH_SPEED_THRESHOLDin the CONFIG section at the top of the file (try steps of 0.2). - If punches fire too easily from normal hand movement: raise
PUNCH_SPEED_THRESHOLD, or raisePUNCH_RESET_THRESHOLDslightly. - If blocking is too easy/hard to trigger: adjust
BLOCK_HOLD_TIMEor thesum(fingers) >= 3condition inHandWorker._process_landmarks.
- Two-player mode: set
max_num_hands=2and split screen zones by x-position of each detected hand. - Special move: track the last ~20 wrist positions and match the path shape (e.g. circle) for a bonus attack.
- Sound: add
pygame.mixerhits on punch/block/attack events — theGame.update_playingmethod already has clearly marked event points (event == "attack_landed"etc.) to hook sounds into.