Showing posts with label game dev. Show all posts
Showing posts with label game dev. Show all posts

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

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.




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!

Thursday, January 9, 2025

Orchestrating the Player's Demise

Creating a compelling death mechanic in a game can do wonders for the overall player experience. It’s not just about letting the player know they’ve failed; it’s about tying that moment into the game’s theme and flow. For my project, I implemented a system where, upon reaching 10 madness points, the player "dies," the message "You have perished." is displayed, and the game pauses momentarily before looping back to the start screen. I took inspiration from the classic Fromsoft "YOU DIED" screen that anybody who has played any of their Souls games has experienced countless times. Here's how I pulled our "death screen" off step by step.


Setting the Foundation

Madness Points Tracking

The player already had a MadnessPoints script that tracks their increasing madness. It was modified to check if the madness points reached 10, triggering the death mechanic. To ensure a clean and modular approach, I encapsulated all death-related functionality within the script.

Adding the Visual Element

I used TextMeshPro to create a dynamic "You have perished." message. The text needed to:

  • Be inactive at the start of the scene to avoid distracting the player.

  • Activate only when the player dies, displaying prominently in the center of the screen.

A prefab of this text object was created for reusability across scenes. This made it easy to drop the prefab into any scene where the mechanic was needed.


Coding the Death Mechanic

Activating the Death Message

When the player’s madness points hit 10, the script activates the DeathMessage object using this code:

if (deathMessage != null)
{
    deathMessage.gameObject.SetActive(true);
    deathMessage.text = "You have perished.";
}

This ensures the message appears dynamically without needing manual intervention.

Adding the Pause

The game pauses briefly to give players time to absorb the moment before resetting. This was achieved using Unity’s Invoke method:

Invoke(nameof(LoadStartScreen), deathPauseDuration);

The deathPauseDuration is a public variable, adjustable in the Inspector, which allows me to fine-tune the timing for maximum impact.

Resetting to the Start Screen

After the pause, the game loads the "StartScene" using Unity’s SceneManager:

private void LoadStartScreen()
{
    SceneManager.LoadScene("StartScene");
}

Refining Across Scenes

One challenge I faced was ensuring this mechanic worked seamlessly across multiple scenes. While the prefab approach helped, I had to double-check that each scene’s MadnessPoints script correctly referenced the local instance of the DeathMessage object. I automated this in the script:

if (deathMessage == null)
{
    deathMessage = FindObjectOfType<TextMeshProUGUI>();
    if (deathMessage != null)
    {
        deathMessage.gameObject.SetActive(false);
    }
    else
    {
        Debug.LogError("DeathMessage TextMeshProUGUI not found in the scene!");
    }
}

This ensured that even if I forgot to assign the object manually in the Inspector, the script would find it at runtime.


Testing and Tweaking

With the mechanic implemented, testing revealed minor issues, like the message not appearing in certain scenes. These were quickly resolved by ensuring proper prefab usage and runtime object linking. Additionally, tweaking the deathPauseDuration helped strike the right balance between pacing and flow.


Final Thoughts

This death mechanic ties together the player's madness system with the overall game flow, creating a moment of tension and reflection before resetting the journey. It’s a small feature, but it reinforces the game's theme and enhances the player's experience.

If you’re working on similar mechanics, keep these key points in mind:

  • Make visual elements modular with prefabs.

  • Ensure dynamic linking for runtime flexibility.

  • Test across all scenes to catch inconsistencies early.

It works, and now we are approaching something that is more in line with an actual game versus a "tag" simulator. Eventually I will implement the ability to give the player some offensive capabilities once the madness points reach 5, which will introduce (hopefully) a sort of "risk/reward" system where the player may actually want to have a higher madness point value during some levels to fight back against the monsters, but if the points get too high, the player dies. We'll see how things go. I also want to tighten up the death mechanic a bit by freezing game time once the player accumulates 10 madness points so you won't have the clumsy situation of the monsters continuing to bash away at you before the game menu screen reloads, but for now I'm happy with what we have.



Monday, January 6, 2025

Scene Transitions

 I put it off for a bit, but if you’ve ever wanted to smoothen the experience of switching scenes in Unity, adding a fade transition can make a world of difference. In this post, I’ll walk through the process of implementing a scene transition effect that fades out before the new scene loads.

