Specialization

I’ve always been fascinated by fast paced combat systems and have long wanted to create my own. As part of my specialization at The Game Assembly, which spanned roughly six weeks of half-time work, I recreated the core gameplay mechanics of Sekiro: Shadows Die Twice, a soulslike, fast paced combat game.

The project focused on:

  • Player movement & controls
  • Target lock-on system
  • Combat mechanics (attacks, blocks, parrying, and finisher)
  • Enemy AI (detection, group reactions, attack coordination)
  • Grappling hook

Why Unreal Engine?

  • I wanted to deepen my understanding of the engine and get more comfortable with it. Since it is widely used across the game development industry, learning its tools and workflows is highly valuable.
  • The reference game relies heavily on animation, and Unreal offers a robust and customizable animation system. Without an animator, I’ll have to rely on animations found online, which Unreal made easy to implement and refine.


For the first week, I focused on the player movement and controls as it forms the foundation for all other aspects of this project. Instead of developing my own character controller, I chose to use Unreal’s built-in character controller due to time constraints. Then, I started by matching the camera by comparing footage to the reference game from different angles.

For the camera lag, I experimented with Unreal’s built-in camera boom, but found it too limiting. Instead, I created a custom camera lag function that interpolates the camera location differently on the vertical axis compared to the forward and horizontal axes.

With the camera aligned, I moved on to recreating the movement feel and locomotion metrics. This include: movement speed, jumping, crouching, dashing, sprinting, and more.

Movement and camera metrics vs Sekiro

Attack, jump, and crouch metrics vs Sekiro

Player states with buffered input

To improve responsiveness, I created an input buffering system so the player inputs aren’t missed during the fast paced gameplay. Early on, I struggled to design a system that allowed smooth transitions between different actions (jump, crouch, block, sprint, attack). While I considered using a state machine, I decided against it due to complexity in Blueprints, and instead developed a more flexible, input-driven system.

When an input is pressed, the game calls TryStartAction and attempts to execute it immediately. If it cannot, the input is timestamped and stored in a buffer. Once the current action ends, the buffer is checked and the next valid action is activated. Action specific conditions (e.g., in air usability or overrides) are defined using DataAssets, decoupling actions from the underlying code and keeping the system modular and easy to extend.

For example, if the player presses jump midair, the input is buffered since the action is not allowed. Upon landing, the buffer is evaluated and jump is executed if possible. Likewise, attacks include a short cancel window, where calling EnableCancel allows buffered inputs to interrupt the current action, making the controls feel more responsive.

Before implementing combat, I created a target lock-on system, which is essential for a third person combat focused game, since it’s otherwise very hard to attack towards the enemy. I began by creating a lock-on interface, allowing any game object to be targeted and provide a unique reference point for the player. Next, I added a lock-on component to handle custom camera rotation, enabling the player to lock onto a target and switch between nearby enemies.

To detect targets around the world, the player uses a sphere collider that registers objects implementing the interface when they enter its range. When lock-on is activated, the system filters targets by minimum distance and selects the closest one based on a dot product between the camera’s forward vector and a normalized vector to each enemy. Similar logic applies when switching between targets.

At last, I implemented event dispatchers that notify the player when lock-on is activated or deactivated. This allows the character to switch between movement based and camera based rotation only when needed, rather than checking every frame.

Lock onto nearby enemies and switch to another



It was finally time to work on combat! I started by downloading one handed sword animations from Mixamo. However, the few animations I found required heavy modifications, as they weren’t designed for the fast paced combat my project required. I had to adjust their speed and timing to ensure each attack has a clear windup, a swift impact, and a deliberate recovery.

When attacking, I use multiple animation notifies to trigger key actions within the attack sequence. For instance, the sword collider is enabled and disabled during a specific window to ensure accurate hit detection. I also implemented a three hit combo system. The attack is decided by a combo index that increments if the attack input occurs shortly after the previous attack ends; otherwise, the combo resets.

