top of page

Game Download

Crusade Game Development

The project, titled Crusade, was developed as a solo endeavour where I took on the role of lead developer. The tools that I utilizing are Unity and Photoshop, my primary goal was to create an engaging Action Role-Playing experience that emphasized key objectives, including multiplayer interaction and a seamless combat system. Throughout the project, I focused on overcoming challenges related to multiplayer implementation, ensuring smooth player interactions and consistent synchronization between the client and server to deliver a cohesive and immersive experience.

Role

Lead Developer

team size 

1 person

development time

6 months

Game Engine

Unity

Genre

Action Role-Play (Souls Like)

Technologies used

image.png

Unity

image.png

C#

image.png

GIT

image.png

Photoshop

image.png

Audacity

image.png

Trello

Coding Process

In developing The Crusade, I followed a five-step coding process, which I will elaborate on throughout this portfolio piece. Each phase was crucial in shaping the game's mechanics, systems, and overall player experience.

1

Concept & Planning

rough coding

2

Development

3

Testing & Iteration

4

passover and feedback

5

Player Input / Combat Input

This script is responsible for managing player input within Unity, leveraging the Unity Input System to capture and process user interactions. It translates raw input events such as movement, camera control, attacks, dodges, and item usage into actionable commands that drive player behaviour in real time. By abstracting input logic from gameplay systems, the script promotes modularity and maintainability, making it easier to adapt to different control schemes or expand functionality across platforms. This integration ensures responsive and intuitive control, contributing to a seamless gameplay experience

Overview of Unity's Input System

Unity’s Input System enables developers to define and manage user inputs through a centralized Input Action Asset, offering a flexible and scalable approach to input handling. This system organizes controls into Action Maps such as "Gameplay" or "UI" which group together context-specific input actions. Each Input Action represents a discrete gameplay function (e.g., Jump, Attack, Dodge), while Bindings connect these actions to physical input devices, such as keyboard keys, mouse buttons, or controller inputs. The input handling script assumes that internal input states (e.g., dodge_Input, RB_Input) are either continuously polled or updated via event-based callbacks registered through the Input System. This approach ensures responsive player control, simplifies input remapping, and supports multi-device gameplay configurations with ease.

image.png
image.png
image.png
image.png
image.png

Class Overview:

The PlayerInputManager class is implemented as a singleton to ensure centralized management of all player input across key gameplay systems, including movement, camera control, combat, and UI interactions. Built on Unity’s MonoBehaviour, it leverages the Unity Input System to handle player actions in a clean and modular fashion. By consolidating input processing into a single class, the system ensures consistent behavior, reduces redundancy, and simplifies the integration of player input with other subsystems. This structure also enhances maintainability and scalability, making it easier to support future gameplay features or adapt to different input devices

Singleton Pattern Implementation:

The PlayerInputManager utilizes the Singleton design pattern to guarantee that only one instance exists throughout the game’s lifecycle. During initialization, the script checks for an existing instance and automatically destroys any duplicates to prevent input conflicts. Additionally, the object is marked with DontDestroyOnLoad, ensuring it persists seamlessly across scene transitions. This approach provides a reliable, centralized input management system that remains consistent regardless of scene changes, making it ideal for complex gameplay systems and multiplayer environments

Player Controls Integration:

Player inputs are managed through a PlayerControls object, which serves as a centralized container for all input mappings. This includes core gameplay actions such as movement, camera rotation, dodging, sprinting, and weapon switching. By organizing input definitions within this object typically generated from an Input Action Asset the system promotes modular, readable, and easily extendable input logic. This structure ensures a clean separation between input configuration and gameplay behavior, simplifying debugging and enhancing support for multiple input devices.

Input Categories:

The input system is structured into distinct categories to ensure responsive and intuitive gameplay interactions. Camera movement inputs handle vertical and horizontal rotation, providing smooth and precise control over the player's viewpoint. Player movement inputs interpret directional input and calculate movement intensity (moveAmount), enabling fluid walking, running, and sprinting behaviors. Combat inputs manage key actions such as dodging, light and heavy attacks (mapped to bumpers and triggers), and toggling two-handed weapon stances, offering depth and flexibility in combat. The lock-on system allows players to target specific enemies and seamlessly switch between them, enhancing tactical engagement. Additionally, UI inputs enable players to interact with menus and interface elements, such as opening the character or inventory menu, ensuring a seamless transition between gameplay and user interface navigation.

