Skip to: main content or site footer.

AI Development: The Strings We Pull

Hello everyone, this is Liam McDonald. I’m one of the Developers at Failbetter Games. I'm taking a brief moment to share some of the exciting combat redesign work I've been doing with Paul Arendt. We've been looking over all the enemies (Agents) in Sunless Skies to update them with some of the new features that are being introduced in the latest build. We’ve just completed the fleshiest part of that work. So cast your eyes downward for a look at how exactly we go about bringing horror to your screens.

Terminology for this article:

Player – The player, that’s you!
Agent – Any monster/enemy/NPC.Actor – An umbrella term: the Player is an Actor, an Agent is also an Actor.

I’m also going to nod quickly in the direction of a crucial bit of underlying tech, the Node Graph. The Node Graph specifies the points in a scene where an actor can move – areas without terrain, basically. The logic which controls how Agents move around our world begins with the selection of a Node; we determine what Node to select by weighting all of the Nodes near the Agent. I'm going to look at how we manipulate the rules behind the Node weighting in order to achieve our design goals.

Below is a screenshot of our Combat Scene where we do a lot of our testing – you can see the Node Graph in all its glory.

NodeGraph

The full design of any Agent is lot to unpack, but I’m going to cover these main topics: Area Denial, Tells, Wrath, Passions, Provocations and Tactics.

I am not going to discuss Movement. We have a wide range of parameters available to control Agent Movement and it is an important part of the look and feel of an Agent. However it is too complicated to get into in this blog. Ultimately, the goal behind our manipulation of the Movement Parameters is to produce an agent that can perform the manoeuvers we’ve assigned it and that has fluid movement appropriate to their nature. For this blog, Movement can be considered a tool that helps improve the quality of the other areas I'll be discussing.

The screenshot below which contains the Movement Parameters.

MovementParameters
The Strings of Design


This is a concept that helps convey how a designer works to achieve their vision. The ‘Strings of Design’ can be individually adjusted by a designer to pull a design into shape. Each string is an individual piece of tech work or a game feature. I’ve already mentioned some of these strings above: Movement Parameters, Passions, Tactics. But we can also use some more common strings, such as Agent Health, Weapon Damage and Wrath (aggro). So, let’s go through these Sunless Skies strings to see how they’re used to improve combat in game!

Tells

When an Agent is about to attack, we wanted there to be signalling, so that a player has time to react. We refer to that signalling as a ‘Tell’. During the combat redesign we added a large number of Tells to the Agents' attacks. We’ll be adding even more in the future. Here are just a few:

scorntell_WM dousertel_WMl cantankeritell_WM

Area of Denial

We don’t want combat to feel static – it should be fluid,encouraging constant assessment of your position, taking into account your opponents’ movement and behaviour. To create this need for the player to move, we’ve created areas of denial – areas in which it would be undesirable for a player to remain. These are created through a combination of the Agent’s ability to reposition itself relative to the player and the agent's choice of attacks.

Areadenial
Here is a first draft of the Scorn Fluke’s attack. When the player is in close proximity, it has the ability to fire a projectile that will explode causing blast damage. We’ve created an Area of Denial ring.

It can be seen in action here:

AuraAttack_WM
Wrath


Wrath is a simple numerical value. It measures how Wrathful an agent is towards another Actor in the game. Agents are capable of gaining Wrath toward any other Actor – the player or other Agents. We do prevent Agents of the same type attacking each other. When an Agent exceeds the Wrath threshold with regards to an Actor, that Actor becomes the Agent’s opponent. Here is the Wrath Indicator in action:


wrath gain

Passions

When an Agent has no opponent, their movement will be dictated by a Passion, selected from their list of Passions. When an Agent is spawned, the Node they were spawned on is stored as their ‘Home Node’. We have designed two sets of Passions: the first allows an Agent to Wander a certain distance around their home area – if they exceed this distance, they return to the Home Node. The second set of Passions move the Agent towards the player – if this exceeds a set distance, they return to their Home Node before trying to move towards the player again. Below shows the Wander passion in effect:

home pathfinding

Provocations

Provocations are the sets of rules which can add or subtract Wrath from an Agent. There are many ways we can implement a provocation. For instance, we can have Agents that gain Wrath rapidly when the player is in close proximity. Or we can have Agents who only gain Wrath if the player’s headlight is on them.

Another powerful aspect of Provocations is the ability to have them check against certain Player states or belongings. Chorister Bees may usually be quite peaceful, but if you happen to be carrying their Nectar…