In a similar way, I use animation notifies to handle blocks and parries. Notifies determine whether a block qualifies as a parry and dynamically adjust the parry window based on how frequently the player presses the block button, improving player response and timing during combat.

One-handed sword attack combo

Animation montage for the first attack in the combo sequence

Parrying and blocking attacks with custom made VFX

To handle damage, I created a damageable interface with a TakeDamage function. It returns whether the attack dealt damage or was parried, allowing the player or enemy to react differently if their attack was deflected or not.

In addition, I implemented a health component that manages key values (health, posture, is parrying, etc.). I wanted to combine both an interface and a component approach: the interface lets objects define their own behavior, while the component handles damage calculations and triggers notifications, such as OnDeath. These events can then be used by UI to update values only when they change or to disable player controls and play a death animation when health reaches zero.

After adding hit reactions, I moved on to the UI. The player UI was straightforward to set up thanks to the existing event dispatchers.

For the enemy UI, I Initially had the player manage it, pooling health bars and dynamically assigning them to visible enemies. While the concept worked, it was a over engineered solution for this project. I simplified the system by moving the enemy UI logic into the enemy base class, making it faster and easier to implement.

Enemy and player UI showing both health and posture

Deathblow: when player is undetected or the enemy’s posture is broken

Finally, I implemented the final combat piece: the Deathblow, an enemy finishing move that plays a key role in Sekiro. For enemy detection, I reused the collider from the lock-on system to notify the enemies to enable or disable their deathblow icon.

When the player attacks, the system checks if any enemy in attack range has Deathblow active. If so, the attack triggers the Deathblow instead of normal attack. The rest is about positioning the player and enemy correctly and timing their animations to make the finisher feel smooth and impactful.

Unfortunately, the finisher animation snaps at the end because it’s rotated incorrectly. As a result, it can’t interpolate smoothly into the next animation.



Different enemy idle states: stationary, wander, and patrol

After finalizing the core player movement and combat into a state I’m happy with, I turned my attention more to the enemies, which use a behavior tree. In Sekiro, combat isn’t everything. Before an encounter begins, enemies must first detect the player. They have three visual states based on their awareness: Idle, Suspicious, and Aggressive.

In the Idle state, they either stand still, wander within a defined radius, or patrol along a set route. I implemented wandering using Unreal’s EQS (Environment Query System) and patrol behavior using a spline component.



I initially planned on creating a specific behavior for when an enemy loses sight of the player. However, since this situation hardly ever occurs, I decided to leave it out and instead have them return to their idle state. Lastly, Enemies in aggressive state actively pursue and attack the player.

On player detection, a progress bar is triggered to visually indicate the enemy’s rising awareness. Once filled up, the enemy becomes aggressive and begins attacking the player. If the player approaches too closely before the bar fills, or if the enemy takes damage, it immediately enters the Aggressive state. Furthermore, the progress bar dynamically adjusts its fill rate based on whether the player is crouching, as well as the distance, angle, and height differences relative to the player.

To further improve the enemy vs player interaction, aggressive enemies also alert nearby allies, making it appear as if they are coordinating as a group. Likewise, when the player performs a finisher, nearby enemies react by jumping back in surprise. These mechanics, inspired by the reference game, make encounters much more dynamic and immersive, as enemies respond more realistically to the player’s actions.

Showcase of enemy detection from different ranges

Enemy alerts nearby allies and leaps back during ally finishers



Issues with lack of variation with enemy combat

Currently, there is only one enemy type, with its main issue being lack of variation. The enemy simply runs toward the target and then attacks within a certain range. This works okay with a single enemy but becomes problematic with multiple enemies, as they group together and behave identically.

Combat is also repetitive since the enemy has only one attack. I initially added four possible different attacks, but this resulted in a predictable pattern: the enemy would attack, wait, then attack again. In some cases, it also selected the same attack repeatedly, which resulted in an even worse experience.

