They have six eyes, bro.

During the SparkInSpace interview (starting at 2:03:00 in the VOD), Spark reminisced about experiencing how good the Ai is during the Mortal Rite Playtest and Demo.

Clip with sound from the SparkInSpace interview.

In Mortal Rite, we have Ai that ranges from fodder enemies that are only difficult when in large number to Ai that, one on one, are supposed to be as challenging as fighting against another player.

In previous talks with Spark, he pointed out that fighting against the Sword Knight is like fighting against a real player in PVP. The Sword Knight will parry, dodge, retreat, do combos, etc. Spark didn’t realize how deep the Sword Knight Ai went until he spent a while fighting it in the arena.

Sword Knight

In the future, I will take the time to go deep into how the Ai works because we’ve put a lot of work in it and it would be cool to do that. But today I am going to go through making the Sword Knight Dodge work in the new project.

One of the rules for the new project is that anytime we can make an ability (everything is an ability: jump, dodge, attacks, etc.) work for both the player and an enemy, we should. This keeps everything simpler than it would have been and means that if there is a problem with an ability not working, and we fix it for the player, it would also be fixed for the enemies. In the previous Mortal Rite project, we had specific Ai Abilities that players could not use and we ran into issues where we’d have to fix issues in both places. Not great.

The Goal

  1. Allow the Ai to determine which way to dodge based on obstacles in the world including other enemies, players and structures.
  2. Be able to control which way a specific Ai can dodge. The Sword Knight specifically wants to dodge away from its target (backwards, back left, or back right) while the Axe Knight wants to dash towards its target (forward left, forward right).
  3. Build this functionality into the dodge ability that the player uses so that we can use the same dodge functionality and systems for both the player and the Ai.

#1 – Which direction to dodge

Translate dodge direction and current location into cardinal direction.

The player uses current acceleration in order to determine which way to dodge. The player’s current acceleration is provided by the controller input(s) that the player is making. We don’t have that from the Ai. The dodge ability uses GetCardinalDirectionFromTransformAndDirection() to translates the player’s current location and current acceleration (in the form of Dodge Direction) into a cardinal direction: North, Northeast, East, Southeast, South, Southwest, West, Northwest. Basically, 8 directions. The player will need to choose which way they dodge using their controller input and then the dodge ability picks up on that while the Ai needs to choose a direction, translate that into the same cardinal direction, and then use the same player dodge logic.

The way we chose to implement this for the Ai is to have the same dodge ability run an Environmental Query via Unreal Engine’s Environmental Query System.

EQS_Q_Dodge Query


The EQS_Q_Dodge query simply does the following:

  1. Generate a circle with 8 points (one for each of the cardinal directions) evenly spaced around the Ai with a radius equal to the distance that the Ai can dodge.
  2. Test for the points being pathable so that the Ai can reach it. If a point is not pathable, reject it.
  3. Test for the points being in line of sight. If a point is not in line of sight, reject it.

The results from this EQS Query are the locations that the Ai should consider dodging towards.

EQS Dodge Query results: Yellow: Locations that passed the EQS Query Tests.

#2 – Control which way the Ai can dodge

Once we have an array of unobstructed, in line of sight locations that the Ai can dodge to, we translate these locations into directions. Then we use location of the Ai and those directions in place of the current acceleration that the player used to generate the cardinal directions.

Iterate through the returned locations, create the directions, use the directions to get the cardinal directions.

At this point we have an opportunity to filter out the undesired directions. By defining a whitelist of cardinal directions for each Ai it allows us to only accept the desired directions.

Dodge ability settings for the Ai
Only accept the directions that are in the ‘Ai Allowed Directions’ list.

Accepting all directions allows all directions to be randomly selected.

EQS Dodge Query results – Blue: possible dodge locations; Green: Chosen Dodge Direction (south in this case)

Changing the allowed directions list to only allow South (back), Southwest (back left) and Southeast (back right) results in the Ai only being able to dodge in those directions.

Sword Knight’s proper dodge settings
Yellow: EQS Locations; Blue: Possible dodge locations

