Skip to main content

๐ŸŽฏ AI Shooting Game Using Python & ESP32-CAM | Hand Tracking Game


In this project, we will build an exciting AI Hand Gesture Shooting Game using Python, OpenCV, and MediaPipe. Instead of using a mouse, you will control the crosshair with your finger and shoot using a pinch gesture ๐Ÿค.

๐Ÿ’ฐ Pro Tip: If you want to make this project portable and advanced, you can use an ESP32-CAM module instead of a normal camera and stream video to your laptop.

๐Ÿงฐ 1. Hardware Required

  • ๐Ÿ’ป Laptop / PC (Windows recommended)
  • ๐Ÿ“ท ESP32-CAM Module (Recommended for smart AI projects)
  • ๐Ÿ”Œ USB to TTL Converter (for programming ESP32-CAM)
  • ⚡ Jumper Wires
  • ๐Ÿ“ถ WiFi Connection

๐Ÿ‘‰ Recommended Module: ESP32-CAM (OV2640 Camera)


๐Ÿ“š 2. Python Libraries Required

Open Command Prompt and install the following libraries:

pip install opencv-python mediapipe

That’s it! Now you're ready to run the AI game ๐Ÿš€


๐Ÿง  3. Complete Python Code

import cv2
import mediapipe as mp
import random
import math
import time

# =============================
# SETUP HAND TRACKING
# =============================
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(max_num_hands=1)
mp_draw = mp.solutions.drawing_utils

cap = cv2.VideoCapture(0)

# Fullscreen mode
cv2.namedWindow("AI Shooting Game", cv2.WND_PROP_FULLSCREEN)
cv2.setWindowProperty("AI Shooting Game", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)

# =============================
# GAME VARIABLES
# =============================
score = 0
disc_radius = 30
disc_speed = 6
disc_x = 0
disc_y = 200

last_shot_time = 0
cooldown = 0.5  # time between shots

# Create new disc
def new_disc(width, height):
    x = 0
    y = random.randint(100, height - 100)
    return x, y

# =============================
# MAIN LOOP
# =============================
while True:
    success, frame = cap.read()
    if not success:
        break

    frame = cv2.flip(frame, 1)
    height, width, _ = frame.shape

    rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    result = hands.process(rgb)

    cross_x = width // 2
    cross_y = height // 2
    shoot = False

    # =============================
    # HAND DETECTION
    # =============================
    if result.multi_hand_landmarks:
        for hand_landmarks in result.multi_hand_landmarks:

            landmarks = hand_landmarks.landmark

            # INDEX FINGER TIP (8)
            index_tip = landmarks[8]
            cross_x = int(index_tip.x * width)
            cross_y = int(index_tip.y * height)

            # THUMB TIP (4)
            thumb_tip = landmarks[4]
            thumb_x = int(thumb_tip.x * width)
            thumb_y = int(thumb_tip.y * height)

            # Draw green circle on index
            cv2.circle(frame, (cross_x, cross_y), 12, (0, 255, 0), -1)

            # Draw blue circle on thumb
            cv2.circle(frame, (thumb_x, thumb_y), 12, (255, 0, 0), -1)

            # -------- PINCH DETECTION --------
            distance = math.sqrt((cross_x - thumb_x)**2 +
                                 (cross_y - thumb_y)**2)

            if distance < 40:  # Sensitivity
                current_time = time.time()
                if current_time - last_shot_time > cooldown:
                    shoot = True
                    last_shot_time = current_time

    # =============================
    # DISC MOVEMENT
    # =============================
    disc_x += disc_speed

    if disc_x > width:
        disc_x, disc_y = new_disc(width, height)

    # Draw flying disc
    cv2.circle(frame, (disc_x, disc_y), disc_radius, (255, 200, 0), -1)
    cv2.circle(frame, (disc_x, disc_y), disc_radius, (0, 0, 0), 3)

    # =============================
    # SHOOT CHECK
    # =============================
    if shoot:
        distance_to_disc = math.sqrt((cross_x - disc_x)**2 +
                                     (cross_y - disc_y)**2)

        if distance_to_disc < disc_radius:
            score += 1
            disc_x, disc_y = new_disc(width, height)

        # Small shoot flash
        cv2.putText(frame, "SHOOT!",
                    (cross_x - 40, cross_y - 40),
                    cv2.FONT_HERSHEY_SIMPLEX,
                    1,
                    (0, 255, 255),
                    3)

    # =============================
    # DRAW RED CROSSHAIR
    # =============================
    cv2.line(frame, (cross_x - 20, cross_y),
             (cross_x + 20, cross_y), (0, 0, 255), 2)
    cv2.line(frame, (cross_x, cross_y - 20),
             (cross_x, cross_y + 20), (0, 0, 255), 2)
    cv2.circle(frame, (cross_x, cross_y), 8, (0, 0, 255), 2)

    # =============================
    # DISPLAY SCORE
    # =============================
    cv2.putText(frame, "Score: " + str(score),
                (50, 70),
                cv2.FONT_HERSHEY_SIMPLEX,
                1.5,
                (0, 255, 0),
                3)

    cv2.imshow("AI Shooting Game", frame)

    # ESC to exit
    if cv2.waitKey(1) == 27:
        break

cap.release()
cv2.destroyAllWindows()

๐Ÿ” 4. Code Explanation (Block by Block)

๐Ÿ“Œ Import Libraries

import cv2
import mediapipe as mp
import random
import math
import time

cv2 → Used for video processing ✅ mediapipe → Used for hand tracking ✅ random → To generate random disc positions ✅ math → For distance calculation ✅ time → For shooting cooldown system


