EcliPsychosis
Project Summary
Enter EcliPsychosis, a challenging, fast-paced roguelike tailored for players seeking a thrilling experience. Adaptation is key as you navigate through an ever-shifting environment and encounter enemies whose behaviour dynamically alters with the alternating cycles of Sun and Eclipse.
Step into the world of The Lost and Not Found, an enigmatic world where the Sun and Eclipse cycles continually shift. Stay vigilant as the shifting celestial phenomena influence and transform the behaviours of your enemies, creating an ever-evolving challenge!
This project marked my debut in Unreal Engine and C++. Developed during the spring term of 2023 at FutureGames, it was a collaborative effort involving two programmers, two artists, and three designers. The project was a significant learning experience for me. Despite a few elements being a touch disorganized, I'm immensely satisfied with the overall outcome and my substantial contribution to the project.
Contribution Summary
-
Combat framework with support for different weapon types and hitboxes.
-
Dash ability.
-
AI tasks and implementation.
-
Ranged component taking off and using projectiles from skeletal mesh.
-
Floating damage text.
Combat Framework
The initial phase of this project involved devising a framework for character movement and combat. Collaborating with another programmer, we developed a component-based combat framework. Both player characters and enemies utilize identical components, configured during initialization. Our system comprises two primary attack components: Ranged and Melee, accompanied by a Hitbox and a Hurtbox component. This versatile framework allows for effortless customization, enabling the creation of diverse character types with unique combat attributes and behaviours.
The melee component implements an IWeapon interface which boasts three distinct attack types: Light, Heavy, and Special. Each type can possess its unique hitbox or share one collectively. Initiating an attack is Blueprint-triggered but doesn't immediately activate the hitbox. Instead, the hitbox is toggled on and off directly from the attack animation itself, offering precise control over its activation for the combat designers. In terms of coding, I assumed the primary role in developing the combat framework.

The Hurtbox comprises a straightforward UBoxComponent that channels damage to the HealthSystem. One of the advantages of this approach is that designers can easily incorporate multiple Hurtboxes within a single character, all referencing the same HealthSystem. Conversely, the Hitboxes functions are even more straightforward, solely registering overlapping components without additional functionalities.
However, the core of the combat mechanics resides within the Melee component. I designed the attack sequence in three distinct phases: toggling on, spanning the duration, and toggling off.
Upon toggling the Hitboxes, I implemented checks for any overlapping Hurtboxes, instantly inflicting damage. Following that, I maintained the overlap trigger on throughout the animation duration, ready to damage any newly overlapped actors. Lastly, upon toggling off the Hitboxes, I made sure to clean up for the next attack.

Light/Heavy/Special attack hitbox.




Light/Heavy/Special attack in action.
To prevent multiple instances of damage to an actor within a single attack, I developed the following mechanism. Whenever damage is inflicted, I save a pointer to the affected actor within an array. This pointer serves as a check, preventing me damaging an actor multiple times. The array is then cleared during the toggle-off phase of the attack, ensuring a clean slate for the next attack.

Dash Ability
I once again established a three-part system for the dash mechanic, structured as a initial Dash function, a recursive ExecuteDash function, and finishing with an EndDash, arranged in sequence.
In the public Dash function, I initiated the dash, conducting obstacle checks, calculating its end point, duration, and other pertinent variables before transitioning to ExecuteDash. The private ExecuteDash function executed the dash itself, employing recursive calls until it reached the designated target, progressing incrementally with each iteration.
The public EndDash function concludes the dash process, triggered either upon reaching the target or by a failsafe timer. Here, I conducted variable and timer clean-ups in preparation for the next dash, broadcasted a delegate to signify completion, and initiated a cooldown timer for the dash.

Dash Ability in action.

Enemy AI
I was responsible for coding the enemies' AI, utilizing behaviour diagrams provided by our project's combat designer. Initially, the plan involved creating an AI Controller with essential functions for world perception and implementing a few functions within the Enemy Character class for executing actions in the environment. The intention was to subsequently pass these over to the designer for implementation within Unreal Engine's Behaviour Tree system.
However, due to the designer needing to prioritize the player character, I ended up taking on the engine-side work in addition to my initial responsibilities.

Given the unexpected workload that emerged a few weeks into the project, I had to adapt and find resourceful ways to program the AI. The sensory functions I developed within the AI Controller primarily fell into two distinct categories. One set focused on detecting the player and understanding its relationship with the AI, while the other revolved around the AI's perception and knowledge of the level.

The functions concerning the player are all relatively straightforward. One function checks the distance to the player, another determined if the player was within range, and a third assessed if the AI was in cover from the player. The cover check function projected a point onto the NavMesh for both the player and the enemy, then conducted a simple line trace between them. This method provided a swift and mostly reliable means to identify terrain elements obstructing their line of sight.

Debug rays showing the AI looking for cover and spheres where it has found good cover.
The second category delves into a more intricate and interesting territory. It primarily begins with two key functions: one for finding positions away from the player, and another for locating cover positions.
The first function used for identifying positions away from the player was achieved by casting rays outward from the player at variable intervals and quantity, stopping upon encountering obstacles. Following this process, the identified positions were projected onto the NavMesh, resulting in an array of suitable locations.
For the second function I came up with a novel approach. Employing a similar method as before, I reversed the process. Instead of casting rays from the player, I directed them towards the player, seeking collision points with terrain cover. Those collision points were then projected onto the NavMesh, compiling an array of locations situated on the opposite side of obstructing terrain pieces.
These functions formed the foundation for the AI's navigation within the environment. I could selectively choose a position based on specific criteria relevant to the current situation and direct the AI to move accordingly.

Additional Work
In addition to the main features I developed I also made a few smaller things.
-
Spawning, attaching, choosing and detaching projectiles for the Cutlery Wizard (Ranged Enemy) before shooting. Used to make projectiles seamlessly animate with character before shooting.
-
The floating damage text.
-
In engine customizable projectile behaviour logic.