Scene-Based Input Management:

The OnSceneChange method dynamically manages player input based on the currently active scene. This system ensures that input actions are only enabled during appropriate gameplay states, such as during active levels or combat zones. When transitioning to non-gameplay scenes such as main menus, loading screens, or cutscenes player controls are automatically disabled to prevent unintended interactions. This approach enhances overall game stability and user experience by isolating input logic to relevant contexts.

Focus and Input Handling:

The OnApplicationFocus method monitors the application’s focus state to manage input responsiveness effectively. When the application loses focus—such as when the player switches to another window or minimizes the game input processing is paused to prevent unintended actions. Conversely, input handling is resumed seamlessly once the application regains focus, ensuring smooth and consistent player control throughout the gaming session. This mechanism helps maintain gameplay integrity and enhances the overall user experience.

Queued Inputs:

The system incorporates input queuing for specific actions such as RB (right bumper) and RT (right trigger), using timers to manage input windows precisely. This approach allows players to buffer commands, ensuring that actions like attacks or dodges are executed at the optimal moment, even if inputs are entered slightly before the character is ready to perform them. By handling input timing with queues, the gameplay feels more responsive and fluid, closely aligning with the fast-paced demands of action-oriented combat systems.

Customizable Input Processing:

The input handling system is designed with modularity in mind, enabling easy extension and customization of individual input types. This flexibility allows developers to tailor input behaviors for specific gameplay mechanics such as toggling two-handed weapon modes, sprinting, and interaction prompts. By structuring input handlers as independent, interchangeable modules, the system supports seamless updates and feature additions without impacting unrelated controls, fostering maintainability and scalability throughout development.

AI System

I engineered a robust and scalable AI system within Unity, utilizing a modular, state-driven architecture tailored for both single-player and multiplayer environments. Inspired by Souls-like combat design, the system enables dynamic, reactive enemy behavior and supports advanced boss encounters. Key features include health-based phase transitions, adaptive combat patterns, and seamless synchronization of AI behavior and movement across networked clients. The architecture is designed for extensibility, allowing developers to easily implement new enemy types, abilities, and state logic while maintaining clean code separation and multiplayer reliability

Below is a brief demonstration of the AI system in action, highlighting key features such as state-driven behavior and health-based phase transitions. This clip showcases the core functionality of the system I designed and implemented

The AICharacterManager serves as the central controller for all AI behavior and state management. Acting as the 'brain' of each AI entity, it orchestrates movement, combat decision-making, state transitions, and health management. It also integrates seamlessly with Unity’s NavMesh system for dynamic pathfinding and navigation. This manager ensures modular, maintainable AI behavior suitable for both standard enemies and complex boss encounters in networked gameplay environments."

The AICharacterManager functions as the core controller for AI characters in a networked multiplayer environment, acting as the central decision-making component responsible for coordinating all major AI subsystems. It manages transitions between states such as Idle, Pursue, and Attack using a modular state machine architecture. For movement, it integrates seamlessly with Unity’s NavMesh system to enable real-time pathfinding and terrain-aware navigation. In combat scenarios, it handles target detection, attack logic, and awareness of player positions. The system also ensures consistent AI behavior across networked clients by synchronizing relevant data through the AICharacterNetworkManager. As the root orchestrator, the AICharacterManager delegates tasks to specialized components for movement, combat, and animation, maintaining a clean and extensible architecture.

Architecture Overview

The AICharacterManager inherits from a base CharacterManager class, which provides shared character logic such as animation handling, stat management, and death behavior. It integrates multiple subsystem managers including combat, movement, inventory, and networking to clearly separate responsibilities and maintain clean architecture. Each AI character independently manages its own NavMeshAgent, enabling precise pathfinding and responsive navigation across complex terrain. Built using Unity’s component-based design philosophy, the system remains modular, extensible, and easy to test, allowing for efficient development and debugging.

