Friday, April 17, 2026

The Lost Art of Tree Fishing

Now that the Northeast seems to have finally shaken off the icy grip of winter, I've taken the first reluctant steps outside of my cave to engage in some outdoor activities. This week I found myself "tree fishing" - which is my self-coined euphemism for hanging a random wire style antenna in one of my backyard trees to use for ham radio communications. This process involved me, standing outside and hurling a spool of bright yellow fishing line into the tree, over and over again, until I managed to toss it over a branch of sufficient height. I then attached some thin nylon rope to the line and pulled it over the branch, finally completing the process by securing 29 feet of suitable wire to the rope and hoisting that up into the air. The antenna was connected to a 9:1 UnUn transformer, which I housed in a water proof box mounted on some stockade fence a short distance from the tree. The UnUn also has connections on the box for a ground wire acting as a counterpoise - which is running across the yard perpendicular from the antenna and a RG58 coax which is running up to the second floor office where it is connected to a 20 watt Xiegu G90 HF transceiver. This configuration works fairly well with bands 10 - 40 meters, and I had great success hearing stations as far away as Ireland and Scotland on 20 meters. I'll probably keep this up as a temporary HF solution through the summer while I figure out a more permanent installation for the fall.

My Neighbors must have been scratching their heads to see me flinging a spool of fishing line up into the tree over and over again. We finally managed to get the antenna hung from a reasonable height. 
 
The Xiegu G90. A surprisingly capable low-watt HF transceiver. Now if only 10 meters would open up!





 

 

 

Sunday, January 25, 2026

-=CYBERDECKS=- (and why build one?)

A cyberdeck is a personal, portable computer inspired by classic cyberpunk fiction—think Neuromancer, Ghost in the Shell, or Blade Runner. Unlike a laptop or phone, a cyberdeck is usually hand-built, customized, and unapologetically weird. It might live in a Pelican case, a 3D-printed shell, or a hacked-together metal box, often featuring a small screen, mechanical keyboard, exposed wiring, and blinking LEDs. Under the hood, it’s commonly powered by a Raspberry Pi or similar single-board computer, running Linux and whatever tools the builder finds interesting.

People build cyberdecks not because they’re practical, but because they’re personal. A cyberdeck is a rejection of sealed, disposable technology in favor of something tactile, understandable, and modular. Building one is a way to learn about Linux, electronics, networking, radio, programming, and hardware—all while making something that feels like it came out of science fiction. 


 

I built my cyberdeck with a spare Raspberry Pi 3B+ board I had lying around, running Raspberry Pi Lite OS. I extended 2 of the USB ports and the ethernet port out to the main panel, which is made of ABS plastic, cut to fit into the opening on a Pelican 1300 case. My plan is to use this machine for software development, networking experiments, and maybe some software defined radio experimentation (more on that to come).


 

To give the Deck more cyberpunk street cred, I slapped a sweet "Legalize Recreational Plutonium" on the side. My plan is to expand the functionality of this unit over time. Some enhancements I'm considering include some blinking LEDs (maybe to give a visual indication of wi-fi strength or some other signal), adding an Arduino to the mix, and creating a custom speaker/antenna mount to handle software defined radio stuff.


Monday, January 19, 2026

Building a tiny Lisp interpreter with C++

I made a very simple Lisp interpreter using C++. I decided to focus on Lisp after a failed experience of trying to write a tiny BASIC interpreter for a cyberdeck build I'm working on putting together. I ended up shifting to Lisp for a variety of reasons. It is an interpreted language, meaning it relies on a program to act as a sort of middle man to execute the code versus compiling it to machine language. It also is one of the oldest computer languages with an extremely simple syntax. I decided to use C++ rather than Python for this task. C++ runs extremely fast compared to Python (which is also an interpreted language), and interpreters really require that speed provided by the program running as native machine code. This project could have been done using Python, but my guess is that running the program would have suffered from the increased headroom necessary of running in interpreter within an interpreter.

The interpreter is very basic by any standard. It currently can process the following mathematical operations: addition, subtraction, multiplication, division, absolute value, square root, and calculating powers. It can define and set variables, as well as print both numbers and strings to the console. It can handle floating point numbers. Essentially, it's an extremely simple programmable calculator!

So what did I learn from this? The first thing is, under the hood, there is nothing basic about BASIC. I was shocked at how challenging it is to write a simple interpreter for such as simple language. The second is the robustness and honestly the beauty of Lisp. The language, even in it's extremely simplified form is a joy to work with. I've always thought that there is magic to be found when working with limitations, and while Lisp seems to be extremely limited compared to more modern interpreted languages like Python, the structure of the language lends itself to some really creative solutions. 

Here is an example of code that runs in my interpreter to solve quadratic equations:

> (define a 1)
=> 1
> (define b -5)
=> -5
> (define c 6)
=> 6
> (define discriminant (- (* b b) (* 4 (* a c))))
=> 1
> (define x1 (/ (+ (- 0 b) (sqrt discriminant)) (* 2 a)))
=> 3
> (define x2 (/ (- (- 0 b) (sqrt discriminant)) (* 2 a)))
=> 2
> (print "Solutions: x1 =" x1 ", x2 =" x2)
Solutions: x1 = 3 , x2 = 2

 Below is a video of me running a demo of the interpreter to show the basic syntax for some of the operations and showing how to set up a simple program that converts Celsius to Fahrenheit.

 



The robotic voice in the video was created by a separate program I built with Python that converts text to speech and then saves the output as a .WAV file. Still working out some of the kinks with that, but it's fine for now.

 

Sunday, December 7, 2025

Housing a Linux computer inside a broken Atari 2600 cartridge

 I started thinking pretty soon after completing the custom cable channel project of housing a Raspberry Pi UNO into a repurposed cable box from the 1980s about other novel and interesting ideas to house single board computers. I recently was tidying up my work space and my eyes fell on a stack of Atari 2600 cartridges that failed to boot when testing them. It was during the process of taking the casing for a fairly grimy copy of Surround apart it dawned on me that these cases are the perfect size to house a Raspberry Pi Zero W, which I happened to have a couple just sitting on the desk mocking me for not using them.


The first stage was to completely disassemble the old cartridge, which honestly is a bit more challenging than it probably should be. I got pretty lucky removing the original labels, so I was able to reuse them after everything is assemble. There is only 1 phillips head screw to remove, but there are 6 plastic clips which require you to deal with before the casing will come apart. I used a small sized slotted screwdriver to carefully push the clips in and manage to get the thing apart, but I could definitely see people breaking the tabs and/or scarring up the plastic of the cartridge while trying to get these things disengaged. After that it's pretty straightforward. The dust cover assembly slides out, and nothing is holding the game ROM in place. I opted to save my ROM, since I'm still going to see if I can rehab the pins to check if it might actually boot after a good scrubbing.


