“interest sources”, contextual interactions for NPCs in Sleight of Hand

I’ve worked with a few “target-based” contextual interaction systems for NPCs on different projects, they’re one of my favourite things to make, and lately I’ve been making one for Sleight of Hand, a stealth game I’m working on at RiffRaff, deets of which I shall here now divulge. Everyone calls these objects something different: at Wolfeye and (I think) Arkane they were Distractions, I’ve also heard Smart Objects and Chores, but I call them Interest Sources on account of I first read about them in a technical article about Mark Of The Ninja while working on The Siege & The Sandfox, whose interest system I also made.

The idea is that, compared to other AI behaviours, these interactions are “inside out”, in that everything particular to the interaction lives on the object being interacted with (a noise, a chair, an alarm button); the “brain” of the NPC only “knows” how to use interest sources “in general”. This confers design and workflow riches beyond imagining.

I approach this in a way that I was taught to call “data-driven”, a term with apparently many meanings, but which here means “most of the behaviour is expressed through data rather than code”. This means you can get meaningfully different behaviour out of your NPCs in response to some new stimuli without touching the AI code or, in most cases, any code at all. If you’ve ever been intimidated by how responsive something like Hitman can seem, with all its intricate reactivity and the amount of implied work, something like this is often the secret sauce: a bunch of seemingly different and separate things are secretly pretty much the same thing.

In Sleight of Hand’s system, it’s possible for an interest source to override the default behaviour, but 90% of them do not. For instance, the AI takes the same path to react to the sound of a bottle breaking on the ground as when finding broken glass on the ground because a bottle broke here when you weren't around to hear it or following a blood trail, but the resulting behaviour can vary pretty substantially. There are limits to recyling the same behaviour with different parameters, but as long as we only ever build something new once we exceed those limits, we’re in pretty good shape to add complexity sustainably.

Here’s what the UI looks like at the moment:

An interest source can be spawned in the world at any time as an InterestSourceActor (mostly just a container for an InterestSourceComponent). When spawned, the interest source does nothing until it’s Perceived by the right number of eligible pawns. Eligibility is subject to a lot of checks, mostly defined here – tags, alert levels, etc. The UI above is used to view and edit the actual Interest Data the ISA is spawned with. This is a lump of information about things like:

  • Priority – any interest source with a higher priority can interrupt one with a lower priority
    • Tangibility – does this interest source represent something tangible? If not, we can get distracted from it by a new, identical interest source (eg: footsteps running down a hallway – we always want to investigate the most recent one we heard!)
  • How/whether the spawned interest should be picked up by the perception system (sound, sight)
  • Whether/when the spawned interest can spawn other interest sources, for chained behaviours
  • Whether to override the InterestSourceComponent class (for bespoke behaviour, rarely needed)
  • Lifespan (how long can this interest go unnoticed before it dies – for a sound, probably only a second)
  • Criteria for who’s eligible to notice this interest source at all, how many, and in what role
    • more on “roles” in a minute
  • More data defining the nature of a role – tags, animations, dialogue, etc
  • Various generic information like tags to assign or check for
  • And Much Much More

Once perceived by enough eligible pawns, the ISC kicks off, the NPC’s StateTree pulls this data, and we move through a few generic States that early-out if inapplicable to the particular interest source. Interests are immediately canceled if their requirements suddenly fail (eg: our alert state ascended beyond the interest’s max), and all of the interest source’s events (Activate, Interact, Cancel, Complete, etc) are bindable from elsewhere.

We haven’t used it yet, but this is also where you’d add a custom role if you wanted to jump into more elaborate set of behaviour as part of an Interest.

As with the quest system I described in another post, this data-centric approach keeps your code and maintenance burden from ballooning, while also explicitly supporting the ballooning of your design aspirations. But even cooler is what it makes easy by making it generic, like: elaborate behaviours coordinated between multiple NPCs at once. In SOH’s system, we have a concept of “roles”, where multiple NPCs can participate in an “investigation” of an interest source. Each role can contain different behaviour. A simple example might be:

  • Three NPCs hear a noise
  • They all turn to it
  • One is the Leader, who gives the order to investigate
  • One is the Investigator, who grumbles acknowledgement and moves to the interest
  • All others are Bystanders, who participate in the conversation but otherwise just look on