I revisited the reference game for inspiration and noticed several interesting design choices:

  • Each enemy has multiple attack types depending on the distance to the player. At long range, they charge to close the gap, while at mid range, they use combo attacks.
  • No more than three enemies attack simultaneously, unless the player chooses to close the distance. The remaining enemies will back off and circle around the player.

Showcase of Sekiro: Shadows Dies Twice dynamic enemy combat

Enemy blocking player attacks and attacking based on player distance

Let’s start by addressing the issue of repetitive attacks. In the reference game, only enemies that charge at the player use single attacks, otherwise each other attack type consists of multi attack combo sequences.

In my case, I’m limited to a one handed sword combo animation from Mixamo, which is already used by the player, as well as a single swift attack animation. Nevertheless, I implemented it so that the enemy uses combo attacks at long and mid ranges, while a swift attack at close range.

Additionally, I added the ability for the enemy to block attacks, allowing them to behave more dynamic in response to the player’s actions.

The next step is to solve the issue of multiple enemies running towards the player. I started by creating an EQS which allows me to define a radius around the player with multiple points, where the enemy can wander to when not attacking.

It was added to the behavior tree and used if the enemy is within a certain range of the player, if outside of this range, it will instead run towards the player until it enters into the range.

Enemies circling around the player

Limited amount of enemies attacking simultaneously

The last step was to get the enemies to coordinate with each other. After some research, I decided to implement an attack manager that controls how many enemies can attack simultaneously.

It prevents all enemies from attacking at the same time. Instead, each enemy must request permission to attack, otherwise continue to wander around the player.

Unreal has multiple types of singleton patterns, I chose to go with UWorldSubsystem since its lifetime is only per level, perfect for what I wanted. Since the system cannot be created directly in Blueprints, I had to create the class in C++ and then expose its functions so they can be used in Blueprints.



Before wrapping up the project, I wanted to add a grappling hook as a final feature. It works similarly to the lock-on system, detecting targets with a sphere collider and selecting the closest one to the center of the screen.

When activated, an Unreal timeline moves the player along a trajectory between a start and end point. I initially experimented with a force based approach but switched to a more predictable timeline driven solution for better control along the trajectory and landing position.

Grappling across different heights

Before and after the deathblow camera sequence

Finally, I wanted to polish the finisher, as it was missing some much needed game feel. Having previously worked with Level Sequence, a cinematic tool for creating sequenced events, during my sixth game project at TGA, I knew it would be a great fit for this feature.

I began by creating a Level Sequence and setting up a simple camera animation at world origin, which I can then offset to match the player’s location. After that, I added blend in and out to make the transitions smoother.

However, I had issues with the blending, as the camera snapped during transitions. After spending more time than expected, I found a solution on the Unreal forums from someone with the same issue.



To summarize, this has been a super fun experience, and I’m proud of what I accomplished. It’s always rewarding to create something playable that you can always revisit and experience again.

I’ve learned a ton along the way, including multiple Unreal tools: Timelines, Event Dispatchers, Level Sequence, EQS, DataAsset, Widgets, and much more. I had never used visual scripting this extensively before, and it proved to be very powerful. It is very easy to prototype with and iterate on. However, it can become messy very quickly, and not all features are exposed through it. In the future, I will certainly experiment more with the C++ and use it in my workflow.

Animations are something I had underestimated going into this project. They play a much larger role in the player feedback than I had initially expected, from understanding what is happening to the pacing of the game. I had to spend more time than expected refining animations from Mixamo to better match the feel of the reference game.

There’s still plenty to refine in the project to reach the same level as Sekiro: Shadows Die Twice. For instance, I would have loved to improve the enemies by implementing enemy avoidance, improving the attack variety, and creating more natural transitions between enemies as they move in and out of combat. And certainly add more animations to further improve the overall quality and feel of the game.

Credits: Katana Model