The EQS Query takes care of obstacles so that if a location is obstructed or not in line of sight then that location is rejected.

Wall causes potential points to be rejected.

Putting everything together and playing as the Sword Knight with debugging on we can see everything working together to give the functionality that we desire.

Note: animations, timings… pretty much everything is still being worked on for the Sword Knight’s dodges so that it’s nice and smooth, but this illustrates the functionality.

Playing as the Sword Knight to test dodge functionality.

#3 – Put everything into the base dodge ability

Combining everything into the base dodge ability and adding in a check for playing as a player so that we only use the Ai logic when an Ai uses the ability ends up looking like this:

Player Dodge ability with the addition of the Ai Logic necessary to support use by both Players and Ai

So, there it is. The rest of the story is having the Ai recognize when it should use a dodge ability, which is a bit more complicated and something that will have to be covered later.

Just Ai Things

This week I worked on making Placers easier to work with in the editor.

What was wrong with them? I’ll tell ya!

Placers spawn in the Enemies as needed in the Editor, but previously needed an external actor – Enemy Manager Helper – to setup the enemies. Problem this caused was that anyone working with levels and enemies needed to put in a placer and an Enemy Manager Helper in order to setup levels. That’s extra steps. As many times as we go over the steps to setup enemies in a level it’s not something that we get to do daily and when people don’t do things regularly or it’s not their focus, they forget.

The future was not having to do anything more than have placers.

Placer with a Sword Knight in a test level.

Enemy Manager Helper used to have all events that able to be called in the editor to do the following: Setup Enemies, Delete Enemies, and Validate Enemies.

Setup Enemies deleted all of the enemies in the level to make sure that everything was clean and then setup all of the enemies that the placers needed in the level. But, due to silliness, Setup Enemies did not clean up soft references properly (Oops). Setup Enemies needed to be clicked on manually by a developer each time you made a change to any enemies in the level. Tedious.

Delete Enemies deleted all of the enemies and was used by other functionality – like Setup Enemies – to clean out all of the enemies in the level currently. Problem with this was that it didn’t properly clean up soft references between placers and enemies. That’s bad.

Validate Enemies validated all of the locations of the enemies and color-coded the placers based on whether they were in good places or not. A similar function – Validate Spawn Location – was used to validate a single placer.

So, that’s all boring, but that was to illustrate the previous setup and show that there was room for improvement.

I ported all of the Helper’s functionality to the Placer itself. Put all of the logic in functions instead of in events and updated everything so that everything used the new functions. While I was at it, I also cleaned up the Patrol Point functionality so that everything that someone needed to use that was previously spread out between Placers, Helpers and Patrol Points were now just on the Placers (Patrol Point functions were left on the Patrol Points too because that’s convenient). Looks like this:

Placer, Helper and Patrol Point functions on the Placer itself.

I also cleaned up the names of the functions to just be better.

We now have:

  • SetupEnemies() – to setup all enemies in the level
  • DeleteAllEnemies() – to delete all enemies and clean up the soft references, but to leave the Placer’s configuration in place so that SetupEnemies() can be used directly after if needed.
  • SetupThisEnemy() – Setups up just this placer’s enemy.
  • DeleteThisEnemy() – Deletes just this placer’s enemy.
  • Validate() – Validates all spawn locations for all placers.
  • ValidateThis() – Validates spawn locations for just this placer.
  • AddPatrolPoint() – Adds a new patrol point.
  • DeletePatrolPoint() – Deletes a patrol point.
  • CloseLoop() – Closes the linked patrol points into a loop so that the enemy will loop through the patrol points.
  • OpenLoop() – Opens the linked patrol points so that they are no longer in a loop.
Placer Details View with the clickable functions via Call In Editor.

Much cleaner. Now anyone can just drag out the placer, choose the enemy they want and click SetupThisEnemy() or SetupEnemies() and be done.

But can we go further? That’s still a lot of steps to setup an enemy. What if just choosing an enemy automatically set it up? And what if anytime the user had to choose from a long list of tags they only saw the tags that were relevant?

