Photoresistor Based Gun (with the Dog!)

by dmytrolev in Circuits > Arduino

881 Views, 4 Favorites, 0 Comments

Photoresistor Based Gun (with the Dog!)

hero.jpg
20210915_215245.jpg

Many years ago we played one interesting game, which differed from others. It was a very primitive hunting simulator game. Unlike other games, it was played by pointing a toy gun into the TV and "shooting" ducks.
Since then I was wondering how it actually worked? Someone noticed that the screen was blinking when the player pulled the trigger. The screen turn black for a moment and the duck was replaced with the white rectangle. We decided that the game decided if the player have hit the target by measuring light intensity.

I always wanted to implement this game, and now finally I decided to try out few designs.

Alternatives considered

Initially, I wanted to build it using ESP32-CAM. I was amazed by the quality and the latency of live stream it is able to produce, and I thought that it would be an excellent machine learning exercise. But before that I wanted to make something simpler. I remembered that I have a photo resistor from the Arduino educational pack, which I had never had a chance to use.

The game could be implemented with typescript, and the gun could somehow connect to the HTML page, sending signals to the game that the trigger is pulled, and then sending sensor value. For this kind of design I could use some ESP32 with WIFI. In this case game should just connect to the server which would be running on ESP and listen to the commands and measurements from that server.

This seemed to be cumbersome, and WLAN latency could be a concern. Usually micro-controllers can be connected to the computer using simple COM port. Unfortunately, static HTML page would not get access to a random attached hardware. On the other hand, micro-controller could pretend to be a keyboard, and send commands and measurements as key strokes.

This seemed to be a wonderful plan - the game could be run locally, or hosted somewhere in the internet. The gun should be assembled at home, connected with the USB cable to the computer, and the game would be playable. Gameplay itself could be tested using simple keyboard. No python would be required, no installation of any libraries. Game modes could be shared by simply uploading static HTML+CSS+JS.

Unfortunately, simple Arduino (in my case it is UNO) is unable to pretend to be a keyboard. It should be some special type of Arduino, for example Arduino Micro (not Arduino Mini, few of which I also have). That would mean that communication between the computer and the gun must be implemented using Serial port. The only way known to me is using Python. That would mean that the game must also be implemented in Python. I've never programmed games in Python, and I have no idea which framework would be good for the project.

Final decision

The gun would be built using Arduino Uno, push button for the toggle, and photo cell as a sensor. The game would be implemented in Python using PyGame framework. Communication between the gun and the game would be implemented using pyserial library.

Supplies

  • Arduino UNO
  • Photo cell
  • Push button
  • 2x 10kOhm resistors
  • Wires
  • Cardboard
  • Drinking straw
  • Black duct tape
  • Small piece of Plasticine
  • Glue

The Gun Barrel

20210916_202901.jpg
20210916_171323.jpg
20210916_172510.jpg

Wrap a drinking straw with the black duct tape to make it extra dark inside. Drinking straw should be long and narrow, but enough to place a photocell inside. Attach wires to the photocell and mount it with a bit of Plasticine inside the drinking straw. Attach it to the Arduino at the analog input A2. The longer the wire - the better, it will be easier to test the setup.

Attach a push button to the Arduino at a digital input 2. This will be a toggle of the gun.

Upload this code to your Arduino:

bool pressed = false;

const int ROUND_DELAY = 25;
const int BAM_DELAY = 75;

void setup() {
  // put your setup code here, to run once:
  pinMode(2, INPUT);
  pinMode(A2, INPUT);
  Serial.begin(115200);
}

void loop() {
  // put your main code here, to run repeatedly:
  if(digitalRead(2) == HIGH) {
    pressed = true;
  }
  if((digitalRead(2) == LOW) && pressed) {
    Serial.println("bam!");
    delay(BAM_DELAY);
    int power = analogRead(A2);
    Serial.println(power);
    pressed = false;
  }
  delay(ROUND_DELAY);
}

There are two constants in this code:

ROUND_DELAY is probably not that important, and could be removed completely. But I prefer to have it in the code. It can introduce a delay between clicking the push button and the game reaction. Also, if the values are big enough - it can miss the click completely.

BAM_DELAY is an important one. It gives the game time to switch between user game view with trees, bushes and ducks and the shoot detection mode, with the entirely black background and a single white rectangle.

Debugging

Turn on the Serial Monitor (Ctrl+Shift+M), set the baud rate to 115200, and click the push button. In the monitor you should see something like:

bam!
2
Now, point your drinking straw gun to a white section on your screen, and click the button again. Now you should see something like:
bam!
56

In the last test, point the gun to a black section on your screen, and click the button. It should print:

bam!
0

These tests make sure that push button works fine and your photocell can distinguish between white and black sections on your screen. In case if the white does not have high enough value - you could try to increase your monitor brightness.

The Game

dog-1.png
dog-0.png
duck.png
dead_duck.png
back.png
front.png

Download attached assets, or make your own. You will need:

  • a background image, which will be drawn behind the foreground and the ducks
  • a foreground image will be drawn above the ducks. It should have alpha channel, so that we could see ducks behind the trees
  • alive duck which a player will have to shoot
  • dead duck if the player was successful
  • dog with no ducks for miss
  • dog with a duck for success

Install pyserial and pygame

PS > pip install pyserial
PS > pip install pygame