AI Behavior Control

The AI system is built on a modular state machine architecture, where each behavior such as idling, pursuing a target, or attacking is encapsulated within its own state class (e.g., IdleState, PursueTargetState). These states are designed to be interchangeable, allowing each one to return a new state based on in-game conditions, enabling clean, dynamic, and responsive behavior transitions (e.g., from detecting a player to chasing and attacking). The core method, ProcessStateMachine(), is executed every FixedUpdate() cycle to continuously evaluate and update the AI’s behavior in real time, ensuring consistent and frame-accurate decision-making.

Main Lifecycle Hooks

The AICharacterManager leverages Unity's lifecycle methods to manage its behavior efficiently in both single-player and multiplayer contexts. During Awake(), it initializes and caches references to key components. The OnNetworkSpawn() method ensures that initialization logic is only executed on the owning instance, a critical consideration for maintaining authoritative control in a multiplayer environment. OnEnable() and OnDisable() handle the registration and deregistration of UI-related events, such as health bar updates, promoting clean resource management and optimal performance. The Update() loop is used for handling ongoing, frame-dependent tasks like action recovery and cooldown timers, while FixedUpdate() is responsible for running the AI state machine and syncing movement data, ensuring smooth animations and accurate networked behavior.

UI/UX

This section covers the implementation of various UI components used in the Crusade to enhance player interaction, feedback, and game management. Each script is designed to handle a specific aspect of the user interface, ranging from color selection, character slot management, in-game health display, and attribute selection for character progression. These systems ensure seamless and intuitive user experiences, while also maintaining a modular and scalable architecture for future development.

System

HUD Rendering

Event-Driven UI

Data-Driven UI

Transitions

Performance-Conscious Rendering

UI Elements & In-Game Variables

Details

Guard follows a set of waypoints in a loop (forward/reverse). Smooth movement and rotation for natural patrols.

OnHPChanged(int oldValue, int newValue) reacts to health changes via a callback system.

CheckForUnlockedTeleports dynamically checks which sites are unlocked, showing/hiding buttons accordingly.

Guards take damage, enter alert mode, and die with animations when health reaches zero.

Minimal per-frame logic; uses Unity’s event system for updates.

teleportLocations[i].SetActive(true/false) in PlayerUITeleportLocationManager dynamically updates buttons based on the game state.

UI Systems Design

UI_Color_Button

This system implements character customization features by allowing players to adjust elements such as hair color through interactive UI sliders and color previews. It integrates smoothly into the existing UI flow, providing an intuitive and responsive user experience that enhances player immersion and creative freedom. This aligns with responsibilities such as designing and implementing systems that enhance player customization and ensuring seamless integration of UI/UX into core gameplay systems.

image.png
image.png

UI_Character_Save_Slot

The UI_Character_Save_Slot system serves as an interface for the save/load system, managing multiple character slots and ensuring a dynamic and responsive UI. It retrieves and displays saved character data (e.g., name, playtime) when a corresponding file exists, while gracefully disabling slots for which no valid data is found. This robust approach guarantees that players interact only with valid save slots, reducing errors and improving the user experience. The system aligns with core responsibilities such as designing and maintaining systems that manage persistent player data and creating robust UI elements for core gameplay systems like saving and loading.

image.png

The UI_Character_HP_Bar system implements a diegetic health bar that appears directly in the 3D world space above characters, enhancing player immersion and providing real-time visual feedback during combat. The health bar dynamically updates based on damage events, displays the character's name when enabled, and shows floating damage values for an additional layer of feedback. It features camera-facing logic (so the bar always faces the player) and an auto-hide timer to prevent UI clutter. This system aligns with responsibilities such as designing and implementing responsive combat UI systems that enhance player immersion and integrating UI elements seamlessly into 3D game spaces.

UI_Character_HP_Bar

image.png
image.png

UI_Character_Attribute_Slider