Step 1: Set Up Your Canvas and Fade Effect

First, you’ll need a Canvas that will hold the fade effect. I created a full-screen black image using a Canvas Group, which controls the alpha (transparency) of the fade. The idea is to gradually reduce the alpha of the image from 1 (opaque) to 0 (transparent) as you transition between scenes.

  1. Create a Canvas – This will serve as the container for the fade effect.
  2. Add an Image – Set this image to cover the whole screen. Make it black to create the fade-to-black effect.
  3. Add a Canvas Group – This component allows you to manipulate the alpha of the image for the fade effect.

Step 2: Implement the Fade-Out Functionality

Next, we need to write a script that will control the fade effect. The goal is to gradually fade out the black screen and then load the next scene after the fade is complete. To achieve this, I used the SceneTransition script, which includes a FadeOutAndLoadScene method. This method takes care of the fade out and scene loading, ensuring the fade is done before the new scene is loaded.

  • SceneTransition Script – Controls the fade-out effect and handles the loading of the next scene.

I ensured that the fade duration was set appropriately (around 0.5 seconds), but this can be adjusted as needed. The fade effect is controlled via the CanvasGroup’s alpha value, which is gradually changed over time.

Step 3: Detect Player Collision to Trigger the Transition

To trigger the scene transition, I needed an object that would act as a "trigger" for when the player collides with it. This object could be anything from an orb to a door in the game. In the LoadSceneOnCollision script, I used Unity's OnTriggerEnter method to detect when the player interacts with the object.

  • Collision Detection – This script listens for the player’s collision with the object and then calls the FadeOutAndLoadScene method from the SceneTransition script.

The key here is that once the player collides with the trigger object, the fade-out effect begins, and once it completes, the scene is loaded.

Step 4: Tying It All Together

The final step was integrating everything into the game. I made sure that:

  1. The SceneTransition object was in every scene that needed transitions.
  2. The LoadSceneOnCollision script was properly configured to load the correct scene when the player collided with the trigger object.
  3. The canvas group that handles the fade-out effect was correctly referenced in the SceneTransition script.

After these steps, the transition from one scene to the next was smooth, with the fade effect happening just before the scene loads, giving players a more immersive experience.

Final Thoughts

Getting scene transitions right can be tricky, but with the right setup and a little bit of code, you can add a professional touch to your Unity projects. The key is to use the canvas, manipulate the alpha for the fade, and trigger the scene change at just the right moment. Hopefully, this guide helps you get that effect working in your own game.



Thursday, January 2, 2025

Computers Inside of Computers

One of the features I wanted to implement was a simple puzzle where the player logs into a computer in-game to unlock a door blocking their progress. The coding for this event was relatively straightforward, but I ended up spending far too much time perfecting the computer terminal's graphics and text to achieve the look I envisioned.

I took inspiration from the old DEC Systems VMS/VAX terminals from the 1980s. During my night shifts at Digital Equipment, we used those "dummy" terminals to log in, check interoffice email, and occasionally spend lunch breaks browsing Usenet for interesting reads. Those terminals had a distinct monochromatic aesthetic that I wanted to replicate.

While I’m not saying I completely nailed it, I think I got pretty close, considering the assets I had to work with.

The trigger works, and the terminal screen looks almost exactly as I’d hoped.


 


Wednesday, January 1, 2025

Quick Updates

We’ve made significant progress on the game over the past few days. Here’s what we’ve been working on:

  • NPC Interactions: We added an NPC that appears in different areas of the game. Players can interact with them to receive additional narrative information.
  • Madness Lighting Effect: We implemented a feature that dims the scene lighting and activates a point light above the player character when their Madness Points reach 5.
  • Custom Fonts: We replaced the standard Unity font with sci-fi/horror-themed fonts to better match the game’s atmosphere.
  • New Environment: We’ve started working on a large, post-apocalyptic urban scene where the player will be transported after navigating the maze levels.