The next step was to hot glue the offsets in place inside the cartridge to mount the Raspberry Pi. I decided to run a very bare bones distro of the Raspberry Pi OS Lite (32-bit) which comes with no desktop. My goal here is a totally basic Linux system hidden inside an old Atari cartridge. Maybe I'll install Stella or some other 2600 emulator on it at some point - I imagine it would be pretty cool to play 2600 games on a computer housed inside an actually 2600 game.


After testing to make sure everything worked, I reassemble the case and glued the orginal labels back into place. Since the Pi Zero W uses 2 micro USB connections (one for USB stuff and one to power the board) I decided to put a red dot of paint underneath the power connection, since you can't easily read the board to prevent me from inadvertently mixing up the connections.


 Fun project that was totally a spontaneous use of time that I should have allocated to doing something else. 

Tuesday, November 18, 2025

Zarphield Says...

While grappling with the daily existential crisis that modern life has devolved into, I wrote an app in Python that sits on my desktop and spits out a fusion of quotes by 80's cat icon Garfield and swinging space pirate Zaphod Beeblebrox. This is Zarphield. Feed him lasagna, Earth man.



 

Monday, November 17, 2025

Psuedo Spectrum Analyzer

Earlier this year I acquired two Elegoo MEGA2560 kits through Facebook market place. These kits are excellent entry points into the Arduino maker space. They come with boards and enough components and modules to keep the average nerd busy for awhile. Lately I've been looking at some of the modules that I didn't have strong thoughts on what to utilize them for and have started brainstorming ideas build some unique things. One of the modules in particular, the sound sensor module, got my attention. This module has a built in microphone and can be used for a variety of applications, such as a sound activated trigger or to capture audio for processing. After reading up on the module (this is one of the odd ball modules included in the kit that doesn't have a readily available board number associated with it), I decided to pair it with an old favorite of mine, the MAX7219 8x8 LED display. I used the MAX7219 to great effect in the cable box I built a couple of weeks ago and I love this one, in particular because it's super easy to get it working with an Arduino board and looks super cool in action. My goal was to create a sort of "spectrum analyzer" like you might see in older hi-fi stereo equipment racks. When I was a kid I had a spectrum analyzer program that I ran on my beloved Tandy Color Computer 2 that accepted input via a set of stereo RCA cables and displayed a dancing set of colorful bars that pulsed to the audio it was fed. This version would be much simpler. I would use the microphone from the sound sensor to listen for audio from the room it was setup in, and process the audio via a UNO R3 board, segmenting the sound into degrees of volume, and then display these lines of differing audio strength signals on the MAX7219.


 

Wiring up the components (the sound sensor and the MAX7219 display) was very straightforward. Both components are labeled clearly which pins do what (i.e. VCC, ground, etc.) unlike a couple of modules I've encountered in these kits (looking squarely at you DHT11 Temperature and Humidity Module). One everything was wired up it really was just a matter of getting the sketch pulled together to get the setup to capture the audio, "rank" the volume levels, and then display the results in an understandable way. There are essentially 6 steps in this process:

1. Read sound from microphone.

The sound sensor outputs and analog voltage that changes with the sound. It can output digital, but I ran into some issues getting that to reliably be read, so I opted for the easy route with analog output. The sound ranges in value from 0-1023.

 int raw = analogRead(micPin);

2. Compare the sound to a dynamic baseline.

A baseline is used to represent the normal quite noise in the room. This gives us the difference between the current sound and the background. The sketch allows the baseline to adapt over time, which keeps the analyzer stable, even if the room becomes noiser over time.

 int diff = abs(raw - baseline);

baseline = (baseline * 98 + raw * 2) / 100; 

3. Convert sound level into bar height.

The difference is scale into a value from 0-8, matching the height of the MAX7219's 8x8 matrix.

int level = diff / 8; 

4. Scroll the display to the left.

The entire display is shifted one column to the left. This is meant to mimic the way a real spectrum analyzer scrolls over time.

 scrollLeft();

5. Draw the new bar on the right side.

The new sound level becomes a vertical bar in the far right column. Each LED row is turned on or off depending on the bar height.

 drawColumn(level);

6. Update and refresh the MAX7219 display.

The display is updated all at once for a smooth animation.

 matrix.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);

Results 

The result is a series of bars of different heights, continually scrolling to the left, with the bar height continually reacting to the volume of the sound it picks up. I can't really call this an actual spectrum analyzer, since there is only one sensor monitoring the room and not a series devoted to the various frequencies across the audio spectrum, but it still looks pretty cool. 


 

Tuesday, November 11, 2025

Low-Powered FM Radio Transmitter with an Arduino and Si4713

Arduino boards are fantastic and versatile for constructing a wide range of electronics projects. One of the greatest strengths are the abundance of electronic modules that are available to add project specific functionality. I picked up a Si4713 board awhile back and decided to spend some time setting it up with an Arduino to see if I could get a functional low-powered FM transmitter running. The Si4713 boards are easy to come by and relatively cheap (I think I paid well under $20 including shipping for mine from a seller on ebay), and have minimal work required to get them hooked up. They come with headers that will need to be soldered onto the board and a very thin wire that is meant to serve as an antenna (which will also need to be soldered to the board). 

The wiring between the Si4713 and the Arduino (I used an Elegoo Mega 2560 R3 I had lying around for this project) is as follows. Adjust the wiring and code as necessary depending on the model of the Arduino you use and your preferences for the reset pin.



Wiring:

VIN-5V
GND-GND
SDA-SDA (Pin 20)
SCL-SCL (Pin 21)
RST-Pin 2
GPIO1Optional (RDS interrupt)leave unconnected unless needed









You will need to install the following Adafruit libraries for the board to work: 

Adafruit_Si4713 Library

Adafruit_BusIO

Adafruit_GFX (if you want to later display station info)

I tried a couple variations on getting a functional sketch. I finally figured out that a persistent problem I was having involved the reset pin not properly resetting after the Mega has been disconnected from the power source and then rebooted. Modifying the sketch to allow for a delay to allow the pin to reset properly seemed to correct this. The updated sketch is below:

#include <Wire.h>
#include <Adafruit_Si4713.h>

#define RESET_PIN 2 // match whatever wiring you selected
Adafruit_Si4713 radio(RESET_PIN);

