Umbral Castle

University Project

Umbral Castle Third Person Action Game

About this project

Umbral Castle was part of my Game Mechanics course at Stockholm university, where we were tasked with creating a 3D PC game. The goal was to work on overarching systems, implement and test new game mechanics, and learn to work in small team environments with scrum and git. This third-person action game drew inspiration from games like Dark Souls, focusing on combat mechanics and environmental storytelling.

Core Systems

Generic Event System
Fully static and type-safe event handling system

I developed a fully static (non-MonoBehaviour) and generic event system where events inherit or implement a base Event type. The system uses a dictionary to store Actions based on event types and provides methods for registering types, registering listeners, unregistering listeners, and firing events.

These Actions function like delegates but take an argument (the Event type) and return void. For example, systems can register listeners to "DeathEvent," storing callbacks to methods that execute when a "DeathEvent" is fired, processing the data it contains to trigger appropriate responses (playing sound effects, spawning particles, etc.).

This decoupled approach allows for great flexibility - if a feature needs to be removed, you can simply unsubscribe the system from the event or remove it entirely without breaking the codebase!

using System;
using System.Collections.Generic;

public static class EventHandler<TEvent> where TEvent : IEvent
{
    private static Dictionary<Type, Action<TEvent>> typeEventListeners;

    /// <summary>
    /// Registers Event Types to a dictionary
    /// </summary>
    private static void RegisterType()
    {
        if (typeEventListeners == null)
            typeEventListeners = new Dictionary<Type, Action<TEvent>>();

        if (typeEventListeners.ContainsKey(typeof(TEvent)) == false)
            typeEventListeners.Add(typeof(TEvent), null);
    }

    /// <summary>
    /// Adds a listener to a specified Event Type
    /// </summary>
    /// <param name="listener">Listener to register</param>
    public static void RegisterListener(Action<TEvent> listener)
    {
        RegisterType();
        typeEventListeners[typeof(TEvent)] += listener;
    }

    /// <summary>
    /// Removes a listener from a specified Event Type
    /// </summary>
    /// <param name="listener">Listener to unregister</param>
    public static void UnregisterListener(Action<TEvent> listener)
    {
        RegisterType();
        typeEventListeners[typeof(TEvent)] -= listener;
    }

    /// <summary>
    /// Invokes all registered callbacks for specified Event Type
    /// </summary>
    /// <param name="e">Event Type to Invoke callbacks for </param>
    public static void FireEvent(TEvent e)
    {
        typeEventListeners[typeof(TEvent)]?.Invoke(e);
    }
}

Gameplay Systems

Orbital Camera & Procedural Shake
Valheim-inspired camera system with advanced shake effects

Inspired by Valheim's camera system, I developed a simple but effective orbital camera with collision detection and scroll-to-zoom functionality. The goal was to recreate the satisfying feel of Valheim's camera while adapting it to our third-person action game.

The camera integrates with an event-driven procedural shake system that offers extensive customization: control over amplitude, frequency, strength over time, and falloff curves, with options to apply shake effects to rotation, position, or both.

Using scriptable objects to store shake settings, any system can trigger shake effects by passing these configurations. The shake system handles multiple simultaneous shake events, managing them until their duration expires before removing them. This flexible, generic approach allows the shake system to be applied beyond just camera effects.

Orbital Movement

Smooth camera rotation around the player with adjustable distance and height

Collision Detection

Prevents camera from clipping through walls and other environmental objects

Procedural Shake

Event-driven system with customizable parameters for different types of impacts

Closing thoughts

Umbral Castle marked a paradigm shift in my approach to systems design and programming. I gained experience with important programming patterns such as state machines and the observer pattern (through our event callback system), along with numerous smaller techniques that improved my overall coding practices.

Another crucial lesson from this project was understanding scope. We initially set extremely ambitious goals that led to burnout midway through production. This experience taught us the importance of developing smarter planning strategies beyond simply aiming for "perfection" in every aspect of the game.

I gained valuable insights about scrum methodology, managing expectations, and proper project planning as a result of these challenges. In retrospect, I believe the game could have been significantly better if we had focused more on making it fun rather than just visually impressive with complex systems we never fully utilized. Despite these realizations, I remain proud of this work as a significant learning experience in my development journey.

Project Details
Team Size5 (2 programmers)
Start DateMay 24, 2021
Duration2 months
ToolsUnity, C#
Keywords
Systems DesignModular SystemsProject ManagementState MachinesEvent SystemThird Person ActionUnityC#