Future Updates We’re Considering

  • Scene Transitions: I’ve been pondering how to implement smooth transitions between scenes. It’s been on my mind for a few days, but I’ve been procrastinating on actually sitting down to figure it out.
  • Additional Monsters: Adding a variety of monsters with unique abilities and behaviors.
  • Final Boss Model: Sitting down with Blender to model the game’s final boss based on my son’s sketches.
  • Madness System Expansion: Enhancing the madness mechanic so the player dies when their Madness Points reach a certain threshold. Currently, Madness Points reset with each new scene, but we’re considering making them persist across scenes. To balance this, we might introduce a consumable item that reduces Madness Points when used.
  • Puzzle Interaction System: Creating a system where the player interacts with a computer in the third scene to open a door blocking access to the first orb on that level.

These updates and ideas aim to make the game more immersive, challenging, and rewarding. We’re excited to see how everything comes together!


Saturday, December 28, 2024

Spending time "polishing".

It can be overwhelming to sit down and list out all the things that need to be implemented in a video game. I think that’s a wall everyone who pursues game development as a hobby encounters at some point during a project. I hit that wall this morning while working out a list of things to add. Should I start on a combat system for our intrepid player to fight back against the forces of evil? Should I work out a damage system? Add more monsters with unique abilities? Tweak the existing AI? Add fades and transitions between levels and scenes? You get the idea—overwhelming!

Faced with this seemingly unlimited list of tasks, I decided to eschew all of it in favor of doing some video game "polishing." Polishing is an aspect of game development that many hobbyist developers tend to neglect. It’s not as exciting as, say, writing a script that lets the player shoot fireballs or laying out the perfect dungeon level. It’s actually kind of mundane and, quite frankly, boring. Polishing involves playing through the game and identifying areas that lack refinement. These might not break the game or even impact gameplay but still look or feel unpolished.

Things like improper transitions, bad scene lighting, clipping issues, and more are all ripe for the polishing list. Addressing these systematically makes your game look better and, in turn, improves the player’s experience.

My polishing list for today included a couple of common issues faced by hobbyist developers: preventing the player camera from clipping through walls and improving the readability of pop-up windows.

The pop-up windows were an easy fix. It took just a few minutes tweaking the pop-up background in the Inspector to create a nice, black, opaque backdrop. Another minute or two centering the text properly, and the pop-ups looked 100% better. It was a minor adjustment, but it paid off in terms of how polished and professional the game felt.

The second issue, where the 3rd-person camera clipped through walls when the player stood too close, was another story. There wasn’t an easy fix in the Inspector for either the camera or the walls to prevent the camera from swooping outside the level. This was particularly problematic near the perimeter walls, where the camera would clip through and reveal the skybox and other unpleasant artifacts outside the playable area.

It became clear this required a script. I needed to create a camera collision system that could detect obstacles and adjust the camera’s position accordingly. This involved using raycasting to measure the distance from potential obstacles and repositioning the camera to prevent it from clipping through walls and other objects players tend to walk up against. After some testing and tweaking, I had a workable fix that solved the wild camera problem.

While testing the camera fix, I also noticed the scene lighting needed adjustment to create more ambiance. I added some point lighting to better highlight important objects, like those tied to the newly improved pop-ups.

Polishing isn’t an exercise in instant gratification, but it makes any game feel significantly more professional and enhances the player’s experience. After finishing these updates, my son test-played the levels and was amazed at how much better the game felt.

So, the lesson of the day is this: don’t forget to polish your game while you build!





Friday, December 27, 2024

Pop (Ups) Rock.

I decided to take the game in a slightly different direction when I realized I had overlooked a major aspect: the actual narrative. What’s happening to the player, and why would they want to run around in the game to begin with?

My son had an excellent idea: seed the game with loose journal pages that the player could collect throughout the game. These pages would gradually introduce the player to some of the less obvious aspects of the game, while also building the story of why the player’s character is there in the first place. The journal pages could also serve to incrementally piece together the game’s overarching narrative—introducing bad guys, bosses, and maybe even some twists and turns along the way.

Pop-ups are a pretty standard component of video games, but, to be honest, I had never implemented one in Unity before. After some Googling, I figured out how to create the necessary panel game object to make the pop-up work. I added text and a button, then found some loose paper prefabs in a texture pack I grabbed from the Unity Asset Store. After attaching a collider and a simple script, voilĂ ! I was in business.

Well... sort of.