Placer settings.
Filtered list of Enemy Tags to choose from instead of all of the tags.

Choosing a valid tag from the filtered enemy tag list will now automatically setup the enemy and clearing the tag will automatically clean up the enemy. Users no longer need to user SetupEnemies() or DeleteAllEnemies()!

PostEditChangeProperty() – Very useful.

This was done by hooking into PostEditorChangeProperty(), which is called anytime a setting is updated. Setup the logic that you need for each case, and then you’re set!

In our case here, I wanted to identify anytime the Enemy To Spawn tag changed and then call SetupThisEnemy(). SetupThisEnemy() is a Blueprint Function so it had to be called in a fancier way than just calling a native C++ function. Since this is Editor Time stuff and performance doesn’t matter, there’s no real need to move the function to C++ in the form of a BlueprintNative or BlueprintImplementable function.

PostEditChangeProperty() implementation where we call SetupThisEnemy in BP from C++.
Easy Enemy Setup

At the end of the Placer changes we have a much cleaner user experience that will save time and with better validation because there are no bad soft references.

All the small things

This week I was running around fixing a bunch of things all at once.

Networking

So since the editor let’s us test under less then desirable conditions we’ve started testing what systems break down under lag.

fancy network testing options

Because of this one of the things we found wasn’t reliable was damage, which I think we can all agree is pretty important. If you take a swing at an enemy and they ignore your damage, no one’s gonna be happy.

There were a few things that could be causing this, one of them which I suspected was causing this, is your weapons only count when the server/host sees a hit happen. While this is great for preventing cheating, if feels pretty terrible and isn’t exactly great for performance.

If we continued down this path it would mean the host would need to tick everyone’s animations perfectly otherwise the game feels unresponsive.

So how do we get around this so combat feels fair and it’s still hard to straight up cheat?


Client side hit detection

So I took a look at how the Lyra project was handling similar things, like shooting/melee since its pretty similar.

Turns out they have a nifty way of sending hit data were it can still feel responsive, while still sending it to the host to verify you didn’t just hit someone from across the map.

So I extracted the relevant parts, and now we can send hit data from your client. Yay!

classic target data

Once the server gets the data, it double checks to make sure the hit was even possible, and then applies damage. Nice!


There will be Blood

So up next was handling hit effects. Up till now we’d been using the Lyra defaults which gave us sparks anytime we hit characters.

So Anthony ported our blood emitters from our previous project, and we spawn it anytime characters are hit.

(Positioning was easy thanks to last week’s post)

One of the funny side effects of this was since effects are triggered from damage, destructibles were bleeding as well.

There’s blood in my metal

I recalled something that could help me out with this. As I was reading through the ability system source code, (You know, a thing normal people do) I came across the Gameplay Cue Translator.

Turns out this handy little class can translate gameplay tags from one form to another, in this case letting us mutate the more generic tag to a more specific one.

…What?

you (probably)

So the vfx tags we send across the system tend to look something like this

GameplayCue.Object.DamageTaken

That’s great and all, but like you saw above, that means anything that takes damage will play the blood effects. If we use the translator, I can add rules that depending on what we hit, it’ll update what tags get sent. So now it’ll end up sending either:

GameplayCue.Character.DamageTaken

or

GameplayCue.Destructible.DamageTaken

Which lets us handle effects differently per object type. Nice!


Back to blood though. The little squibs of blood are cool, but we also want the decals to show what a mess you’re making. I started to add decals directly beneath the enemy that was being hit, and it looked ok, but kinda weird considering the blood was gushing out and appearing –directly- beneath your enemy.

I tried adding some traces to kind of figure out roughly where we should place the blood, but it was kind of a mess. Then it hit me.

WHAT IF WE ADDED A BALLISTIC TRAJECTORY TO DETERMINE WHERE THE BLOOD SHOULD FALL?

Luckily this was actually super easy to add.

behold. cannonball technology

With this we end up nice a nice trace for world geometry that lets us know where the blood would have fallen according to a given velocity.

