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!

"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 fina...