The problem I encountered was that while the pop-up worked when the player triggered the collider, those pesky AI monsters would continue attacking, racking up Madness Points while the player was trying to read. That wouldn’t do!

A little more research revealed that my pop-up script needed a method to pause the game. I adjusted the script to stop game time while keeping the UI functional, so the player could still use the mouse to click the “OK” button and close the pop-up after reading it.

It took some tinkering and a bit of help from ChatGPT, but we finally got everything working. Here’s a screenshot of the first pop-up in action:


It’s not perfect yet, and I’ll probably tweak the settings in the inspector to clean it up a bit more, but it works! This will go a long way toward fleshing out the game and building a solid narrative.

Eventually, I plan to upload all the scripts for this game to GitHub and share the link here—just in case anyone out there might want to peek under the hood of this project. But that’s a task for another day!

Thursday, December 26, 2024

It's alive! Well, sort of...

After a couple of days reacquainting myself with Unity, I decided it was time to start creating. I laid out my notes and began by setting up a basic sandbox environment where I could create prefabs and hook up scripts to implement some of the game’s core mechanics.

I started with a simple scene featuring basic obstacles—nothing too fancy. Then, I decided to import Unity's standard third-person controller. I know there are plenty of controllers out there, and I could have opted to create one from scratch, but I actually like Unity's default controller. Plus, it works seamlessly with my USB PlayStation 4 controller, which I planned to use for this game. So, the standard option it is!