oh yes.

Now we add a placeholder decal to test that it’s rotated the correct way

The decals are aligned towards the character, which helps the blood to look more aligned. It’s definitely not perfect, but it should get us most of the way there.

So replaced with the actual decals, it looks more convincing


Finally the last thing I worked on this week was tweaking how we handle fire effects. Rod has been handling most of the effects work, but this particular effect was giving him some trouble so I thought I could help a bit here.

Fire effects are going to be a pretty important part of Fia, so I went ahead and tweaked how the fire sprites behave when in motion. The most important thing here was to give it left and right motion, which we can see the beginnings of here.

whoosh

I think it came out alright.

The last thing I happened upon this week was batch importing animations.

Since we’ve been going through and cleaning up animations, I wanted a way to batch import any animations that we exported from Unreal into Akeytsu (our animation software)

Typically the whole process sucks since Unreal exports animations individually, and Akeytsu accepts one file at a time, so when you’re dealing with 100 animations, going through that many dialogs and having to manually rename each one since Unreal exports the name as “Unreal Take” no matter what, I figured there had to be something I could do to make this smoother.

Turns out Blender let’s you batch import files, which does solve one of the problems, but the naming problem was still there.

I wondered if I could edit how Blender imports .FBX files.
Turns out, the importer is a python script.

How very editable…

Me

So all I had to do was find where it handled the names, and replace it with the name of the actual file.

Who knew I’d be doing this today?

But with that done, I was able to import the animations in minutes instead of hours.

yay!

Anyway, that’s all I got for this week. See ya’ll next time.

Destructibles

Last week I talked about how Level Transitions allow us to change anything in the level either at level load time or, if a loading screen is okay, once a level has been loaded. We can add a wall, have a bridge that was standing be destroyed, change weather or theme, or anything that causes the player to have to find a different path through the “same” level. But level transitions don’t replace having parts of a level be destructible.

Today, I’m going to go over our Destructibles.

Destructibles are objects in the game that can be destroyed by walking into them, sprinting through them, rolling or dodging into them, or by attacking them. The goal of destructibles is to make the world feel more alive, solve a few gameplay needs, and to just be fun. It’s fun, either intentionally or accidentally, to break a destructible and see the chaos unfold.

Statues not as strong as they look.

We like the idea of cover that is destructible so that taking cover from the Heavy Archer on the bridge in the playtest is possible, but you can’t camp there for too long.

Cover can help a lot, but you can’t camp forever.


We have also used destructibles to delay the player from getting from place to place.

Hey! That wall has cracks in it!

Destructible Implementation

In Mortal Rite, destructibles are actors that have one or more static meshes that, based on criteria, can be broken. Upon breaking the destructible a Gameplay Cue is set off and the destructible actor is deleted (deleted here means that the actor is no longer visible and no longer has collision). A Gameplay Cue is a mechanism for easily displaying visual effects for multiple players. The Gameplay Cue knows how to properly display the visual effects for the destructible’s mesh or meshes and can be referenced easily using a Gameplay Tag.

Base destructible settings.

Destructible settings available to a developer:

  • Lifespan: Time the destructible lives once it is broken.
  • Tags Must Have to Break on Overlap: Tags that must exist to break when something overlaps the destructible.
  • Tags Break on Overlap: Tags that will break the destructible on overlap.
  • Gameplay Cue Tag: The Gameplay Tag for the Gameplay Cue to use when this destructible is broken.
  • Lock on by Player: Allows this destructible to be locked on by a player.
  • Lock on by Ai: Allows this destructible to be locked on by an Ai/Enemy.
  • Startup Tags: Tags that identify this object in the world. In this case, this is a destructible, and we can query for objects with this tag when we want to find destructibles within an area or in the level.
Bookcases