void setup() {
  Serial.begin(115200);
  delay(500);             // time for power to stabilise
  pinMode(RESET_PIN, OUTPUT);

  // Toggle reset: LOW -> HIGH with pauses
  digitalWrite(RESET_PIN, LOW);
  delay(50);
  digitalWrite(RESET_PIN, HIGH);
  delay(200);

  Wire.begin();
  Serial.println("Attempting to find Si4713...");

  if (!radio.begin()) {
    Serial.println("Couldn't find Si4713");
    while (1) {
      delay(500);
      Serial.println("Retrying...");
      if (radio.begin()) {
        Serial.println("Found Si4713 on retry!");
        break;
      }
    }
  }

  Serial.println("Si4713 found!");
  radio.tuneFM(10110);
  radio.setTXpower(88);
}
void loop(){}

After I verified that the transmitter was operational, I decided I needed something cool to transmit. One of my first exposures to short wave radio was listening to "numbers stations", which are famously mysterious stations that broadcast cryptic messages. I fired up Garageband and recorded some esoteric sounding number station inspired audio ala' "Stranger Things" that I could endlessly loop on my station. I grabbed a receiver capable of tuning in FM and dialed up my frequency. I don't have to tell you how cool it was to hear my message looping over the radio! I did some proximity testing, moving the radio around the room and it seems like the effective transmission distance using the basic thin wire antenna provided with the Si4713 is about 15 feet. I believe I could improve the distance by building a better antenna, such as an optimized quarter-wave antenna - which might possibly extend the reach a bit more (maybe 25-30 feet).

Some future enhancements to this little radio station I can envision include: building a better antenna, fitting everything into a suitable enclosure, and adding an old ipad shuffle for audio to make this a truly mobile radio station.
 

Sunday, November 9, 2025

Constructing a homebrew "Cable Channel" Part 2

 It's relatively easy to use a Raspberry Pi to approximate the experience of having your own custom cable television channel similar to what folks of a certain age may remember from their youth. In a previous post I went over some of the basic steps to get a Raspberry Pi 4 set up as a "retro box" that automatically plays a curated collection of movies and shows from the 1970s, including period correct public service announcements and commercials. After a couple of weeks of testing - which was essentially just letting the box run to make sure that the flow of the programing actually felt like watching period correct television - I decided that I needed to ramp things up. It wasn't good enough to just have a Raspberry Pi that played old movies and commercials. Any modern computer can do that. This needed to actually have the entire aesthetic of a classic 1970s cable channel. It needed to dress the part.

That's where eBay comes back. Some quick searching and I was able to locate an untested cable box circa early 80's for parts/repair that had a buy now price of only $3.81 (plus $12 for shipping). This box included a front channel display and was roomy enough to house both the Raspberry PI and an Arduino UNO (more on that below). I decided to go with the 80's style box, since the larger encloser would make it much easier to work with, and I had some ideas about utilizing the front channel display screen.

 

When the box arrive it was immediately obvious that the particular Jerrold 550 Starcom converter had lived a rough life. The was some serious damage to one corner of the plastic face plate due to what I can only attribute as "falling damage" from being dropped on a hard surface, and the unit was covered in old masking tape and 40+ years of grime. I also noticed that the unit's enclosure was secured with rivets, probably since the majority of these units were distributed by local cable companies and they didn't want to make it easy for industrious hackers to open them up to try to figure out how to get free Home Box Office. The first step was to drill out the rivets so I could take the unit apart.


 
After I parted the case out I spent some time scrubbing the case down with dish detergent and using a liberal amount of Goo Be Gone to get all that sticky tape residue cleaned up. Once everything was clean and dry, I could spend some time attending to the smashed corner of the face plate. I had a couple of options for the repair, but I decided that the easiest (and structurally most secure) fix would be to recreate the corner using Green Stuff two-part epoxy. For folks not familiar with Green Stuff, it essentially is a two-part epoxy that you mix by hand and then mold like a really rigid Play Dough into to place. It is typically used a lot when kit bashing models for games like Warhammer 40K, when you need to sculpt a custom piece, and maybe adhere a particularly tricky portion of a model together. It works well, can be easily sculpted, and takes simple water-based acryllic paint just fine. I eyeballed the amount that would be needed to recreate the missing/damaged corner, mixed it up, and then went to work sculpting the corner as best as I could. Green Stuff sets pretty quickly - typically with 5-10 minutes - so I didn't have the luxury of time here. My goal was good, not great. After the epoxy set I mixed up a fairly good approximation of the color of the faceplate using acrylic paint to blend the repair in as best as I could. Overall, I'm pretty happy with it. From across the room it's not noticeable and the Green Stuff actually made the faceplate much more sturdy that it was previous to the repair.


 
Once the plastic was repaired I started brainstorming how to use the front channel display. I immediately though about using an Arduino UNO to power a small LED screen, since I'd used that pairing to great effect on a couple of smaller projects. I suppose I could have powered the LED's from the Raspberry Pi, but to be honest it was quicker and easier for me to wire up the display to an extra UNO R3 I had and I was able to repurpose a sketch that I had written for a handheld snake game. The Pi can power the UNO via USB, and if I ever want to change the display it is super easy to do (and doesn't affect the functionality of the Pi). It also gives me to option of adding a module for a remote control if want later down the line. I played around with different text for the display, finally settling on having an asterisk gliding across the display. It looked cool and I'm a sucker for moving LED's (let's just blame that on "Knight Rider"). After some tweaks for placement and a couple of generous blobs of hot glue to hold the screen in place, I had a functioning display that I could easily modify to anything that used a basic character set.
 

 
The final stretch was just to drop the Raspberry Pi into the box and get everything secured. I cut and glued some rubber welcome mat materials to the bottom of the case to assist with giving everything a nice, non-conductive floor to mount the pieces on, and mapped out the most efficient layout to screw everything down.

 



And there it is...a working retro "cable channel" that lives inside a period correct cable box with a customizable LED front panel. Eventually I will add an Arduino remote control module that I will use to change up the front display to a couple of different visual options. I still need to come up with a reasonable plan for cable management that will allow me to plug in a mouse and keyboard into the back. I can remote into the box from another computer to add videos if I want. Overall, I'm pretty stoked on this little project. I'd like to make another one at some point using a Raspberry Pi 3 with composite video so I can easily run the signal to one of the CRT televisions I have. This is the first project I'm done that has utilized both the Raspberry Pi and the Arduino boards and I love the flexibility of having both in a build. Now it's time to watch some TV!
 

Sunday, October 19, 2025

Using HTML to generate random horrors from beyond...

A webpage that generates random "Elder God" names in the style of H.P. Lovecraft? Sure! This started as a Python project that would generate a list of 10 random names in the style of the infamous Elder Gods from the Lovecraft pantheon. I had so much fun with that I decided to port it over to the land of HTML.

 The page is available on my GitHub, in case anybody wants to summon an unthinkable horror with too many consonants.

https://robot-sword78634.github.io/elder-names/ 

The HTML uses the same syllable list and logic as the original Python code, but instead utilizes Javascript. It also adds some nifty apostrophes for that added Lovecraft flair.

 

