7/24/2014 – Creature AI Part 2

The Creature AI upgrade stretch goal is now 100% complete. In addition to the behaviors I blogged about earlier we now have companions, stealth and behavior voting.

Companion AI

You can tame animals with their favorite food. If a creature sees you with their favorite food equipped they’ll wait and watch you. If you manage to approach without scaring them – timid creatures will bolt if you do anything but crouch and move slowly – then you can tame them with a context skill. They’ll eat the food and follow you around from that point on.

If any hostiles attack you, they’ll attack the hostiles on your behalf unless they are Timid and spook. If they don’t have hostile capabilities then they’ll just follow you and look at things they find interesting. If the animal has at least average intelligence (the range is ‘Stupid, Average, Smart’) then you can instruct it to wait for you. But it will only wait as long as its short-term memory lasts.

The best part? None of this is hackey or kludgey – it’s all done using the Motile script, two WIScripts (Tameable / Tamed) and a basic Skill script.

Stealth

Creatures can now ‘hear’ IAudible objects within their hearing range and ‘see’ IVisible objects within their view distance and field of view. IAudible / IVisible objects can influence the effectiveness of the creature’s seeing and hearing by supplying multipliers for these ranges. So if a creature sees the Player (which is both an IAudible and an IVisible) and the player’s combined stealth skills return a 0.5 multiplier on any creature’s visible range, then the creature fails to see the player at any range greater than half its visual range. Separating the visual / audio makes it possible to mix and match stealth skills – Eg the Silent Stalker skill will decrease your audible range while the Hidden skill will decrease your visible range. And creatures who have bad eyesight can have great hearing & vice versa.

By design creatures can’t identify anything based solely on sound, but they can hear just about anything including the rustling of leaves and bushes. If they turn to look at the source of the sound, then they can identify what it is.

Here’s a top-down screenshot from Unity where I’ve turned on visible field of view / audible range for players & creatures:

It’s nothing fancy – there are some really amazingly sophisticated stealth engines out there – but it gets the job done. The only unanticipated downside of all this is that the stealth skills make it harder to train animals, since they have to see you holding the food and a narrowed field of view / reduced visible range makes it harder to get their attention. Not really sure how to fix that one.

Behavior Voting

To keep all these WIScripts (Timid, Aggressive, Hostile) from contradicting each other I’ve created an anonymous voting system. Each Creature has a CollectiveThought object – they can collectively vote on how to react to what they’ve seen. Whenever a Creature is informed that they’ve seen an item of interest, it loads it into its CollectiveThought and invokes its OnCollectiveThoughtStart action. Any WIScripts subscribed to that action can vote on how to react to it. It can also vote multiple times if it’s really serious. Here’s how the TImid script handles a CollectiveThought:

Code: Select all
namespace Frontiers.World
{
public class Timid : WIScript {

public Creature creature;

public void OnInitialized ( )
{
creature = worlditem.Get ( );
creature.OnCollectiveThoughtStart += OnCollectiveThoughtStart;

...
}

public void OnCollectiveThoughtStart ( )
{
IItemOfInterest itemOfInterest = creature.CurrentThought.CurrentItemOfInterest;
switch (itemOfInterest.IOIType) {
case ItemOfInterestType.Player:
default:
if (creature.IsFamiliarWith (Player.Local.ID)) {
creature.CurrentThought.Vote = ItemOfInterestReaction.IgnoreIt;
} else {
if (Player.Local.IsCrouching) {
creature.CurrentThought.Vote = ItemOfInterestReaction.FleeFromIt;
} else if (Player.Local.IsWalking) {
creature.CurrentThought.Vote = ItemOfInterestReaction.FleeFromIt;
creature.CurrentThought.Vote = ItemOfInterestReaction.FleeFromIt;
} else if (Player.Local.IsSprinting) {
creature.CurrentThought.Vote = ItemOfInterestReaction.FleeFromIt;
creature.CurrentThought.Vote = ItemOfInterestReaction.FleeFromIt;
creature.CurrentThought.Vote = ItemOfInterestReaction.FleeFromIt;
}
}
break;

case ItemOfInterestType.Scenery:
//scenery is things like rustling trees
creature.CurrentThought.Vote = ItemOfInterestReaction.WatchIt;
break;

case ItemOfInterestType.WorldItem:
if (creature.CurrentThought.CurrentItemOfInterest.worlditem.Is () && !creature.Den.BelongsToPack (itemOfInterest.worlditem)) {
creature.CurrentThought.Vote = ItemOfInterestReaction.FleeFromIt;
} else if (itemOfInterest.worlditem.HasAtLeastOne (creature.Props.ThreateningScripts)) {
creature.CurrentThought.Vote = ItemOfInterestReaction.FleeFromIt;
}
break;
}
}
...
}

Unless the creature is familiar with the player – this is something the Tamed script is able to modify – the Timid script votes to flee. If the player is acting aggressively – standing or sprinting – it votes to flee multiple times.

And if the creature sees another creature that doesn’t belong to its pack, it votes to flee. Or if it sees a WorldItem that has a script that it finds threatening (eg, Hostile or Fire) it flees. TL;DR – it flees.

But that doesn’t mean that the eventual result will be ‘FleeFromIt’ because other WIScripts are getting in on the voting as well. If (say) I had a Starving script for creatures that hadn’t eaten in a long, long time (I don’t plan to include this but it’s something modders could add) it might vote like this:

Code: Select all
namespace Frontiers.World
{
public class Starving : WIScript {

public Creature creature;

public FoodStuff ThingToEat;

public void OnInitialized ( )
{
creature = worlditem.Get ( );
creature.OnCollectiveThoughtStart += OnCollectiveThoughtStart;

...
}

public void OnCollectiveThoughtStart ( )
{
FoodStuff foodStuff = null;
IItemOfInterest itemOfInterest = creature.CurrentThought.CurrentItemOfInterest;
//can we eat it?
if (itemOfInterest.IOIType == ItemOfInterestType.WorldItem && itemOfInterest.worlditem.Is (out foodStuff)) {
ThingToEat = foodStuff;
//we can eat it! vote to eat it
creature.CurrentThought.Vote = ItemOfInterestReaction.EatIt;
if (Stacks.Can.Stack (foodStuff.StackName, creature.Props.FavoriteFood.StackName)) {
//it's our favorite food! oh my god we HAVE to eat this
creature.CurrentThought.Vote = ItemOfInterestReaction.EatIt;
}
} else if (ThingToEat != null) {
//we don't care WHAT this new thing is, we have a thing to eat
//ignore anything that's not the thing to eat
creature.CurrentThought.Vote = ItemOfInterestReaction.IgnoreIt;
creature.CurrentThought.Vote = ItemOfInterestReaction.IgnoreIt;
creature.CurrentThought.Vote = ItemOfInterestReaction.IgnoreIt;
creature.CurrentThought.Vote = ItemOfInterestReaction.IgnoreIt;
creature.CurrentThought.Vote = ItemOfInterestReaction.IgnoreIt;
}
}

...

}
}

If it sees its favorite food it’ll try to eat it – and if anything else comes up while it’s still trying to eat that food, it’ll spam the votes with ‘IgnoreIt’ to keep its Timid nature from winning out. And at no point did I have to coordinate with the Timid script to make sure we don’t contradict each other – the two scripts know nothing about one another.

Read more here: 7/24/2014 – Creature AI Part 2