Ways to break destructibles

  • Overlap: When a character overlaps with the destructible, a check is done to see if the character meets the criteria to break the destructible. The first check is for Must Have Tags if they exist. If the character that overlapped with the destructible does not have all of the Must Have Tags set on the destructible, then the destructible cannot be broken on overlap by the character. The second check if for the Tags Break on Overlap to see if the character has at least one of those tags. This means that a developer has the ability to determine exactly what can break a destructible. Developers can create destructibles ranging from a destructible that will break if anything overlaps with it, to a destructible that will break only if Shold overlaps it while specifically wearing his Rock Armor and sprinting, to a destructible that will only break if a Boss overlaps with it. Very flexible.
  • Damage: Destructibles are also on a team. Teams is how we control whether or not something is hostile to any entity in Mortal Rite and only hostile entities can do damage to each other directly (e.g.: sometimes AOE damage doesn’t care about teams at all). If some entity is on a different team, we consider those two entities hostile to each other, and this holds true for destructibles as well. Destructibles default to team 69 (for obvious reasons), which is a team that is hostile to players and, even if the enemies are on different teams allowing them to attack each other, is a team that is hostile to enemies. Destructibles can be setup to have any amount of health so that we can control how easy it is to break them with damage. Sometimes you want to delay a character from entering an area by using a high health wall and sometimes you just want a destructible that doesn’t break on overlap, but will is the player attacks it once.

Other Use Cases

Shold’s rock abilities use destructibles that have their team set to the owning Shold’s team so that enemies can target them and, after a bit, break them, but friendly players cannot break them or target them.

Level Transitions

Level transitions are what I am calling the system that replaces most of the previous systems and logic used to determine what gets loads dynamically in a level. Specifically a component that is globally accessible called a LevelTransitionComponent (LTC).

The LTC manages Data Layers that are part of Unreal Engine 5’s World Partition system. LTC allows level designers to setup configurations for each map that define the following Data Layers:

  • Level: Additional terrain or geometry in the map to meet specific needs. This could come in the form of a wall that blocks access in a specific configuration while another configuration would have no wall and allow access to meet the needs of another configuration. Note: This does not replace destructibles in levels that players can interact with within a level. Destructibles still exist. (Probably talk about destructibles next week).
  • Theme: Global Lighting, Weather
  • Items: Item layouts specific to a configuration. Allows dynamic item layouts.
  • Enemies: Enemy layouts specific to a configuration. Allows dynamic enemy layouts.
NLT: Test Clip Configuration

Level designers may create any number of configurations for each number of players in a level.

Data layer transitions

Above is a clip from a test level of the data layers transitioning between unloaded, loading and activated.

NLT: Base

When a level is loaded, the LTC shows the loading screen, determines which configurations are valid for the level and the number of players. The LTC then uses a seeded stream to determine which configurations to load as needed. Once the configuration is chosen, the LTC replicates the needed information to any attached clients (for multiplayer) and waits for the clients to report that they have loaded the Data Layers needed for the chosen configuration. It’s necessary to know when all clients have loaded the configured data layers so that all clients can have their loading screen hidden at the same time.

NLT: Living Trees

LTC has other helper functions such as a function to load the next configuration and a function to reload the current configuration and Reload Current Configuration that are exposed to Blueprints.

NLT: Dead Trees

Below is a video showing the Nanite Lumen Test level in a very unfinished form. For this clip the NLT level was setup with 3 themes (Cloudy, Thunderstorm, and Dust Storm), and 3 level layers (blank, dead trees, and living trees). Using those layers the following configurations were made: Cloudy/Blank, Thunderstorm/Living Trees, and Dust Storm/Dead Trees. The clip shows Dawksin in a configuration that has a wall blocking him until he uses a trigger to cause LTC to load the next configuration. By the end of the short clip, Dawksin has gone through all 3 test configurations.

In a real level that players will be going through, they would be presented with a combination of themes, level layouts, enemy layouts and item layouts that would not repeat until all available combinations have been used at least once.

The clip is kind of a quick test of the LTC using real assets and it might not look very impressive, but it has helped iron out a lot of bugs.

Current known Issue: First level theme load causes shader compilation which delays the level from being lit even with sizable delays. Hopefully have a fix for this soon.

Charge it up

