Quest of the Templar
About the Project
Quest of the Templar was the project I started in order to learn as much of Unreal Engine 5 as I could, as someone with very little prior experience in Unreal Engine, none of which was in UE5 or functional C++. To build these valuable skills, I followed a course on Udemy with two personal goals:
– Understand everything I was doing, making sure I wasn’t just blindly copying someone else’s work and learning nothing
– Build everything I thought of, going beyond the course and figuring it out through initiative, documentation and experimentation
This turned out to be a very effective learning style and resulted in a finished project that meaningfully diverged from the course, being more complete and feature-rich. Finally, a big focus in this project was learning to code the right way, learning to write code that doesn’t just compile, but to craft clean, efficient, and maintainable code.
Download and Try For Yourself
Keybinds:
WASD movement
Left Click Attack
Shift + Attack for Heavy Attack
Right Click Dodge
E to Interact
F to Toggle Equip
Alternatively view all of my code on Github:
Full walkthrough further down
Development Overview
World Building
Experimenting with Unreal Engine 5 was a key step in expanding my development skill set, allowing me to explore the engine’s advanced graphical capabilities. Most of my previous work in Unity focused on gameplay and mechanics, with limited attention to visual presentation. For this project, I wanted to create a strong visual identity from the start, one that would enhance and complement the core gameplay.
I began by creating a large-scale landscape featuring a mountain range, forest, and rolling hills, before carving out a pit to eventually form a lake. Once I established a consistent visual style through carefully selected assets, the world began to take shape naturally. From there, I added smaller narrative and environmental details, such as bones lining the lake’s edge to imply danger, and forest paths linking campsites for the player to raid, to reinforce atmosphere and worldbuilding.
The highlight of this process was designing the final boss arena. It successfully balances a sense of scale with the decayed, atmospheric qualities typical of underground dungeons. Through careful lighting and post-processing, I unified the boss’s colour palette with the torches throughout the room, creating a cohesive and gritty tone. The result is a visually striking environment that gives the final encounter a real sense of power and closure.

Characters
In a souls-like game, the most technically demanding aspect is the character system, covering both the player-controlled knight and the enemies and bosses they face. To keep my character code modular, maintainable, and efficient, I built a Base Character class that handled shared functionality such as health management, damage logic, and animation playback through utility functions. This eliminated redundant code and provided a clean foundation for all character types.
Both the knight and all enemies inherit from this base class, gaining access to core systems and only needing to define their own animation montages and section names for unique behaviours. This also provided directional hit reactions that grounded each encounter with clear visual and tactile feedback.
On top of this foundation, I implemented knight-specific mechanics using Unreal Engine 5’s Enhanced Input System. This enabled responsive control over the knight movement, then the ability to jump, and then even the ability to dodge enemy attacks, all integrated through the Base Character’s animation montage helper functions. This was governed by a custom Action State System that strictly manages when the player can perform certain actions. This gives combat the deliberate, weighty feel that defines the Souls-like genre.
Core functionality such as attributes, saving, and audio were built as independent components, keeping systems modular and scalable. Combat feedback ties directly into audio and particle effects, while ambient sound and combat music are dynamically triggered to reinforce atmosphere and intensity.
This modular architecture made it easy to expand gameplay and add new characters, enemies, and abilities without rewriting existing systems. It established a scalable, production-ready character framework that delivers reliable gameplay behaviour and the responsive, high-impact combat experience central to the project.

Enemy AI
My goal for the Enemies was to make them dynamic and readable, to match the tough but fair combat system of a souls-like. I achieved this using Unreal Engine 5’s AIController and Pawn Sensing systems, enabling player detection, patrolling and combat logic. This is all controlled using a custom state machine that transitions between patrolling, chasing, attacking, reacting to hits and dying. These states are incredibly controlled, therefore ensuring predictability and ease of debugging, critical to their success.
Every enemy inherits from Base Character, same as the Knight, gaining access to the same combat and animation functionality like attacking, hit reactions and health handling. This helped maintain visual consistency across all combat encounters in the game, and improving overall polish and cohesion from the start.
For awareness, every enemy uses a UPawnSensingComponent. When this detects the player, enemies transition from their patrol state into a chase state, managed through the AIController’s move system. A confusing bug I had to overcome with this was when the enemy would stop chasing and return to patrolling seemingly randomly, however this was solved eventually after iterating on my timed patrolling system. Once within a determined combat range, and checking cooldown timers among other conditions, the enemy uses the readable and predictable combat system baked into the Base Character class, mirroring an attack-chain in other games.
While patrolling, enemies follow a waypoint system, looping through an array of patrol locations and waiting at each location for a random amount of time. This helps to ensure the AI feel more organic and less scripted. Patrol progress is also saved and restored dynamically through the game’s save system along with lots of important information that will be detailed below.
If eventually defeated, enemies trigger their own death montages, before disabling their collisions and potentially dropping their weapon to be picked up and used by the player, and then spawning a soul and in some cases a key.
This modular AI system balances challenging, fair combat with robust, maintainable code, producing reactive and deliberate enemies that are well integrated into the wider character system.