๐Ÿ“Œ Setup Hand Tracking

mp_hands = mp.solutions.hands
hands = mp_hands.Hands(max_num_hands=1)
mp_draw = mp.solutions.drawing_utils

This initializes MediaPipe Hand Detection. We detect only one hand for better performance.


๐Ÿ“Œ Game Variables

score = 0
disc_radius = 30
disc_speed = 6

๐ŸŽฏ score → Stores player score ๐ŸŽฏ disc_radius → Size of target ๐ŸŽฏ disc_speed → Speed of flying disc


๐Ÿ“Œ Pinch Detection (Shoot Logic)

distance = math.sqrt((cross_x - thumb_x)**2 +
                     (cross_y - thumb_y)**2)

if distance < 40:
    shoot = True

๐Ÿค When thumb and index finger come close, distance becomes small → system triggers SHOOT.


๐Ÿ“Œ Disc Movement

disc_x += disc_speed

if disc_x > width:
    disc_x, disc_y = new_disc(width, height)

The disc keeps moving horizontally. If it goes outside the screen → new disc appears randomly.


๐Ÿ“Œ Hit Detection

if distance_to_disc < disc_radius:
    score += 1

If crosshair touches disc → Score increases ๐ŸŽ‰


๐ŸŽฎ How the Game Works

  • ๐Ÿ‘‰ Move index finger to control crosshair
  • ๐Ÿ‘‰ Pinch thumb + index to shoot
  • ๐Ÿ‘‰ Hit the flying disc
  • ๐Ÿ‘‰ Score increases
  • ๐Ÿ‘‰ Press ESC to exit

๐Ÿš€ Why Use ESP32-CAM?

  • ๐Ÿ“ท Low cost AI camera module
  • ๐Ÿ“ถ Wireless streaming
  • ๐Ÿค– Perfect for robotics projects
  • ๐ŸŽ“ Ideal for school & college AI demos

๐Ÿ‘‰ Get your ESP32-CAM here: ๐Ÿ”ฅ Buy ESP32-CAM Now


๐Ÿ’ก Future Improvements

  • ๐Ÿ”ซ Add gun sound effects
  • ๐ŸŽฏ Add multiple targets
  • ⏱ Add timer mode
  • ๐Ÿ† Add leaderboard

๐Ÿ“ข Final Words

This project is perfect for students learning AI + Computer Vision + Python. If you enjoyed this tutorial, share it with your friends and try building it using ESP32-CAM for advanced learning!

Happy Coding ๐ŸŽ‰

Comments

Popular posts from this blog

Interfacing Load Cell with Raspberry Pi 3 (via HX711) ⚖️

Interfacing Load Cell with Raspberry Pi 3 (via HX711) ⚖️ Interfacing Load Cell with Raspberry Pi 3 (via HX711) ⚖️ A load cell is a transducer that converts force (weight) into an electrical signal. The HX711 is a precision 24-bit analog-to-digital converter (ADC) designed for weigh scales. Today we’ll connect a load cell to Raspberry Pi 3 using the HX711 module. ๐Ÿงช ๐Ÿ”ง Components Required Component Quantity Raspberry Pi 3 1 Load Cell 1 HX711 Module 1 Jumper Wires 6 Breadboard (optional) 1 ๐Ÿ”Œ Pin Connections HX711 Pin Raspberry Pi Pin Pin Number VCC 5V Pin 2 GND Ground Pin 6 DT GPIO 5 Pin 29 SCK GPIO 6 Pin 31 Figure: Load Cell connected to Raspberry Pi 3 via HX711 ๐Ÿ’ป Python Code from hx711 import HX711 import RPi.GPIO as GPIO import time hx = HX711(dout_pin=5, pd_sck_pin=6) hx.set_reading_format("MSB", "MSB") hx.set_reference_unit(1) hx.reset()...

Interfacing Sound Sensor with Raspberry Pi 3

๐Ÿ”น Overview The KY-037 is a high-sensitivity sound detection sensor that can detect noise levels in the environment. It provides both analog and digital outputs. In this tutorial, we’ll interface the digital output of KY-037 with Raspberry Pi 3 Model B+ (without using an ADC like MCP3008) and detect sound events.

Interfacing Water Flow Sensor with Raspberry Pi 3 ๐Ÿšฟ

Interfacing Water Flow Sensor with Raspberry Pi 3 ๐Ÿšฟ ๐ŸŽฏ Objective To measure the flow rate of water using a Water Flow Sensor (YF-S201) and Raspberry Pi 3. Useful in smart irrigation and water management systems. ๐Ÿงฐ Components Required Component Quantity Raspberry Pi 3 1 YF-S201 Water Flow Sensor 1 10K Pull-down Resistor 1 Jumper Wires As required Breadboard 1 ⚡ Circuit Connections Sensor Pin Connect To Red (VCC) 5V (Raspberry Pi) Black (GND) GND (Raspberry Pi) Yellow (Pulse Out) GPIO18 (Pin 12) with pull-down resistor ๐Ÿง  Python Code import RPi.GPIO as GPIO import time FLOW_SENSOR = 18 pulse_count = 0 def countPulse(channel): global pulse_count pulse_count += 1 GPIO.setmode(GPIO.BCM) GPIO.setup(FLOW_SENSOR, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.add_event_detect(FLOW_SENSOR, GPIO.FALLING, callback=countPulse) try: while True: pulse_count = 0 time.sleep(1) flow_rate = (pulse_count / 7.5) ...