The UI_Character_Attribute_Slider script implements a clickable attribute selection system for character progression, enabling players to interact with complex systems like leveling up in an intuitive and accessible way. By mapping user interactions to in-game character attributes, this system bridges gameplay mechanics with the UI, ensuring players can easily modify their builds and make meaningful gameplay decisions. This feature aligns with responsibilities such as creating UI systems that allow players to interact with and modify their character builds and simplifying player interaction with complex gameplay systems.

image.png

Gameplay Systems Integration

System Design Architecture

My UI systems are not standalone components. they are tightly integrated with larger gameplay systems, demonstrating an understanding of system architecture, data flow across modules, and real-time state management.

For example, the UI_Character_Save_Slot system ties UI elements like save slots to the underlying save/load architecture (WorldSaveGameManager). This ensures that character data such as names, progress, and slot usage dynamically updates the UI, supporting a seamless and intuitive save/load experience. This design aligns with responsibilities like:

Similarly, the UI_Character_HP_Bar system demonstrates real-time data flow between the UI and gameplay modules. It interfaces with both PlayerManager (for multiplayer features) and AICharacterManager (for PvE systems), ensuring that the health bars, damage displays, and player/AI names accurately reflect the dynamic in-game state. This approach supports features like PvE boss fights, multiplayer combat, and in-world health feedback, aligning with responsibilities such as

By integrating UI components deeply into core gameplay systems, I ensure that user interactions and feedback are directly tied to the player’s experience, supporting both immersion and functionality across various game modes.

Modular and Scalable UI Systems

My code demonstrates a strong understanding of reusable and modular systems, ensuring future scalability and maintainability across the project’s UI architecture.

For example:

Health Bar System

The UI_Character_HP_Bar is a generic, reusable component that supports both AI and player characters. This design allows the same UI logic to drive health feedback across single-player and multiplayer scenarios without duplicating code.

Save Slot System

The UI_Character_Save_Slot leverages CharacterSlot enums and a dynamic file-checking system, making it easy to scale beyond 10 slots if needed. The modular approach ensures each slot uses the same logic, avoiding redundant code and simplifying future expansions.

Color Customization System

The UI_Color_Button offers a generic color selection mechanism that integrates seamlessly with sliders and color previews. This system can easily be extended to support additional customization elements like skin tone, eye color, or armor palettes, without rewriting core logic.

This emphasis on modularity and scalability across my UI systems aligns with industry best practices, ensuring the project is both maintainable and ready for future content expansions.

Clean Code Practices and Systematic Design Thinking

My code demonstrates a strong commitment to clean code principles and systematic design thinking, ensuring clarity, maintainability, and scalability. Naming conventions are consistent and descriptive, methods follow a structured format, and features are logically grouped by system for instance, UI logic is encapsulated within UI classes, while gameplay data is managed by dedicated systems like WorldSaveGameManager. This separation of concerns promotes clear system architecture, which is essential for effective collaboration across disciplines in a professional development environment.

This level of organizational clarity and methodical structure is crucial for large-scale projects, where readability, maintainability, and collaboration are key to long-term success.

A comprehensive analysis of the game’s User Interface (UI) and User Experience (UX) was conducted to assess overall usability, accessibility, visual clarity, and player engagement. The UI design follows a minimalist, immersive approach inspired by Souls-like games, delivering essential information without overwhelming the player. Key elements such as health, stamina, and focus bars are positioned intuitively, while item and equipment slots are icon-based for immediate recognition. Special attention was given to ensuring that visual feedback is responsive and readable, and that controls remain intuitive across gameplay scenarios. This evaluation informed iterative design improvements, resulting in a streamlined and cohesive interface that enhances player immersion and facilitates seamless interaction.

The user interface was designed with a Dark Souls-inspired philosophy, prioritizing minimalism and immersion while delivering essential gameplay information clearly and efficiently. The HUD is structured to keep the player focused on the environment, with core elements positioned unobtrusively. 

In the top-left corner, compact health, stamina, and focus bars—colored red, green, and blue respectively—convey the player's core stats without cluttering the screen. In the bottom-left, an icon-based quick slot system displays the currently equipped left-hand weapon, right-hand weapon, and usable item (e.g., a healing flask), allowing for instant recognition and fluid equipment management without menu interruptions. During boss encounters, a dedicated boss health bar appears at the bottom-center of the screen, displaying the enemy’s name (e.g., “Grim Warden”) alongside a distinct red bar. This separation from the main HUD ensures clarity in high-stakes combat scenarios while preserving the clean aesthetic of the interface