Thursday, October 2, 2025

Let's create a cable TV channel!

I’ve become infatuated with Raspberry Pi single-board computers recently, and thanks to a low-grade eBay addiction, I now have several of them just sitting around, mocking me to make them useful. Recently, I read about someone who turned one into a sort of personal cable channel, and I got fired up on the idea of creating my own “cable channel” of highly curated videos—which I’m sure nobody else would want to watch.

It probably would have been easier to just follow the tutorial I found online for this type of project, but as usual, I felt it necessary to reinvent the wheel for the sake of “learning.” For me, the basic parts of getting a home cable channel working are:

  1. Figure out how to randomize/play/loop videos from a folder

  2. Determine how to “broadcast” the content (HDMI, Wi-Fi, etc.)

  3. Decide what content to use

I kept the first part simple. Since I’d be running this project on a spare Raspberry Pi 4 with Pi OS, I decided to use VLC as the media player. VLC natively does everything I needed (randomize, play, loop from a folder), and it can be controlled via terminal/bash scripts—perfect for making everything restart automatically if the system reboots.

I skipped the broadcasting question for now and focused on getting the system running. The fun part, after all, was deciding on a theme for my channel and grabbing videos! I’ve always been a movie buff and grew up watching late-night “midnight movie” flicks on TV, so it was a no-brainer to load up the Pi with movies and shows that fit that vibe.

I could have taken the easy route and gone straight to the Internet Archive, but instead, I pulled material from YouTube. A little-known fact: YouTube not only streams video content but also allows you to download files. Since I’d need a large number of videos, I wrote a quick Python script to batch-download user-curated playlists. Python makes this easy with the pytube library, which was designed for this purpose. The script asks for a YouTube URL and a save location, then pulls down the highest-quality stream available.

At first, I ran into certificate errors on macOS. Instead of troubleshooting libraries, I just ran the script on an Ubuntu machine and it worked without issues.

The flow is straightforward:

  1. Prompt for the YouTube URL and the save folder

  2. Use pytube to get the highest-resolution stream

  3. Download it to the folder

It’s a short and simple tool, but it does the job—a quick way to grab videos for offline viewing. After downloading a bunch of nostalgic movies and TV shows, I started scraping vintage 1970s commercials too. Since VLC randomizes playback, there’s a chance that after a movie or show, one or two commercials will play before the next feature. After raiding YouTube’s vaults, I had a solid library for my channel. Then came the fun part: testing and tweaking.

Long story short—it worked! It feels like watching a real TV channel from the 1970s. Videos flowed seamlessly from one to the next, with a nice balance of movies, shows, and commercials. (I might add more commercials later—it’s surprisingly fun to see a 1972 McDonald’s ad pop up.)

My startup bash script launches VLC at boot, runs everything in fullscreen, randomizes playback, and loops continuously without showing the user interface:

#!/bin/bash
vlc --fullscreen --loop --random /home/pi/Videos/

Right now, I have the Pi hooked up to a spare TV via HDMI, but I’m tempted to pick up an HDMI-to-RF modulator so I can run the channel on a CRT I keep around for playing Nintendo. Ideally, I’d love to broadcast to multiple TVs around the house—but I don’t know if I want to run 100 feet of coax cable just to watch Leonard Nimoy’s In Search Of in every room.

Or do I?



 


 

 

Wednesday, October 1, 2025

DOOM!

A LAN game of Chocolate DOOM running on the unholy trinity (Windows, Mac OS, and Ubuntu).

 That is all.


 

Sunday, September 28, 2025

Through a Glass Darkly: Building a Philip K. Dick Inspired Text Adventure in Python

As someone who's spent countless hours lost in the paranoid, twisting realities of Philip K. Dick's novels, I’ve always thought it would be fascinating to bring that sense of dislocation and uncertainty into a playable form. At the same time, I have a deep love for the classic text adventure games of the 1980s, especially Infocom’s masterpieces like Zork, where every room, every object, and every line of text contributes to an immersive narrative. It occurred to me that blending these two interests could produce something truly engaging: a text adventure where nothing is quite what it seems, and every discovery feels like piecing together fragments of a memory.

I decided to code this game in Python, keeping a few core design goals in mind. First, I wanted the game to feel like an Infocom adventure, so I structured it around a series of interconnected rooms, each with its own description, items, and NPCs. The player moves through these spaces using simple two-word commands like "go north" or "take wallet," much like the old parser systems that made classic interactive fiction so approachable and fun.

The game engine itself is data-driven. Rooms, items, and NPCs are all stored in dictionaries with unique identifiers, which makes it straightforward to expand the world later. Each room defines its exits, items present, and NPCs in place. Items can have clues associated with them, and NPCs can offer dialogue that hints at larger mysteries. Using Python’s dataclasses made it easy to encapsulate these properties while keeping the code readable and maintainable.

Player actions are handled through simple functions tied to verbs: go, take, use, talk, and so on. For instance, when the player uses the take command, the code checks whether the item is present and portable, adds it to the inventory, and collects any associated clues. Use commands allow the player to interact with objects or room features to trigger additional story events, like unlocking a door or revealing hidden information. There's also a clue and memory system that monitors the player’s discoveries; once enough clues are collected, the player recovers a key memory, which acts as a milestone in uncovering their identity.

Save and load functionality was also important, reflecting the classic adventure game experience where progress mattered. The game can save the current room, inventory, NPC states, and collected clues to a JSON file, and restore everything from that file later.

The narrative logic is built around the idea of memory recovery in a dystopian world—a theme central to Dick's work. The player starts with no memory and must piece together their identity by exploring the environment, interacting with NPCs, and collecting items. Clues are scattered across the world, and certain items or interactions trigger additional revelations. The balance between exploration, discovery, and narrative progression aims to capture the same unsettling tension found in Dick's novels, where reality is mutable and perception is unreliable.

This project was a perfect pairing for my interests: the procedural, logic-driven structure of a text adventure married to the thought-provoking themes and disorienting world-building of Philip K. Dick. It's designed to be extensible, so I can easily add more rooms, items, and puzzles in the future. For fans of either classic interactive fiction or Philip K. Dick's writing, this game represents a small but enjoyable intersection of those worlds—a place where the past, memory, and reality are constantly up for interpretation.

The code can be downloaded here if you want to try it out.

Now to see if ChatGPT can pass the Voight-Kampff test...

Sunday, March 30, 2025

"Hello Dr. Faulken. Would you like to play a game?"