Weapons
Weapons were obviously required in a Souls-like experience, and mine were built from a shared Item class that provides the framework to handle pickup and collision through a clean inheritance chain. Weapons derive from a base Item class, which manages world interaction such as hovering, pickup detection and visual effects. This ensures that any weapon can exist in the world as a lootable item before being equipped.
AItem handles player interaction with a USphereComponent that detects overlaps with actors that implement the IPickupInterface. When the player enters the sphere, the item sets itself as the active pickup target. On pickup, items then trigger a Niagara particle effect and a sound, keeping feedback consistent across interactable items and minimising repeated code.
AWeapon builds on this by including combat-specific functionality. Each weapon defines two scene components, Box Trace Start and Box Trace End, which are used to trace its hit area during attacks. When damage is enabled by notifies in attack montages, a box trace runs between these points each tick, detecting targets while ignoring the weapon’s owner and any actors already hit that swing. After extensive testing with more efficient systems that ran fewer traces, I deemed this to be by far the most reliable method of both hit detection and hit placement, at negligible performance cost.
On contact, weapons apply damage through UGameplayStatics::ApplyDamage before calling IHitInterface::Execute_GetHit on the hit actor, triggering animation driven hit reactions. Friendly fire checks are also in place to prevent enemies from hitting each other, and impact points from the box trace are used to spawn hit effects like particles and sounds.
When picked up and equipped, weapons attach directly to sockets added to character’s skeletons via an equip function. This function also handles equip sounds and particle deactivation, seamlessly transitioning the item from a world object to an equipped weapon.
The result is a flexible, maintainable weapon architecture that integrates cleanly with the project’s animation, damage, and item frameworks, supporting both gameplay and world-building needs through a single inheritance model.

Interfaces
Interfaces were a key part of keeping the project scalable and efficient. By defining classes like IHitInterface, IPickupInterface, IInteractInterface, and IUpgradeInterface, systems can interact without knowing the internal logic of the objects they manipulate. This ensures interactions are predictable, reusable, and easy to extend.
IHitInterface handles combat. Weapons call GetHit on any actor implementing the interface, triggering custom hit reactions, particle effects, and sounds; all without the weapon needing to know whether the target is a player, enemy, or boss.
IPickupInterface standardises item collection. Characters can pick up weapons, souls, treasure, or keys through SetOverlappingItem, AddSouls, AddGold, or AddKey, letting a single system manage all pickups and inventory updates.
IInteractInterface covers environmental interactions like doors. It provides methods like SetOverlappingActor and DoorText so objects can define custom responses to player interactions while remaining decoupled from the character class.
IUpgradeInterface supports the progression system. It exposes camera references, interaction text, and activation methods (SetOverlappingCampfire, CampfireText) that allow the player to safely trigger upgrades without directly linking UI, character, or campfire logic.
Interfaces were also used to minimise direct includes across interacting systems. Instead of including multiple class headers in every file that interacts with weapons, items, or environmental objects, the project relies on interfaces. This reduces compilation dependencies, lowers compile times, keeps file sizes smaller, and prevents tight coupling; all while still allowing full interaction functionality.

Progression
To give the player a sense of progression and a reason to fully explore the vast open world, I added incremental character statistic improvements tied to multiple currencies. Players access these upgrades through campfires placed throughout the level, which act as upgrade points and checkpoints for saving game data.
Campfires use a trigger sphere for overlap detection, similar to items, as well as a custom widget component for displaying upgrade prompts. When the player enters its radius, the campfire uses the IUpgradeInterface to retrieve the text it should display, alongside the location of the player camera to face. This interface-driven design keeps the campfire fully decoupled from the player and UI logic without compromising its functionality or file size.
All progression logic is handled through the UAttributeComponent, which stores and manages character stats such as health, stamina, regeneration rate, and damage multipliers. When an upgrade is purchased, the component checks resource requirements (Souls and Gold) before applying stat scaling, increasing both the player’s power and the cost for the next upgrade. Upgrade costs and multipliers grow progressively, creating a natural difficulty curve and a consistent sense of advancement. After testing this system of uniform exponential growth, I decided that the player never got to the desired health by the time all of their other stats were very good. This led to an extra multiplier applied exclusively to the player’s health which allowed extra fine tuning and ultimately a much better player experience with a very simple and efficient adjustment.
By splitting logic between the campfire and the attribute component, linking them only through interfaces; the system remains modular, lightweight, and easily extendable. Additional upgrade points or progression mechanics can be added without modifying the core player or attribute logic.

Game Saves
Finally the game needed a save system before it could remotely be considered complete, which was implemented through a modular save system component. This was designed to persist player progression and dynamic world state with minimal overhead. Rather than relying on level streaming or continuous autosaves, it performs saves at defined gameplay events such as upgrades or key progression moments, guaranteeing consistency while avoiding unnecessary writes.
When saving, the component collects and serializes data for every relevant entity: player stats, equipped weapon, active enemies, dropped items, and destructible objects. Each category is handled by dedicated functions, keeping logic isolated and scalable. Enemy and item data also use FSoftClassPath and FSoftObjectPath for reliable reference serialization across sessions, ensuring compatibility even after blueprint or asset path changes.
Loading is the opposite: existing enemies and items are cleared from the world before new instances are spawned from the saved class references. Attributes, patrol routes, timers, and arena bindings are restored individually, allowing AI to seamlessly resume what they were doing without being reset.
Whenever an upgrade succeeds, the attribute component automatically triggers a save through the game mode’s save system, ensuring progress persistence without any manual handling. The campfire then plays a Niagara particle effect and sound cue to reinforce the upgrade feedback loop.
This architecture keeps save logic fully decoupled from gameplay classes, improves maintainability, and eliminates the need for per-actor save code. The component can be dropped into any future project with minimal modification, providing a lightweight, reliable foundation for persistence across sessions.
