MagiQuest wand input | Laser tag toy gun input |
---|---|
This is the code behind Imposter Attack, an infrared shooting gallery game I made for Halloween 2024 that uses battery-powered ESP32 targets and a Node.js-based scoreboard server.
Important
π Full writeup here: https://blog.langworth.com/imposter-attack π
Warning
I didn't plan on open-sourcing this originally, so it's quite a mess and has a bunch of stuff hardcoded. Feel free to send a pull request if you want to clean it up.
- Python 3.x
- pyenv (Python version manager)
- Node.js v20 or newer
- Two or more ESP32 devices
- MicroPython firmware
This is a tool to copy dependencies and files to a MicroPython board over serial. It wraps mpremote
in order to watch a directory for changes, automatically copy them to the board, and then monitor the serial port for output.
To install dependencies, run pip install -r devtool/requirements.txt
.
Then run devtool.py <directory>
, like devtool.py target
.
This is the scoreboard and game server. To run it:
- Make sure an ESP32 device running the
bridge
code is connected to your computer - Install pnpm
- Run
pnpm install
- Run
pnpm dev
- Go to
http://localhost:4000/
for the scoreboard andhttp://localhost:4000/admin.html
for the admin interface
To add music, download the files into scoreboard/public/music/game
or scoreboard/public/music/menu
and add them to the gameMusicFiles
or menuMusicFiles
array in scoreboard/public/scoreboard.js
.
const gameMusicFiles = [
"among-us-hide-and-seek.mp3",
...
]
const menuMusicFiles = [
"pigstep.mp3",
...
]
You'll need to add your own sound effects referenced in scoreboard.js
since I don't have the license to distribute the ones I used. You can find sound effects on Freesound or OpenGameArt.org, or you can make some with sfxr if you like the retro sound.
Change scoreboard.js
like so:
const startSound = new Howl({ src: ["/sfx/round-start.mp3"] });
const endSound = new Howl({ src: ["/sfx/victory-crew.mp3"] });
const bossAppearSound = new Howl({ src: ["/sfx/impostor-roar.mp3"] });
const bossDeathSound = new Howl({ src: ["/sfx/boss-death.mp3"] });
This is the code that runs on the ESP32 devices, which communicate with the bridge, which communicates with the scoreboard. Get started downloading the firmware and modifying 01_flash.sh
with the path to the firmware. Then run 01_flash.sh
to flash the MicroPython firmware to the ESP32, then run 02_install.sh
to install the code and dependencies.
When iterating quickly, it's easiest to keep the ESP32 plugged in use devtool.py
to watch the target
directory for changes and automatically copy files to the ESP32.
In the field when devices aren't connected, I use uOTA to update the firmware over the wifi network. The devices don't connect to the wifi network until requested to update. This requires running a simple HTTP server (I run npx http-server -p 4444
in the webroot
directory) and then running 03_update.sh
to build a tarball and trigger the update. The devices will connect to the wifi network and update themselves.
Make sure to update the target/main.py
file with the correct WiFi credentials:
WIFI_SSID = "mywifi"
WIFI_PASSWORD = "sekrit"
You'll also want to update target/uota.cfg
with your IP address or where you're serving the webroot
directory:
{
"url": "http://192.168.0.123:4444"
// ...
}
This is a simple bridge that receives messages from the ESP-NOW network and sends them to the serial port. It also receives messages from the serial port and sends them to the ESP-NOW network. This is how the targets communicate with scoreboard. All devices speak JSON.
You can use the 01_flash.sh
command to flash the MicroPython firmware to the ESP32. Then you can use the standard mpremote
command to copy main.py
to the board, or use my own devtool.py
command if you want to make frequent changes.
I used yt-dlp to download the music from YouTube and sox to convert the Opus files to MP3 (but you could also use ffmpeg). Put these in scoreboard/public/music/game
or scoreboard/public/music/menu
and add them to the gameMusicFiles
or menuMusicFiles
array in scoreboard/public/scoreboard.js
.
- Among Us Hide and Seek
- Cyberpunk 2077 - Rebel Path
- DOOM - At Doom's Gate
- Hotline Miami - Hydrogen
- Jetpack Joyride - Main Theme
- Jetpack Joyride - Bad as Hog
- Jetpack Joyride - Gravity
- Crypt of the NecroDancer - Cold Sweat
- Crypt of the NecroDancer - Deep Sea Bass
- Crypt of the NecroDancer - Disco Descent
- Crypt of the NecroDancer - Heart of the Crypt
- Crypt of the NecroDancer - Hot Mess
- Crypt of the NecroDancer - Igneous Rock
- Crypt of the NecroDancer - Knell
- Crypt of the NecroDancer - Konga Conga Kappa
- Crypt of the NecroDancer - Last Dance
- Crypt of the NecroDancer - Mausoleum Mash
- Crypt of the NecroDancer - Metalmancy
- Crypt of the NecroDancer - Momentum Mori
- Crypt of the NecroDancer - Stone Cold
- Crypt of the NecroDancer - Wight to Remain
- Streets of Rage 2 - Alien Power
- Streets of Rage 2 - Dreamer
- Streets of Rage 2 - Go Straight
- Streets of Rage 2 - Mad Max
- Streets of Rage 2 - Under Logic
- Breath of the Wild - Field (Day)
- Breath of the Wild - Hinox Battle
- Breath of the Wild - Parasail Theme
- Breath of the Wild - Shrine Theme
- Breath of the Wild - Stone Talus Battle
- Chrono Trigger - Main Theme
- DOOM - Dark Halls
- DOOM - Suspense
- GoldenEye 007 - Pause Theme
- Halo - Main Theme
- Hotline Miami - Crystals
- Hotline Miami - Horse Steppin'
- Hotline Miami - Miami
- Hotline Miami - Miami 2
- Jetpack Joyride - Colossatron Theme
- Jetpack Joyride - Home Base
- Jetpack Joyride - The Stash
- Minecraft - Subwoofer Lullaby
- Crypt of the NecroDancer - Fungal Funk
- Crypt of the NecroDancer - Tombtorial
- Minecraft - Pigstep
- Streets of Rage 2 - Slow Moon
- Tears of the Kingdom - Korok Forest (Day)
- Tears of the Kingdom - Title Theme
- Howler.js (included as minified)
starfield.gif
is from Giphyastronaut.png
and all likenessness to Among Us characters are from Innersloth- VCR OSD Mono font is from DaFont
- micropython_ir
- uOTA
- Halloween Icon Pack is from Josy Dom Alexis on Iconfinder
Unless otherwise specified, all code is MIT licensed.