I recently re-watched the classic proto-hacker 80's film "WarGames" starring Mathew Broderick and Ally Sheedy. The famous final scene, where the supercomputer Joshua blazes through game scenarios to final come to the conclusion that "the best way to win is not to play" got me thinking and inspired me to try my hand at writing a Python script that pits a computer against itself in a game of Tic-Tac-Toe, using the basic behavioral science concepts of positive and negative reinforcement, to see if I could approximate the fundamental principle of machine learning. After I got the script up and running, I added the ability to dump the results of each game played to an Excel spreadsheet, and then wrote another script using Matplotlib to visual the results. The hypthothesis is that after a certain amount of games, applying a basic machine learning algorythm the program will eventually move towards a state of optimal play, where the game registers more draws than wins. Essential the program is rewarded for making "good" moves that end in a win and penalized for making "bad" moves that result in a loss. The program will reach a state of optimal play when every game results in a draw. Essentially, when both the X and O are playing in an optimal state, the game plays to prevent a loss by achieving a draw.

Building the AI-Powered Tic-Tac-Toe

The foundation of the program is Q-learning, a reinforcement learning algorithm. The AI starts with no knowledge of the game and gradually improves by assigning values to moves based on rewards and penalties.

Here’s a quick breakdown of the main components:

  • Q-learning: The AI maintains a dictionary of board states, updating its move choices based on rewards.

  • State Representation: Each board is stored as a flattened tuple, allowing the AI to recognize patterns.

  • Exploration vs. Exploitation: Initially, the AI picks random moves to explore, but over time, it relies on learned strategies.

  • Game Logic: The program enforces Tic-Tac-Toe rules and determines wins, losses, or draws.

With this in place, I let the AI play itself continuously, logging the results to an Excel file.


 

Tracking 15,000 Games in Excel

To measure progress, I recorded each game’s outcome in an Excel file using pandas and openpyxl. The data captured:

  • Game number

  • Result (X wins, O wins, or Draw)

The AI played nonstop, generating a dataset of over 15,000 games. The expectation was clear: over time, the number of draws should increase as the AI moves toward optimal play.

Visualizing Learning with Matplotlib

Once the data was collected, I used Matplotlib to analyze how the AI improved. The key visualization was a draw percentage over time graph, calculated as:

Draw Percentage = (Total Draws / Total Games) * 100

Plotting this gave us a clear trajectory of improvement. Initially, wins and losses fluctuated wildly, but as the AI refined its strategy, the percentage of draws steadily climbed. By the end, draws accounted for over 40% of games played—a strong indication that the AI was approaching a level of optimal play.


 

Results: AI in Action

The final graph showed exactly what we hoped for: a sharp increase in draws, meaning the AI was learning to avoid losing. In Tic-Tac-Toe, perfect play from both sides should always result in a draw. The fact that the was AI approaching 100% draws proved that it was learning over time to eventually solve the game.

Final Thoughts

This project was a cool dive into reinforcement learning, data tracking, and visualization. Watching the AI evolve from random flailing to calculated mastery was incredibly rewarding. If you’re interested in AI, I highly recommend trying something similar—watching an algorithm teach itself is a fascinating experience. While 15,000 games of Tic-Tac-Toe seems like a LOT of games for a computer to begin to teach itself such a simple game, there are a few tweaks to the code that could be made to accelerate the learning. I'll probably continue to tinker with this one for a bit, as it is a surprisingly satisfying to watch the machine start to rack up draws against itself.


Saturday, February 22, 2025

Brackey's 2025.1 Game Jam

One of the coolest things to do when learning game development is to challenge yourself by participating in a game jam. Game jams are essentially online competitions where participants have a limited amount of time—ranging from several months to as short as 24 hours—to conceive and develop a working game. Most jams have "themes" that are announced when the competition begins. The themes vary with each jam, but one consistent rule is that participants must incorporate the theme into their game in some way, whether visually, mechanically, or narratively.

This past week, I had time off from work and discovered that the Brackeys 2025.1 Game Jam was happening, so I challenged myself by signing up. The jam started on Sunday and ran for seven days, with the winning theme, chosen by popular vote, being "NOTHING CAN GO WRONG."

I started brainstorming ideas for the game as soon as the theme was announced, eventually settling on a concept inspired by the Many-Worlds Interpretation in quantum mechanics. This theory posits that multiple timelines or universes are constantly being created and potentially destroyed. I'm kind of a nerd when it comes to physics, so this seemed like a cool basis for a game.

I began developing a simple game where the player controls an object on the screen that leaves a trail (representing the "timeline") while dodging enemy objects that randomly spawn from the top of the screen. To enhance the "moving through space" feel, I created a starfield background using Unity's particle system. One of the trickier mechanics involved spawning duplicate versions of the player whenever a collision occurred—these "alternative timelines" formed the core of the game's challenge. If too many spawned, the game would end. Since enemy spawns increased in intensity the longer the player survived, I realized I needed to balance the difficulty by adding a "good" object that could remove a randomly instantiated timeline. This added a risk/reward element, as players had to maneuver across the screen to collide with these objects and prevent the timeline from spiraling out of control. Fortunately, implementing this was easier than expected, thanks to Unity's prefab system and some trial and error with scripting. Everything was going great—until Day 3.

Disaster Strikes on Day 3

On Day 3, I decided the game needed a start screen. In Unity, this is done by creating a separate scene, which acts as a container for all the assets and code used in that part of the game. While working on this, I noticed that my main game scene still had the default name "SampleScene." Thinking nothing of it, I renamed it to "GameLoop" to make it more descriptive. I then returned to my newly created "StartScene" and began designing a start menu with some simple animations.

Once I was satisfied with the start menu, I saved my progress and went back to the GameLoop scene—only to realize something was very wrong. The game view screen was now completely gray, and all the game objects I had created and set up in the Unity Hierarchy window were gone.

Panic mode.

I quickly quit Unity and reloaded the project, hoping that would restore everything. It didn’t. The scene was still empty. A frantic Google search revealed the awful truth: I had inadvertently deleted my entire game scene by renaming it.

At that moment, I decided it was best to step away from the computer, grab a cup of coffee, and reconsider my options. I briefly thought about quitting the jam entirely and chalking this up as a painful lesson in Unity file management. But after a short break, I examined the empty scene again. My assets, prefabs, and, most importantly, my scripts were still intact in the Project Window.

All hope was not lost!

Rebuilding the game from scratch was still a daunting task, but I decided to give myself one hour to attempt it before making any final decisions. To my surprise, after that hour, I had restored most of the game’s core mechanics. So I kept going.

At the 3.5-hour mark, I had fully reconstructed the scene and even made a few improvements to the game's structure. Lesson learned: 

Rename your scenes BEFORE working on them!

Day 4 – Polishing the Game

Day 4 was all about refining the visuals. I replaced the placeholder geometric shapes I had been using for enemy and helper objects with actual pixel art. I also created a portrait of the player's character for the HUD, inspired by the classic DOOM face from the 1990s game. Eventually, I might even animate it to react when the player collides with an enemy, but for now, I’m quite happy with how it looks. I also fine-tuned the start menu’s timing and tested the game on multiple machines to ensure it functioned smoothly.