angrybees

Tactics

Once an Agent has an opponent, they will stop using Passions for their movement and start using Tactics. Provocations will still add or subtract Wrath when the right conditions are met. So, if you are able to flee from the Agent, Wrath will go down until you are no longer their opponent. But while the Agent has an opponent, its movement will rely on the Tactics.

Tactic

We score all Tactics using their Base Weight and a Multiplier. This Weighting dictates how likely a Tactic is to be selected. We use the Multipliers to increase the likelihood of a specific Tactic being chosen. If we wanted to create a Tactic that allows the firing of the Agent's Primary Weapon – perhaps a shotgun – when the Agent is close to the player, we’d add a large Multiplier to the Primary Weapon when the distance to the player is small.

Next up is Node Weighting, which governs where the Agent is trying to go when they are executing a Tactic. The Agent needs to select a Node from our Node Graph, so we have to score the Nodes so that the Agent can decide which they should pick. In the screenshot of the Close Range Flank Tactic, above, we’re saying that Nodes which are at a particular angle from the opponent (in this case behind the opponent) are weighted at, let’s say, 8. We also weight Nodes within 300-100 units from the opponent at 6. So if a Node is both behind the opponent and between 100-300 from them, it’ll benefit from the weighting of both conditions and therefore will be the most likely to be selected. The result of these weightings helps us to achieve the flanking behaviour that this Tactic is named after.

Finally, we have some optional parameters. We can, for example, have a completion condition. Maybe I want a Tactic to finish when the Agent gets within 400 units from the opponent, so that it can transition to a Ramming attack. We can also add a Timeout. Perhaps a Tactic is hard to achieve when the player is always moving. In this case, we want the Agent to try to achieve it for x seconds, then to move on to another Tactic.

Player Facing will force the Agent to constantly try to face the Player while executing its Tactic. Finally, we have check boxes which turn on Aura Abilities and Weapon Fire. These mean that it is possible for those to execute while the Agent is performing this Close Range Flank Tactic.

Chaining Tactics allows us to layer the complexity of the design. We may want to have a Tactic where an Agent starts by using ‘Approach Player’, then, after completing this, it goes straight into ‘Ram Player’. After the Ram completes, we may want it to then ‘Create Distance From Player’. The design might dictate that the Agent is only able to Ram when it follows that particular chain of Tactics. Other times it makes more sense to have many Tactics available and let the Agent select between them to create a more unpredictable flow to the fight. Which approach is best depends largely on the behaviour we’re trying to achieve and the movement abilities of the specific Agent.

Passions, Provocations and Tactics play the largest role in the AI behaviour of an Agent. Here’s an example of a completed set, using the Douser Engine:


douser

Those are just some of the strings we pull to determine an Agent's behaviour. But how do we decide what behaviour to choose for an Agent? It starts with our Agent’s Personality – we have wealth of information from Sunless Skies’ narrative. If we need more information, we can speak directly to the writers to get a better sense of the Agent we’re designing. Then we boil this information down to touch stones that we can work with. Here’s the Scrive Spinster:

Personality: grieving, bitter, lonely, wants to be left alone – unless the player has works from the Scrives’ library, because they frantically want those back.

Movement ideas: these are teasers who will attempt to keep the player at medium distance, so that they are at the mercy of their high damage, ranged attack. Movement should feel floaty and ethereal.

We can quickly translate that into behaviour that we know we can effect with our design strings. Here’s what that personality might be broken down into:

Passions: Should roam, quietly and alone.


Nodes: The Scrive doesn’t want to go face to face. It will also favour keeping distance between it and the player.

Provocation: If provoked it will attack. If it senses that you are carrying works from their library, it will attack.

Tactic: Because it doesn’t want to face the player, if the player forces that situation, the Scrive should become aggressive and attack more frantically.

This is step one of the design. We lay out the core pillars that should be present in order to translate the narrative ideas into the Agent’s behaviour. When we’re happy with that base, we keep layering, keep pulling the design strings to add complexity to the design until we reach a point where we know that the initial ideas are strongly present in both the movement and attacks.

Here’s a look at how that face to face retaliation panned out:

scrivehunt
Hopefully this has been a useful insight into part of our design process. I also hope that you’re itching to jump into Sunless Skies and experience all this new combat work. We look forward to hearing your feedback :).



Follow Liam on twitter @wgoodspeed, and follow development @failbettergames. Sunless Skies is available on Steam and GOG.