Save this code to the game.py file:

from serial import Serial
from random import choice
import pygame
import time

DELIM = "\n".encode()
TRAGECTORIES = [(10, -10), (5, -15), (7, -12)]
START_POS = [((100, 500), 1), ((1100, 500), -1)]

ser = Serial("COM3", 115200, timeout=0.01)

waiting = True
pygame.display.set_caption("Hunt")
window_surface = pygame.display.set_mode((1280, 760))

back = pygame.image.load("back.png")
front = pygame.image.load("front.png")
duck = (pygame.image.load("duck.png"), pygame.image.load("dead_duck.png"))
back_duck = (pygame.transform.flip(duck[0], True, False),
             pygame.transform.flip(duck[1], True, False))
dogs = (pygame.image.load("dog-0.png"), pygame.image.load("dog-1.png"))

black_background = pygame.Surface((1280, 760))
black_background.fill(pygame.Color('#000000'))
bird = pygame.Surface((200, 200))
bird.fill(pygame.Color('#ffffff'))

dog_pos = None
dog_id = 0
dog_direction = (0, -5)
bird_pos = None
tragectory = None
direction = None
is_alive = None
score = 0

while waiting:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            waiting = False

    if dog_pos is None:
        if bird_pos is None:
            tragectory = choice(TRAGECTORIES)
            start = choice(START_POS)
            bird_pos = start[0]
            direction = start[1]
            tragectory = (tragectory[0] * start[1], tragectory[1])
            is_alive = True
        else:
            if is_alive:
                bird_pos = (bird_pos[0] + tragectory[0],
                            bird_pos[1] + tragectory[1])
            else:
                bird_pos = (bird_pos[0] + tragectory[0],
                            bird_pos[1] - tragectory[1])

        resp = ser.read_until(DELIM).decode()[:-1]
    
        if resp != "":
            window_surface.blit(black_background, (0, 0))
            window_surface.blit(bird, bird_pos)
        else:
            window_surface.blit(back, (0, 0))
            window_surface.blit(
                (duck if direction == 1 else back_duck)
                [0 if is_alive else 1], bird_pos)
            window_surface.blit(front, (0, 0))
    
        pygame.display.update()
    
        if resp != "":
            ser.timeout = 0.15
            power_str = ser.read_until(DELIM).decode()[:-1]
            ser.timeout = 0.01
            power = int(power_str)
            if is_alive and power > 20:
                score = score + 1
                is_alive = False
    
        if bird_pos[1] < -200 or bird_pos[1] > 500:
            dog_direction = (0, -5)
            dog_pos = (440, 420)
            if bird_pos[1] < -200:
                dog_id = 0
            else:
                dog_id = 1
            bird_pos = None
    else:
        window_surface.blit(back, (0, 0))
        window_surface.blit(dogs[dog_id], dog_pos)
        window_surface.blit(front, (0, 0))
        pygame.display.update()

        dog_pos = (dog_pos[0] + dog_direction[0],
                   dog_pos[1] + dog_direction[1])
        if dog_pos[1] < 220:
            dog_direction = (0, 5)
        elif dog_pos[1] > 420:
            dog_pos = None

You will probably want to replace "COM3" string to the name of the Serial interface used by Arduino on your platform. You can start the game using:

python game.py

This is a simple game, which will generate one duck at a time, and react to the input from the Serial port. When it gets some signal from the gun - it will display a black background with a white rectangle in the place where the duck was located. It will give 150ms to the connected device to send it's measurements back to the game, and will decide if the duck was shut.

From the previous debug session make sure that 20 constant is good for your photocell, and also you can play a bit with Serial timeouts to allow enough time for the photocell to capture the white rectangle.

At this step you can check that the whole setup works well, and the game is playable.

The Gun Body

20210916_205740.jpg
20210916_205814.jpg
gun body.jpg

Print the attached outline, cut outlines roughly and use some glue to attach them to the cardboard, then cut a cardboard pieces of the gun, which you can glue together to get nice, fake gun. Wires can be hidden between the layers of cardboard, and the gun barrel can be attached with pieces of transparent duct tape.

In the internet you could find a lot of examples of "cardboard guns". Some interesting alternatives could be:

  • a sniper rifle. Another drinking straw attached on top of the barrel, could work as a sniper scope. It can also be used to "debug" any other barrel architecture.
  • futuristic hand gun. You can design some watch-style gun, which keeps your hands free.

Final Result

Duck hunt with the dog demo

The game is quite playable. In the original game, there was a mode to shoot two birds at the same time. This could be achieved in a few ways - we could run few rounds displaying white rectangles, one for each of the birds. Or there could be two photocells covered with colorful filters, or a more complex light sensor. In this case we could display a red rectangle for the one bird, and a blue rectangle for the another one. Color, which was sensed, would determine a shot bird.

Single gun could be used for different games with similar principle. Of course forms of the guns themselves could increase feelings from the game. Guns could be also equipped haptic response to make shooting experience more believable.

The gun could also get a tilt sensor, which could be used to "reload" the gun. For example, we could limit the amount of shots to 8. To reload the gun - player would have to point it down (tilt sensor would capture this event) and then shoot again.

In my experience constructing a gun is the most complicated part. But also it is most enjoyable. It's a lot of fun when you can see that the gun is actually "shooting", and you can experience something new. Would you spend time constructing a cardboard gun?