Day 5 – Preparing for Submission

Day 5 was spent setting up the itch.io page and adjusting the build settings to ensure the game displayed properly in WebGL. I’ve overlooked this step in past jams, and it can really impact how players experience the game. If a game doesn’t display correctly in the browser window, it can lead to a poor user experience and lower ratings. A little bit of research and testing goes a long way in making sure your hard work is properly showcased.

After a few final playtests on itch.io, I officially published the game and submitted it to the jam.

As of today, the jam ends tomorrow at 6 AM EST. After that, all participants can play and rate each other’s games based on various criteria. I’m excited to see what others have created and to receive feedback on Timeline. Every game jam is a learning experience, and this one definitely pushed me creatively.

If you're interested in playing my game, you can check out Timeline here.




Wednesday, February 5, 2025

Are You Keeping Up With the Commodore?

The Commodore 64 was an amazing computer, for a number of reasons. It's arguably not only the best-selling home computer of all time, but the one machine that really was able to change the opinion of the masses in terms of how computers could be used. It was the perfect fusion of game machine and home computer, something that other companies during the 1980s had a hard time keeping up with.


 

The downside of all old technology is that things break. I had noticed that the sound quality produced by my Commodore 64 was distorted during some programs. A good example would be the introduction to Activision's "Ghostbusters", when it plays the theme song. Some parts were fine, but others were either extremely distorted or in extreme cases not even present. This is a classic symptom of a failing SID chip in the old Commodores. The SID chip, otherwise known as the "Sound Interface Device", is the IC responsible for producing the lush, 80's sound that the C64's were renown for. The SID was extremely advanced in terms of its ability to produce a wide range of sounds, combining both analog and digital circuitry. The downside, it that overtime, these SID chips have a tendency to fail, causing the same distorted or dropped audio that I have been experiencing on my machine. In serious cases, the chip fails completely producing no sound whatsoever. Also, unfortunately, these chips have not been produced in decades, so fully functional old-stock SID chips can fetch a handsome prince on eBay. So what is one to do if they desire to keep their beige time machine singing sweet tunes? There are some after market options which can emulate the sound of a classic SID, but they are not perfect in their emulation and they can be a bit pricey as well. While pondering this conundrum it occurred to me that I actually had another Commodore 64 that I had acquired in a lot of used and mostly broken electronics. I decided to roll the dice and do a SID swap to see if the chip from the donor machine would work.


 

The actual process of swapping chips is extremely easy. Just open up the case, locate the chip (the year range of my 2 C64s the SID chip used was a 6581, which is printed on the top of the chip.), pull the chips using a chip puller - which is essentially a large set of tweezers - plug the donor chip into the socket. You want to make sure the chip is align *exactly* as it was when you pulled it from the donor machine. Mine had a dot on the chip which made it easier to see which end should be pointed "up" in the socket. After completely the swap I turned on the 64 and loaded up "Ghostbusters" and to my infinite pleasure I was treated to the entire tune during the opening screen. I then loaded up a new Commodore 64 game called "Galencia" - yes, people are still making software for these old workhorses - and was treated to lush sound dripping with that certain je na sais pas that only a fully functional SID chip can produce. After running "SID Bench", a piece of software that tests all of the registers in the chip for functionality and sound quality, I was off to the races! Luckily, the donor machine had a functioning chip. Fixing stuff is fun!

Sunday, February 2, 2025

Reviving a dead Atari 2600 (Part One)

 

Anybody who knows me fairly well can probably attest to my love of video games, particularly retro games. In many ways, I grew up alongside the home video game industry. My first console was the venerable (and much-maligned) Atari VCS—later renamed the Atari 2600. A good friend’s family bought one, and as soon as I sat down to play, I was hooked. I pleaded with my parents to get one, and we eventually struck a compromise: if I saved up $100, they would cover the remainder of the cost. It took me well over six months to gather the funds, but I did it, and I’ve been a console owner of some kind ever since.

The 2600 is a special machine, in my opinion. Yes, its graphics are crude by almost any standard, and most of its games are quick simulations or basic shoot-’em-ups. But over time, those early programmers truly mastered how to squeeze lemonade out of those lemons. Games like Demon Attack (Imagic), Pitfall! (Activision), and Yar’s Revenge (Atari) pushed the limits of what even the original engineers thought possible. I’ve always believed that’s the beauty of the system—like most things in life, it’s the limitations that force us to think creatively and come up with novel solutions.

With that in mind, I was stoked to find a very early "Heavy Sixer" listed on eBay for $20 (shipping included). The catch, of course, was that the seller listed it with the caveat: "Dead. Parts Only. As Is." Okay, sounds like a challenge to me…

I was pleasantly surprised when the console arrived. Even though the seller was shipping a broken machine, they had taken the time to package it well, preventing any unnecessary damage in transit. The system didn’t come with a power supply, so I borrowed one from my trusty four-switch "Woody" and hooked it up. After connecting it to a CRT tuned to channel 3 and loading up a copy of E.T., I powered it on—only to find that it did absolutely nothing. No black screen, no glitchy garbage, just the everlasting static of channel 3 on an analog television. The seller was honest! So, I proceeded to open up this relic of my childhood and take a look inside.

The console had about 40 years' worth of "patina," both inside and out—a lot of settled dust, caked in place. I started by blowing it out as thoroughly as I could with some canned air. While I had it apart, I gave the case a good soak in hot water with dishwashing detergent to clean it up. One thing I noticed was the manufacturing date on the RF shielding—May of 1982. That meant this was actually a "Light Sixer," not a Heavy Sixer. The Heavy Sixer/Light Sixer distinction refers to the number and type of switches on the console. The earliest models had six switches, while later versions had four. Heavy Sixers, the earliest models, are highly collectible and command premium prices when in working order. No worries, though—I was still excited to have a cool project to work on.

Looking over the motherboard, everything initially seemed pretty good. There were no noticeable burn marks or swollen/leaky capacitors. However, I did notice that one of the contacts for the power input seemed to be missing some solder. It looked like a chunk had broken off. Two out of the three pads associated with that component had massive mounds of solder, but the first had almost nothing in comparison. I could even see the tab of the component peeking through the board. Could this be it? Could it really be that easy? Maybe.

I grabbed my soldering iron and carefully added solder to the open connection, making sure not to bridge any surrounding gaps. One surefire way to fry these old boards is to accidentally bridge the first and second power connections. The second and third can be bridged, but connecting the first and second? That makes smoke appear—not ideal.


 