This week was all about adding in Dawksin’s Hunters Charge ability.

While relatively straight-forward there were a few hiccups that were a tad unexpected. The idea of the ability is the tap version lets you blink towards an enemy and strike, applying some stacks. The hold version lets Dawksin pass through enemies dealing damage to anyone in the path.

This’ll be no problem, I can have it done today.

Me

But he did not have it done today


Step 1 was getting the animation back in there, but from the retargeting fiasco, Dawksin’s legs were punching through the floor. On top of that, looking at the animation with fresh eyes, it didn’t have the strength to show he was hitting hard. So I reworked it a little, and had Anthony polish it up.

Not so great
Look at that stronger ending pose!

So cool, with that we can dump it in the engine, and wire it up to play!

Which technically works, but we’re missing the bits that tell him to move forward and restrict input during the move.

So here’s were we get to add our anim notifies that tell him how to move.

What’s an anim notify?

a curious reader

Look at you, asking all the right questions.

These are little tags with data we can throw onto an animation, that say “Hey do something at this point in the animation!”

So for example:

This is more or less what it looks like for hunters charge

So how do we break this down?
Ignore the long green bar, thats just the animation itself.

  • Light Green: Prevent additional abilities from activating
  • Gray: Add some flags on Dawksin.
    • Ex: 1st one speeds up his rotation in case you want to change your aim
    • Ex: 2nd one locks your rotation so you can’t make Dawksin spin in place while moving.
  • Darker Green? (I made too many things green) : Tells Dawksin to move
  • Red: Damage windows. One for each blade.
  • Blue (Beginning): Lets you change your rotation even if you’re locked onto a target
  • Blue (End): Ends the animation early if you’re trying to move.

We do put a lot of thought into how each ability feels, so all these notifies are there to make sure it feels good to use. This is also how melee attacks are built.

So if we dig into the movement notify, we can see a ton of options for how to make Dawksin move. In this case, we’re making him move 1000 units forward over the length of the notify which is like 0.13 seconds.

So now our move ends up looking like this:

eyyyy makin progress

But that’s not quite a blink is-

ADDING IN THE BLINK

The blink needs Dawksin to disappear, which is no problem, we can make him do that.

It would be pretty swell, if he came back though.

Lets add his weapons to the mix, and make him re-appear. I think that’d be for the best.

And with that, we have the basis for the tap portion complete.

That doesn’t look very good.

The visual effects connoisseur

VISUAL FX

Here we can add some flair to communicate when Dawksin should be doing damage. In the anim notify windows above, we defined when Dawksin was doing damage with each blade.

That’s fine and all, but we need to –SEE- when he’s doing damage.

So here we can add weapon tracers, courtesy of Rod.

Um… while that looks kinda cool, the tracers are not really showing the path of his weapons. Lets look into that.

So it turns out there’s a bug in the stock effect attachment code from the engine.

Here the tracers are being attached to each weapon bone, which is great, however, the code looks at that and says “ah yes, lets attach it, AND lets take the location of the bone, and offset it permanently”

I can’t override the behavior since we’re using a binary install of the engine. So you know what that means.

We’ll re-write all of it.

A sad Alex

Not my favorite thing to do. I typically avoid this as much as I can, but in this case, we attach a lot of effects, so this would affect a ton of particle systems.

A few hours later we now have our own attachment code with all the same bells and whistles. In addition, we can now specify our own custom offsets in case things don’t line up perfectly.

oo how fancy!
Hey that looks right

We’ll tie it all up with the vfx of his cape showing when the blink starts and ends, and some tracers to show the path he traveled.

So that’s most of the ability complete.

From here we start taking care of the edge cases. Anthony showed last week how we could have attacks that take into account when Dawksin stows his weapons, so I added a new variation for when his weapon is stowed.

And to speed things along, I took the same principles from here and made the Hold version.

The main difference was his blades don’t really do anything here, its the path he travels along. I take a snapshot of where his location is, and when his movement is finished, we trace a big ol box to determine who was damaged.

And that’s all I had time for, see ya’ll next week.