PlayerNetworkManager Script Overview

This script is a key component of a multiplayer game developed in Unity, responsible for synchronizing player-related data across the network. It leverages the Unity Netcode for GameObjects framework to manage player states, inventory, equipment, combat interactions, and more in a distributed multiplayer environment.

Key Features

  1. Networked Player States

  2. Real-Time Equipment and Combat Management

  3. ​Player Stats Management

  4. Multiplayer Interaction Handling

1. Networked Player States

  • Utilizes NetworkVariable to track and synchronize important player attributes such as:

    • characterName: The name of the player's character.

    • currentWeaponBeingUsed: Tracks the currently equipped weapon.

    • isTwoHandingWeapon: Indicates whether the player is using a weapon two-handed.

2. Real-Time Equipment and Combat Management

  • Dynamically updates player equipment and weaponry across the network, ensuring consistency in all connected clients.

  • Includes methods like OnCurrentRightHandWeaponIDChange and OnIsTwoHandingWeaponChanged to handle equipment changes.

3. Player Stats Management

  • Updates player health and stamina dynamically based on changes in attributes such as vitality and endurance.

  • Integrates with a custom Player UI to reflect updated stats on the HUD.

4. Multiplayer Interaction Handling

  • Includes ServerRPCs and ClientRPCs for reliable communication between server and client for weapon actions and updates.

PlayerEquipmentManager Script Overview

This script manages the equipping and unequipping of weapons and armor in a multiplayer game environment. It handles loading models, calculating stats, and synchronizing equipment changes across clients. The script also supports a dynamic two-hand system for weapons and ensures that equipment changes are reflected visually and mechanically.

Key Features

  1. Dynamic Equipment Loading

  2. Weapon Management

  3. ​Two-Handed Weapon Support

1. Dynamic Equipment Loading

  • The script dynamically loads models for equipped weapons and armor.

  • It uses predefined slots for each equipment type (e.g., head, body, legs, hands).

2. Weapon Management

3. Two-Handed Weapon Support

  • Allows players to two-hand either their left or right weapon.

  • Updates animations, strength bonuses, and model placements based on the two-hand state.

PlayerManager Script Overview

The PlayerManager script is the central hub for managing a player character in a multiplayer game using Unity and Unity Netcode. It orchestrates interactions between various sub-managers like locomotion, inventory, equipment, and combat, ensuring seamless synchronization of player actions and states across the network.

Key Features

  1. Modular Manager System

  2. Networked State Management

  3. Multiplayer-Specific Behavior

  4. Save and Load System

  5. Death and Respawn Logic

1. Modular Manager System

  • The script integrates multiple sub-managers (e.g., PlayerAnimatorManager, PlayerStatsManager) for handling specific aspects of the player character, adhering to the single responsibility principle.

  • Ensures all sub-managers are properly initialized during the player's lifecycle.

2. Networked State Management

  • Synchronizes player stats, equipment, animations, and actions across all connected clients.

  • Uses OnValueChanged callbacks to handle changes in network variables and update the UI or gameplay logic accordingly.

3. Multiplayer-Specific Behavior

  • Implements ownership checks (IsOwner) to ensure only the client controlling the player can execute certain actions (e.g., movement, input).

  • Loads and synchronizes player data when joining or leaving a multiplayer session.

4. Save and Load System

  • Supports saving and loading player data (CharaterSaveData) to ensure persistence across game sessions.

  • Handles equipment, stats, and positional data, restoring the player's state accurately when rejoining.

5. Death and Respawn Logic

  • Handles player death and revival, including UI updates and network synchronization.

  • Plays death animations and resets stats on revival.

Code Challenges and Solutions

PlayerNetworkManager Challenges and Solutions Overview