Once I finished, I hooked it back up to the CRT and tested it with Space Invaders… and voilĂ ! Well, sort of. The system powered on, loaded the game, and started the familiar color-flipping demo that Space Invaders does. But the sound was full of white noise, and the colors on the screen looked washed out. The game played fine, though, which was a huge success—it proved the machine wasn’t totally dead. It just wasn’t feeling great.

After a quick consultation with an electronics-savvy friend, they suggested I replace the electrolytic capacitors on the board. Over time, capacitors dry out and notoriously cause issues like the ones I was seeing. Fortunately, a diehard community of enthusiasts is dedicated to keeping these machines alive, offering repair and maintenance kits for the Atari 2600. A quick Google search led me to a company selling a "refresh and caps" kit for less than the cost of a couple of decent pizza slices.

I’ll post an update as soon as my kit arrives and I get a chance to swap out the old components!

Thursday, January 30, 2025

Making a retro inspired shoot 'em up in 2 days

 Developing Virex: A Retro-Inspired Journey

Two evenings, minimal assets, and a strong dose of inspiration from classic arcade games—this was the foundation of Virex, a fast-paced, score-driven 2D game heavily influenced by Asteroids and other golden-age titles. While the idea for the game was simple, implementing it introduced a variety of new techniques that expanded my Unity skill set.

The Core Concept

The premise of Virex revolves around the player navigating a simple yet challenging environment while fending off enemy threats. With only movement, shooting, and a regenerating shield to rely on, the player must rack up points while staying alive. Enemies spawn dynamically, and a rare Bonus Enemy appears every 500 points, offering extra scoring opportunities.

Game Management & Tracking

One of the major takeaways from this project was the implementation of a Game Management System. Instead of handling everything in isolated scripts, I used a centralized ScoreManager to track score updates and trigger events. This approach made it easier to handle elements like spawning bonus enemies when the player reaches certain point thresholds:

if (scoreManager.GetScore() >= nextSpawnScore)
{
    SpawnBonusEnemy();
    nextSpawnScore += scoreThreshold;
}

This simple yet effective system ensured that bonus enemies only spawned at the correct intervals, preventing unintended multiple spawns per frame. I had never used a dedicated Game Management System before for any of my projects, and I certainly will start implementing for future games. It made me life a heck of a lot easier!

Enemy and Bonus Enemy Behaviors

Unlike standard enemies, the Bonus Enemy follows a different movement pattern. Instead of homing in on the player, it moves in a random straight-line direction and wraps around the screen when it reaches the edges—similar to Asteroids. This required learning how to detect when an object exits the screen and reposition it accordingly.

if (transform.position.x > screenBounds.x)
    transform.position = new Vector2(-screenBounds.x, transform.position.y);

This created a seamless transition, keeping the enemy in play without abrupt disappearance.

A Minimalist Approach to Assets

One of my goals for Virex was to keep assets minimal—no fancy sprites, no elaborate animations. The visual elements consisted primarily of special fonts for UI elements and simple geometric shapes. For audio, I used basic sound effects to enhance the game feel, particularly when the player takes damage or destroys an enemy. The idea was to build this game quickly, with a focus on clean, minimalistic design, with the heart of the game residing in the code and gameplay.

A Simple but Effective Game Over System

A new technique I learned was how to handle game-over states properly. When the player's shield reaches zero, the game pauses and displays a "Game Over" message. One trick I picked up from the Galaxian port on the Atari 2600 console is the ability to restart the game with a simple press of the fire button - which I also implement here:

if (Input.GetButtonDown("Fire1"))
{
    SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}

Final Thoughts

Despite being developed in a short time frame, Virex turned out to be a great learning experience and honestly, it's a pretty fun little game. While testing the gameplay out I kept finding myself restarting the game to try to beat my previous high score - which is always the sign that you're doing something right. Implementing a structured game management system, working with screen wrapping, and handling conditional enemy spawning were all valuable lessons that will carry over to future projects. I'll probably eventually end up doing a separate build of this one to post on my itch.io page and make it playable in a web browser.



Sunday, January 26, 2025

When You Run Out Of Ideas, Work On The Start Screen.

Implementing Blinking Text and Interactive Buttons for "Descent into Madness"

The start screen is the first thing players see when they launch "Descent into Madness," so we wanted to ensure it sets the tone for the rest of the experience. We aimed for a balance of eerie atmosphere and intuitive functionality—achieving this with blinking text and buttons that react to the player's mouse movements. Here's how we did it.

Blinking Text for Atmosphere

First, we wanted the title text to "blink" as if it were flickering in and out of reality—a subtle nod to the unsettling vibe of the game. To create this effect, we wrote a script that smoothly fades the text in and out. By adjusting the alpha value of the text's color, we were able to achieve a fading blinking effect that feels alive yet haunting.

Here’s the core of our script:

using System.Collections;
using UnityEngine;
using TMPro; // For TextMeshPro

public class FadingBlinkingText : MonoBehaviour
{
    public float fadeDuration = 0.5f; // Time for a complete fade-in or fade-out

    private TextMeshProUGUI textMeshPro;
    private Coroutine fadeCoroutine;

    private void Start()
    {
        textMeshPro = GetComponent<TextMeshProUGUI>();
        if (textMeshPro != null)
        {
            fadeCoroutine = StartCoroutine(FadeLoop());
        }
    }

    private IEnumerator FadeLoop()
    {
        while (true)
        {
            yield return FadeText(1f, 0f); // Fade out
            yield return FadeText(0f, 1f); // Fade in
        }
    }

    private IEnumerator FadeText(float startAlpha, float endAlpha)
    {
        float elapsedTime = 0f;

        while (elapsedTime < fadeDuration)
        {
            elapsedTime += Time.deltaTime;
            float alpha = Mathf.Lerp(startAlpha, endAlpha, elapsedTime / fadeDuration);

            Color color = textMeshPro.color;
            color.a = alpha;
            textMeshPro.color = color;

            yield return null; // Wait for the next frame
        }
    }

    private void OnDisable()
    {
        if (fadeCoroutine != null)
        {
            StopCoroutine(fadeCoroutine);
        }
    }
}

We attached this script to our title text GameObject in the Unity Editor and set a fade duration of 0.5 seconds for a smooth, eerie effect. The result? The title text now feels like it’s phasing in and out of existence—perfect for setting the mood.

Interactive Buttons for Intuitive Navigation

Next, we turned our attention to the start menu buttons, ensuring they felt responsive and interactive. We wanted each button to highlight when hovered over, giving players clear visual feedback.