After writing some janky scripts (did I mention that C# isn’t my preferred language? I mean, “methods” instead of “functions”? What’s up with that?), I had a character who could walk, run, jump, and was fully animated.

A couple more scripts and a quick trip to the Unity Asset Store later, I had a generic monster chasing my player around. I have to say, the AI script for my monster is probably my proudest Unity moment so far. I managed to make the monster patrol randomly within a set perimeter and then chase the player as soon as it acquired line of sight. I even got the monster animated—huge for me, considering Unity's Animator has always been one of the most perplexing parts of the engine.

My son suggested that the game should feel like a nightmare, where the monsters gradually drive the player mad just by being near them. Think of a nightmare where you feel an evil presence close by—maybe right behind you—and you know that turning to look at it would be your doom. That’s the vibe we were going for.

To keep it simple (at least for now), I decided that if a monster got close enough to touch (collide with) the player, the player would accumulate “Madness Points.” Get enough Madness Points, and strange things would start to happen in the game. Too many, and the player would die. It seemed like a cool and doable mechanic. After a couple of attempts, I had a script that registered Madness Points and displayed them on screen using TextMeshPro.

While working with colliders and triggers, I decided to implement a system where the player has to find and activate a series of floating orbs. Activating these orbs would eventually open a portal to the next level.

Pretty soon, we had a playable prototype where you could run past monsters, activate hidden portals, and progress to the next level. The two levels we have so far are pretty basic but completely scalable. We even added music to each scene for that extra post-apocalyptic meets Stranger Things vibe.

Next on our to-do list:

  • A custom model for the player.
  • A few different types of monsters.
  • A combat system, so the game isn’t just “catch me if you can” parkour.
  • Sweet fade transitions between scenes.






Wednesday, December 25, 2024

Brainstorming 101

 

The most important lesson I’ve learned since getting serious about coding is this: you need a plan. The biggest trap I’ve fallen into when starting a new coding project is not having a clear roadmap for where I want to go and how to get there. I’ve spent countless hours writing what I thought was meticulous code, only to realize I had no idea how to tie everything together or how to implement the components I needed to fulfill my vision.

With that in mind, my son and I sat down over a delicious meal at a local Mexican restaurant to hash out the basics of our game. We brainstormed plenty of ideas, but these are the ones that rose to the top:

  • A 3D game.
  • A setting in a post-apocalyptic, broken world.
  • The player is on a quest to discover who they are and their ultimate purpose.
  • Minimal combat, with a focus on puzzle-solving.
  • Dark, nightmarish entities that stalk the player.
  • A “Madness” mechanic, where the player accumulates madness, which alters aspects of the game.
  • A storyline revealed gradually, potentially through finding diary pages.
  • The title: "Descent Into Madness."

Based on these factors, I decided to stick with Unity. I had dabbled with 3D projects in a few game jams before, so I felt more comfortable with its workflow. I did briefly consider using this project as an opportunity to learn Godot, but Unity’s sweet siren call was too strong to resist!

After reviewing my notes, I decided the best first step was to create a basic "sandbox" level (or “scene,” in Unity terms). This would be my testing ground to get the core mechanics in place: basic player movement, collisions to trigger events, and a simple AI for some monsters. My goal was to build prefabs and reusable scripts that could be implemented throughout the larger game as we expanded the world.

Time to fire up Unity Hub!

Let's make a video game!

 

Full disclosure: I’ve never worked in any sort of technology field—aside from a couple of awkward years in my early 20s when I worked as a maintenance technician for Digital Equipment Systems, troubleshooting and repairing surface mount technology equipment on their night shift. However, ever since I saved up my money at the tender age of 12 to buy an Atari 2600 video game console I’ve always considered myself a fairly avid fan of all things computers and anything remotely “techy.”

It’s no great stretch of the imagination that during COVID, while everyone else was picking up hobbies like baking sourdough bread and knitting, I took on the seemingly impossible task of teaching myself how to stop passively enjoying computers for entertainment and actually learn to manipulate these electronic magic boxes by teaching myself to code.

I did have some experience with coding in my past. It started with my trusty Commodore VIC-20 and its built-in BASIC interpreter and then progressed to my school’s Commodore 64 during junior high. I enjoyed peeking and poking my way through the numerous magazines dedicated to the burgeoning microcomputer scene of the mid-1980s and even had modest success creating text-based dungeon crawls for my friends to play.

I didn’t do much else with coding until I worked part-time at a community college library during the evenings while pursuing my bachelor’s degree at a local public university. One of the librarians decided the library needed a website (this was the early 2000s, and many organizations were still ramping up their online presence). I had mentioned in passing that I had some experience with HTML. It wasn’t entirely a lie—I had dabbled with HTML and JavaScript a bit during the ’90s—but it was mostly the ubiquitous “Hello World” stuff that many people never seemed to advance beyond.

Long story short, I found myself charged with creating a basic website for the library. After spending numerous shifts Googling and scanning the library’s limited selection of web development books, I managed to pull it off. Afterward, I felt content to leave my coding journey on the sidelines, enjoying games, social media, and general web surfing. Regular “normie” stuff.

That is, until COVID hit.

During the pandemic, I had the wild idea that I could actually learn to code—actively create software and unravel the mysteries of all those binary files people depend on for everything that makes our digital lives so cool. To my surprise, it was easier than I’d imagined.

I started with C++, which probably wasn’t the best choice in terms of ease of entry. Still, I eventually got the basics down well enough to create a terminal-based version of a solo board game I’d designed a few years earlier. Needless to say, I was stoked, though it was far from impressive.

Soon after, I made the rookie mistake of jumping into another language: the much more user-friendly Python. Python was great, and with its plethora of libraries, I quickly figured out how to create simple games with basic graphics. I had finally escaped the terminal!

At some point, I discovered “game engines,” which facilitate game development—a natural focus for me since I love video games. After experimenting, I landed on Unity, which uses C# as its scripting language. C# clicked for me because it’s a powerful language in the tradition of C++, yet it has the high-level syntax I’d grown accustomed to with Python.

Before I knew it, I was writing lots of spaghetti code to cobble together broken games for game jams, swearing under my breath because none of my TextMesh Pro features worked the way I wanted.

Now, here we are.

A few weeks ago, my 13-year-old son—who has diligently absorbed all my knowledge of Star Wars, LOTR, and all things nerdy and cool—approached me with the idea of collaborating on a video game. He had a head full of ideas for a story about a person lost in a nightmarish, post-apocalyptic dreamscape, on the run from dark forces while piecing together the clues of their own identity and purpose in a cold and cruel universe. He would handle the ideas, and I would provide whatever meager technical skills I’d cobbled together to make it happen on screen.

I’m not going to lie—it was one of my proudest moments as a parent. (Well, maybe not the proudest, but it’s definitely up there.)

This “blog,” or whatever it is, will serve as a sort of development log and a place to vent when things don’t go well (looking at you, TextMesh Pro).

So, as they say, the journey of a thousand miles begins with a single keystroke...


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