In a stealth game, obvious examples are things noises, corpses, blood decals, and doors that have been left open by the player, and these can be thrown around pretty effortlessly this way. DoorLeftOpen, for instance, is just “the door spawns an interest source when the player opens it and deletes it if it closes again”. DoorLeftOpen can even contain the behaviour of the NPC closing the door again. Less obvious examples might be things like kicking a table over during combat to use as cover, or, when idle, a group of guys sitting around playing poker. We even support individuals breaking off from one interest source to investigate another without canceling the original, so in that example, you could distract only one person from the poker game without everyone suddenly standing up, but if you distracted all but one person, the remaining person would also stop playing.

The Piss Test

A scenario I’ve found useful to expose a lot of edges cases is a guy pissing against a wall and leaving a puddle of piss. Support this scenario with no aberrant behaviours and you are doing okay.

Firstly, pissing is itself an interest source: we want to pre-determine the locations where a guy might take an opportunistic piss, so we place the interest Urinate. An NPC who is nearby to Urinate and is in an idle state, with nothing better to do, lets Urinate know that it has been perceived. No guy should piss twice in the timeframe our game is dealing with, so Urinate adds the tag HasPissed to the user and is predicated on the absence of that tag. Since Urinate has a MaxParticipants of 1, it kicks off immediately: our guy walks over and starts pissing. Pissing is simply playing a pissing animation, which includes a particle effect which spawns piss decals. So far so good!

If a second NPC sees this NPC actively pissing, we want them to react with disgust, so as soon as the pissing begins, Urinate spawns the interest UrinationInProgress. UrinationInProgress will naturally remove itself when its parent interest concludes. SpawnedInterestsInheritHistory here means the pisser will be ineligible to perceive the spawned interest sources, because he already knows about them – “do not cite the piss to me, witch”.

We also want any NPC who isn’t around at pisstime, but who might happen by later, to be able to react to the resulting puddle of piss, so OnComplete, Urinate spawns FindPiss. Now any NPC who comes by who isn’t dealing with something more important can notice the piss and go “Fuck’s sake, who pissed on the fucking wall”. Because these piss-related interests all have a low Priority, if anything more important comes up, the NPCs involved will abandon their interest in piss.

Once you have the hang of this, you find that it’s very easy to create reasonably complex new behaviours without new code, but more often the boon is creating simple-but-specific responses to specific player-involved stimuli: if the player makes a bullethole, we can have someone comment on the bullethole. If a player disarms a tripwire, we can have the NPC repair it and become suspicious. If it’s this easy to create interesting reactive behaviour, you find you do it all the time: if I want to make the player create muddy footprints when they step through mud and have that trail be followed by NPCs, I can do that in literally a minute. In SOH, we’re using them to let guards crouch down to investigate hiding spots, search closets, follow blood trails, yell for backup, telephone for backup, answer ringing phones, wake each other up when knocked out, and more.

This is all even better if you have an empowering conversation system to work with (we use SUDS), because it means interest sources can contextually kick off entire conversations between their participants. Our implementation in SOH lets us address the participants by role, so this works:

Something to consider, also, if you implement a system like this for your NPCs, is whether to use it directly for your player interactions as well. Weird West‘s “Distractions” manage most interactions by NPCs with the environment, from drinking at a bar to walking around town lighting the streetlamps before sunset, but many of them are also used by the player – an NPC can use a hand pump to get water, but so can you. Arguably, the “playing animations while syncing up with something in the environment” part of interest sources ought to be a secondary system that the interests interact with, and I may yet split that out for SOH, but it never happened with WW.

If this post was (or was not) useful to you, consider putting money in my patreon!

Comments

Leave a Reply