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!


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