1. Ensuring Smooth Synchronization Across Clients

  • Challenge: In a multiplayer environment, ensuring all clients see the same state for critical variables like player equipment, stats, and actions can be difficult due to latency and potential desynchronization.

  • Solution:

    • Leveraged Unity Netcode's NetworkVariable system to track and synchronize key player states in real-time, such as equipped weapons, armor, and spells.

    • Used change callbacks on NetworkVariables (e.g., OnCurrentRightHandWeaponIDChange) to trigger updates whenever a value changes. These updates ensure that any modification is immediately reflected across all clients.

    • Implemented logical checks to prevent redundant updates, reducing unnecessary network traffic.

Achieved seamless synchronization, ensuring all players experience consistent gameplay regardless of their connection quality.

2.Managing Complex Equipment States

  • Challenge: Tracking and managing various equipment slots (weapons, armor, spells) while allowing for real-time updates presented challenges, particularly with interdependent equipment states like two-handed weapon usage.

  • Solution:

    • Designed a modular system using NetworkVariables for each equipment type (e.g., currentRightHandWeaponID, headEquipmentID).

    • Developed specialized methods (e.g., OnIsTwoHandingWeaponChanged) to handle state transitions dynamically, including animations, effects, and stat adjustments.

    • Integrated with a centralized item database (WorldItemDatabase) to ensure consistent item instantiation and data retrieval.

Allowed for robust handling of equipment changes, improving the user experience and enabling more dynamic gameplay scenarios.

Debugging & Problem Solving

While developing the AI patrol system for Crusade, I encountered a persistent issue where AI characters remained in the Idle state and failed to begin their patrol, even when assigned valid patrol paths. This write-up outlines the root cause, how I investigated it, and what steps I took to resolve the issue.

The Problem

Despite having patrol logic defined within the IdleState script and a valid AIPatrolPath assigned through the spawner, AI characters were stuck in the Idle state and did not begin moving along their patrol routes.

image.png
image.png

Investigation

I began debugging by placing breakpoints and Debug.Log() statements in the Patrol() method within IdleState. My goal was to track where the patrol logic was failing:

  • Check 1: Was IdleStateMode actually set to Patrol at runtime? ✅ Yes.

  • Check 2: Was aIPatrolPath null at the time of execution? ❌ Yes — for some AI characters.

  • Check 3: Was the AICharacterSpawner assigning the patrol path properly? ❌ Not always.

image.png

First Problem and Fix

One of the first issues I encountered in the WorldAIManager class was within the initial implementation of the GetAIpatrolPathByID method. In the original version, the method lacked a local variable, such as patrolPath, to store the result of the search. Instead, it returned the matching patrol path directly from within the loop. While this approach functionally worked, it limited the flexibility of the method by making it difficult to implement additional logic later on, such as logging or tracking whether a match was found. Furthermore, the placement of the error logging introduced an additional issue: the log statement was positioned outside the loop and not conditional, resulting in error messages being printed even when a patrol path had been successfully located and returned. This led to unnecessary and misleading error messages in the console, complicating the debugging process and reducing the overall reliability of the method.

Old Version 

Fix Version 

What Was Fixed:

Introduced a Local Variable (patrolPath):

The updated method now declares a local variable, AIPatrolPath patrolPath = null;, to store the result of the search. This change improves readability and flexibility, as it allows for additional logic, such as error handling, to be added more easily ensuring that errors are only logged when no match is found.

Conditional Logging:

Error messages are now logged only if a patrol path is not found (by checking if (patrolPath == null)). This prevents misleading error logs that might otherwise appear even when a valid patrol path was successfully located.

break Statement for Efficiency:

A break statement was introduced within the loop to stop searching as soon as a match is found. This reduces unnecessary iterations and improves performance. The old version relied on returning immediately, which lacked explicit control and could make the logic harder to follow.

Cleaner Return Logic:

The method now has a consistent and clear return structure: after the search, it logs an error if needed and returns the result, whether a match was found or not. This approach improves code clarity and maintainability by separating concerns searching, logging, and returning into distinct, logical steps.

This section of the code is currently under investigation because there appears to be a potential bug that may be preventing the AI from successfully patrolling.

  • Facebook
  • Twitter
  • LinkedIn

©2021 by Joseph Merrick. Proudly created with Wix.com

bottom of page