Unity’s UI system makes this easy with built-in button color transitions, but we needed a bit more control to match the aesthetic of "Descent into Madness." We wrote a custom script to handle color changes dynamically:

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class ButtonHighlighter : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
    private Button button;
    private Color originalColor;
    public Color highlightColor = Color.red; // Set the highlight color in the Inspector

    private void Start()
    {
        button = GetComponent<Button>();
        if (button != null)
        {
            originalColor = button.image.color;
        }
    }

    public void OnPointerEnter(PointerEventData eventData)
    {
        if (button != null)
        {
            button.image.color = highlightColor;
        }
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        if (button != null)
        {
            button.image.color = originalColor;
        }
    }
}

This script listens for mouse enter and exit events using the IPointerEnterHandler and IPointerExitHandler interfaces. We attached it to each button GameObject in the menu and configured the highlightColor to be a slightly menacing red. As a result, when players hover over a button, it subtly shifts to this color, drawing attention without overwhelming the design.

The Result

With these two features, the start screen of "Descent into Madness" is both visually engaging and user-friendly. The blinking text draws players into the game’s unsettling world right from the start, while the interactive buttons provide intuitive navigation with a touch of personality.

This implementation was a relatively small addition in terms of development time, but the impact on the overall presentation of the game is significant. It’s always worth investing effort into those first impressions—they set the stage for everything that follows.


Saturday, January 11, 2025

Shoot! Shoot!

 Implementing the Player Shooting Mechanic

In the world of game development, one of the most satisfying tasks is bringing a simple concept to life and watching it evolve. For our game, we needed to implement a shooting mechanic for the player character, but with a twist: the player can only shoot when they have accumulated enough "madness points." Since the player can only shoot when they have 5 madness points, and they die when they reach 10, the idea is to create a sort of "risk/reward" mechanic where on certain levels it makes sense for the player to engage in riskier behavior in order to access to the ability to shoot. In this post, I'll walk you through the journey of getting the shooting mechanic to work, from initial struggles to the final solution.

The Basics

The goal was simple: the player would shoot a projectile at enemies when they had earned 5 madness points. To achieve this, I used a basic system involving Unity's physics engine. The projectile itself needed to be able to interact with the game world, destroy monsters upon collision, and be destroyed after a set period to avoid cluttering the scene.

Starting Simple: The Projectile Prefab

The first step was creating the projectile. This was a straightforward task – I created a basic sphere mesh, attached a Rigidbody to it (so that it would move based on physics), and gave it a collider for interaction with other objects. The projectile would be instantiated at a specific spawn point in front of the player, and it would move in the direction the player was facing.

However, there was a catch. I wanted to ensure that the player could only shoot when they had accumulated at least 5 madness points. This led me to tweak the shooting mechanic to check the player's madness points before firing.

Checking for Madness Points

I created a script that referenced the MadnessPoints script on the player, checking if the player had accumulated 5 or more points before allowing them to shoot. This was implemented in the Update() method, where I checked if the fire button was pressed and whether the player had enough points.

void Update() { if (Input.GetButtonDown("Fire1")) { if (madnessPoints != null && madnessPoints.madnessPoints >= 5) { Shoot(); } else { Debug.Log("Madness Points insufficient to shoot! Need at least 5."); } } }

With this in place, the shooting mechanic would only be triggered if the player had the required madness points.

The Shooting Function

Once I confirmed that the player had enough madness points, I needed a way to actually fire the projectile. This involved instantiating the projectile prefab at a specific shoot point (right in front of the player). Here's the key part of the code that handled the shooting:

void Shoot() { if (projectilePrefab != null && shootPoint != null) { GameObject projectile = Instantiate(projectilePrefab, shootPoint.position, shootPoint.rotation); Rigidbody rb = projectile.GetComponent<Rigidbody>(); if (rb != null) { Vector3 forwardDirection = transform.forward; forwardDirection.y = 0f; rb.velocity = forwardDirection.normalized * projectileSpeed; } } else { Debug.LogWarning("Projectile Prefab or Shoot Point is not assigned!"); } }

This code spawns the projectile, adds a velocity to it (based on the direction the player is facing), and sends it flying. This mechanic worked well initially, but it wasn't quite finished yet.

Handling Collisions: Destroying the Monster

Now that the projectile was shooting, I needed to ensure that it interacted correctly with the monsters. Specifically, when the projectile collided with a monster, the monster should be destroyed. This meant adding a collision handler to the projectile script. The goal was to detect when the projectile hit an enemy (tagged as "Basic Monster") and destroy it.

Here's the collision detection code that did the trick:

private void OnCollisionEnter(Collision collision) { if (collision.gameObject.CompareTag("Basic Monster")) { Destroy(collision.gameObject); // Destroy the monster Destroy(gameObject); // Destroy the projectile itself } }

This code worked as expected – when the projectile hit a monster, it destroyed both the monster and the projectile. But this was only the beginning.

Adding a Time Limit on the Projectile

I didn’t want the projectiles to just hang around in the scene forever, so I added a time limit. After a brief delay, the projectile would destroy itself, cleaning up the scene. I made this delay configurable in the inspector so I could adjust it as needed.

public float lifetime = 2f; // Time before the projectile is destroyed private void Start() { Destroy(gameObject, lifetime); // Automatically destroy the projectile after a set time }

This ensured that after the projectile fired, it would be cleaned up after 2 seconds, preventing unnecessary clutter in the scene. It was a small detail but helped maintain a clean game environment.

A Final Touch: Adding Sound Effects

Of course, no shooting mechanic is complete without sound. I added a sound effect that would play every time the player fired a projectile. The sound is played using Unity’s built-in AudioSource component, attached to the player.


public AudioClip shootSound; // The sound to play when the player shoots private AudioSource audioSource; void Start() { audioSource = GetComponent<AudioSource>(); } void Shoot() { if (madnessPoints != null && madnessPoints.madnessPoints >= 5) { GameObject projectile = Instantiate(projectilePrefab, shootPoint.position, shootPoint.rotation); Rigidbody rb = projectile.GetComponent<Rigidbody>(); if (rb != null) { Vector3 forwardDirection = transform.forward; forwardDirection.y = 0f; rb.velocity = forwardDirection.normalized * projectileSpeed; } // Play shooting sound if (audioSource != null && shootSound != null) { audioSource.PlayOneShot(shootSound); } } }

Now, every time the player shoots, the sound effect plays, adding some nice audio feedback to the action.

Final Thoughts

After some trial and error, I was able to implement a working shooting mechanic that not only respects the madness points but also includes sound and effective collision handling. By breaking down the task into manageable parts and adding debugging steps along the way, I was able to quickly identify and fix any issues that cropped up. It’s a small feature in the grand scheme of the game, but it’s these little mechanics that bring the world to life.

Next up: refining AI behavior and adding some more exciting features to the game. Stay tuned!

The Lost Art of Tree Fishing

Now that the Northeast seems to have finally shaken off the icy grip of winter, I've taken the first reluctant steps outside of my cave ...