Spamocalypse 0.7.2: NPCs aren’t deaf any more

I am an eejit. While trying to fix the performance issues a month ago, I think I broke the spammers’ sound detection. However, I didn’t realise this until I tried making them go on alert if they heard a SQL round miss. I’m not even sure if that’s when it happened!

The root cause of this bug is that I had converted their sound detection to use a Coroutine with a 0.2-second interval, rather than OnTriggerStay. OnTriggerStay runs on the physics timestmp, which by default is 50 times a second – which is a bit more intensive than I wanted. However, when converting it, I forgot to actually make it run more than once! So, the spammers were effectively deaf, which pretty much defeated the point of the game.

Another thing that was broken was their Sockpuppet detection. This was a physics issue – I had set the Spammer layer to ignore collisions with the Decoy layer. However, that’s the layer which is supposed to contain the Sockpuppets!

There are two things that would have helped with this:

  • Automated testing. I might have noticed this if I had a dedicated scene where I can set some bots to detect and attack things in a controlled setting. This
  • Source control. Why should I try remembering what I changed last week when the computer has a record of that for me? I have some familiarity with Git in my day job.

18 months of work down the drain

This is a massive, rage-inducing WTF moment. I kept having problems with the pathfinding code marking corridors as impassable even though there was enough room to walk along them, and having issues with checking underneath objects, particularly in buildsings with more than one floor. I decided to try updating my pathfinding code in Spamocalypse to use the latest version in my repository, mainly because that allows me to store the navigation data in a ScriptableObject. I thought of using separate meshes for separate floors, or possibly separate rooms, but I then ran into an issue where the dictionaries weren’t being set up on load, resulting in NPCs not being able to find a path and thus trying to walk through obstacles.

I’ve been using this code for around 18 months now. The reason I started using it is because I first started the project on Unity 4.6, when pathfinding was only available to Pro users, and for my thesis project I built/adapted a pathfinding system to track where units died in a team deathmatch game. The way it works is that I cast rays downwards at regular intervals to check if an NPC can walk at that position, and store the result. The only problem is that casting at regular intervals doesn’t really work with arbitrary shapes like those in a real-world city, especially if there are floors below it. So, a few weeks ago I started considering just using the built-in pathfinding system, but I kept putting it off because I thought that refactoring it would take too long…and I didn’t want my code to go to waste. The sunk cost fallacy rears its ugly head!

*headdesk*

FFFFUUUUUUUUUU!

Continue reading “18 months of work down the drain”

How NOT to calculate lighting for stealth games

My original stealth system for Spamocalypse was based around the navigation system. Each Node has a variable for illumination, which is calculated using raycasts and then baked in. For point, area and spot lights, this is inversely proportional to the distance from that light; for the sun/moon, the value of the sun’s intensity is just added without any processing. When an NPC or the player is trying to check if they can be seen at a player, they query the NavigationMesh to get the closest Node, and then check the illumination of that Node.

What’s the problem?
This is expensive! As I covered in previous post, this causes a performance impact. However, the fix I made for that causes another issue while trying to calculate the effect that street lamps would have on the lighting. They had absolutely no effect – it was possible to stand underneath one, and the NPCs would simply ignore you if there were no other lights around. I suppose I could have rewritten that as a “Refuge In Audacity” thing, where the spammers wouldn’t see you because they wouldn’t believe someone would stand under a street lamp in full view of them…but that’s another game, I think.

The street lamps I have are essentially a long, narrow box which acts as a parent for a point light. When the light was casting rays to check a line of sight to different nodes, it just hit the inside of the collider. So, the next thing I tried was disabling the parent collider for those lights, but I ended up accidentally disabling other scenery colliders. When some other point lights were casting rays around themselves, they ended up hitting points that were outside the navigation mesh, which caused the BuildNavMesh class to throw an exception.

WTF was I thinking?
I just didn’t know of any better way to do this. My first thought was to try using Unity’s LightProbes, but I started Spamocalypse the free version of Unity 4.6, and they weren’t available to free users at the time. I also don’t know if the API for LightProbes allows the intensity to be retrieved as a float, which is what I was looking for. I also had some experience at trying to generate a pathfinding system for my Master’s thesis, so I decided to give that a try, as I knew the general concept would work.

The actual solution
After looking around the Unity forums, I’ve created a physics-based solution that uses trigger colliders and raycasts. Each spot, area and point light has a trigger collider attached, which tracks instances of a CalculateLight component that are inside the collider. If the light has a “line of sight” to that CalculateLight, the light contributes to it’s total intensity. The amount contributed is the intensity of the light, divided by the distance between the light and the object it’s tracking, and at the moment is clamped to a maximum of 1.
There is only ever 1 directional light in a scene, so the Sun component handles this. It has a list of CalculateLight components, and tracks each of them using FixedUpdate. It basically casts a ray to check if the object can be seen, and toggles whether or not it “visible” to the sun/moon. If a CalculateLight component suddenly is in the sun, it’s total intensity is immediately increased by the intensity of the sun, and stays that way until the character can no longer be seen.

Does it work?
Yes, it does. I haven’t done a full test over the City Inbound level yet, but it doesn’t take as long to calculate, and it actually works in real-time – so, I could possibly use this for things like spotlights or torches in future projects. I will update this with the full test results.

Spamocalypse: Respawn mechanism and NPC models

Another update to Spamocalypse. This time, I’ve added a respawn mechanism for the spammers. They will respawn after a 30 second delay, which will remain constant across all difficulties.

My experience of stealth games has mainly been shaped by Thief and Thief 2. In those games, killing people is discouraged – in fact, on expert difficulty killing people is forbidden. On the lower difficulties, it is generally allowed (though Thief 2 has a few missions where it has to be kept to a minimum for the sake of the plot), but it is discouraged by the fact that you have to hide bodies, and that does include removing their blood stains. I don’t have those in Spamocalypse, so I’ve added the respawn mechanism as an alternative to that.

What happens is that when an enemy is killed, the Level Manager will wait thirty seconds and respawn them in the same spot. If this happens too often, they will be come suspicious and start searching around them. The criteria for “too often” will depend on the NPC type – spambots require about 20 deaths to realise that something is wrong, regular spammers will cop on at about 5 deaths, and Moderators will realise this after 3 deaths. In the meantime, other NPCs who find their corpse will trigger an alert.

I also have some NPC models! These haven’t gone into a build yet, as I’ve only just finished the Moderator. However, they are textured and, after following this tutorial, they are animated.

Spambot/Dumbbot
The Spambot/Dumbbot is the stupidest weapon available to the Word of Turscar. It trundles around blaring out Turscarite propaganda (i.e. spamming loudly) and launching cans of spam at anyone who gets in the way. They were designed to be mass-produced and deployed en masse for assaults on target Sites, and have not been programmed to account for Sockpuppet during their sales attempts.

Not very bright, but it doesn't need to be. This is what I imagine spambots look like.
Not very bright, but it doesn’t need to be. This is what I imagine spambots look like.

Regular
These two show a regular spammer, which is really just a zombie that vomits spam. Excessive exposure to spam may result in conversion. They can use whatever humanity they have left to identify a Sockpuppet, and will search nearby when they recognise one.

Spammer1
If they’re idle, it’s because they need to take a break. Hence the preference for bots during attacks.
Spammer1 Attacking
Achoo! It’s supposed to be vomiting spam rather than sneezing, but I haven’t got that far yet.

Moderator
The Order of the Hammer of Moderation is an order that is pledged to protect Sites from Turscarites, Trolls and other degenerates of the Nett. Any Moderator (or Hammerite) that is caught by the Turscarites can expect a long, slow and painful process of conversion. When the process is finished, they are no longer human, and serve the Word of Turscar with the same fanaticism with which they once opposed it. They still retain their abilities, including that of tracing a Sockpuppet’s launch position.

I did originally plan to give them a muzzle-loading air rifle for ranged attacks, or possibly a wheel-lock pistol like in Dishonored, but I decided that would take too much time. Instead, I just went with the hammer.

I art bored, brethren.
Guard duty art tedious, brethren.
To arms, brethren!
To arms, brethren!

So, my artwork isn’t great at the moment. However, the regular spammer and the moderator are the first two humanoid characters I’ve ever animated, so I think they can be excused. 3D modelling of characters was the last major development hurdle for Spamocalypse…at least, until the bugs and Gremlins turn up.

Spamocalypse: Demo Release (Updated)

Update: I’ve added the Mac binary. The Windows version is here, and the Mac version is here.

I’ve finally got Spamocalypse to a prototype stage for the AI. There’s no artwork yet, some basic sounds recorded using this speech synthesiser, and the player can’t attack. However, the enemies are able to move around and respond to the player’s sounds and their line of sight checks are dependent on how bright the player’s position is. And after fixing a bug in the navigation mesh I built, their pathfinding will actually consider straight lines in all 8 directions around them, as long as they don’t have a clear line to their destination.

There’s still a few problems. Firstly, the AI don’t attack the player at point-blank range – in fact, they can’t even see the player at that range. I think it’s a problem with the models for their line-of-sight area, or perhaps their positioning. I also had to limit the number of times they speak, as they ended up talking over each other in a horrible gibbering mess. Finally, when they follow a path back to a patrol point, it sometimes glitches out once they reach it, and they just stand there doing nothing. Well, they are functionally zombies, which aren’t known for their intelligence…but enough excuses. It’s glitchy, and I need to fix it.

Here’s an image of the prototype so far. In the final product, the colliders for the detection systems won’t be visible, and neither shall the details about the spammers, but they should give an overview of what’s happening for now.
Temp Player Interface

I’ve uploaded the binary folder to Dropbox. The initial one is for Windows only, and it’s a standalone build – this is due to my own navigation system relying on serialisation, and I only have my laptop to develop and test. However, I’ll add a Mac version later, and if you give this version a try, let me know what you think. I’ve added a Mac version, so if there’s any errors in that, let me know.

Spamocalypse AI Update

So, I’ve been working on Spamocalypse on-off over the last while, and I’ve got a basic framework for the AI in place. At the moment, I’ve only got the basic dumb bots able to attack, but that’s on hiatus until I get the AI to be able to move close enough to a suspected player. That’s proving to be harder than expected, but I think I’ve got it now.

One of my problems was that the alert time isn’t incrementing properly. It does work if I set it to constantly increase while they are searching, but that results in them getting bored before they get to the endpoint. What I’d like is for their alert time to increase only if they have nothing else to do: if they still have a path to a possible player position, the time spent travelling along that path shouldn’t count. And if there’s a direct line to their new position, why bother searching for a path in the first place?

Running the pathfinding code for five or six bots on startup caused an unacceptable drop in framerate, so I’ve tried to rework the pathfinding code. Instead of doing a while loop that lasts until they find their destination, they will now do a fixed-depth search of 50 nodes. I tried this during my Master’s project, and it didn’t work out as planned, but I suspect that was due to the level layout and resetting the open list too frequently. In real life, most people would not map out and remember every step along a path, so limiting the search depth makes some sense. However, this hasn’t worked, so I’ve gone back to using the while loop – as that will at least work.

I’ve also adjusted the method to set the units’ destinations: it now performs a raycast to make sure they have a direct line to their destination. If this returns false, then they assume that there are no obstacles within 20 metres of them and their destination, and they will just move in a direct line. If that fails, whether due to an obstacle being in the way or the destination just being too far away, they search for a path. If they get within their optimal attack range from the decoy/Sockpuppet, they stop and then the alert time increments. I’ve done some quick tests, and it seems to work well enough for me to start planning other mechanisms.

Pathfinding update

So, an update on my pathfinding system. I recently imported it into Spamocalypse, and there were a few things I had to change.

For starters, I can’t store the neighbours of a Node inside that particular Node, or else Unity tells me I’ve reached the serialisation limit. In hindsight, this isn’t a surprise: if each node stored references to their neighbours, then referring to another neighbour would have in turn referred to the neighbours of that neighbour…and so on. Recursion is not a good idea, so I’ve had to make finding the neighbours of a particular Node a run-time method, i.e. it’s calculated on the fly while the game runs. So far, I haven’t run into any problems when testing that, but I’ve only used a single agent to test it.

I’ve also added a few useful things to the editor script that builds the navigation meshes. For starters, I’ve added in options to specify the walkable layers in the project. These work fine, although in Spamocalypse it seems to reset the walkable layers to include the bots and walls when starting up again, but in spite of that the code works. I’ve also added sections to change the square footprint of a cell, along with it’s max and min height, and finally managed to clamp light intensity. Light intensity will be clamped to a range between 0 and a value set by the user, if they wish to enable this.

So, I have the core changes uploaded into the repository. For those who don’t have or don’t know how to use Git, I’ll probably make a Unity package that you can import directly into the editor at a later stage.

Path-finding for stealth

One of the problems I have with Spamocalypse is how to build light intensity into the player’s ability to hide. Most of the stealth-based games I’ve played include a mechanism like this, and I think it’s worth adding. My problem is how to do that, especially given that I’m using Unity Free and don’t have access to things like Light Probes.

The solution I’ve come up with is to add illumination to the pathfinding system. However, for that I’ll need to build one. This is something I did for my M.Sc. (see the Completed Projects page), but it had a few problems. Firstly, the mesh for each level was generated from scratch upon runtime, and the nodes were essentially limited to a two-dimensional array. Due to the level design, I also had a lot of unreachable nodes that were simply wasted space. While premature optimisation is a bad idea, I still hate the idea of useless data.

So far, I’ve managed to create a mechanism for serialising and deserialising the navigation meshes to the disc, allowing me to save and load navmeshes at runtime. I also have a template for adding nodes underneath obstacles, although it doesn’t quite work yet. Finally, I have managed to add an illumination variable to nodes, so that part of the system is done. At the moment, what I have left to do is to finish an A* implementation, and I should be fine.

I’ve decided to put this up as a GitHub repository, partly for storage, partly to track my changes, and partly so other people can use it, and it can be found here. When it’s finished, I will also add it to Dropbox as a Unity Package.

Spamocalypse: First version of the AI detection mechanisms

I’ve been working on the AI for Spamocalypse in my free time, and I have the basic detection mechanisms done. Here’s how they work.

    Line of Sight

Line of sight detection in Unity is something I’ve long since got the hang of. My usual method is to create a 3D shape, import it into Unity, and then attach a Mesh Collider with the “isTrigger” attribute marked in the Inspector Panel. When an object such as the player enters that trigger volume, a ray is cast towards the object. If the ray hits something other than the object, then the AI unit won’t have a line of sight to the object; however, if it does, then the AI unit will begin a counter to decide if the player is actually the player. If that counter reaches 50, it informs the brain, which will cause the AI to investigate; whereas if the counter reaches 100, then it has definitely spotted the player.
There is one problem with the above setup: the line runs from the AI to the centre of the player. If the player’s centre is just hidden behind a wall, but they are still visible peering around the corner, the AI won’t register them as visible yet. I’ll need to figure this out, but the core concept works.

    Sound

Sound detection was a little harder, simply because I’d never done it before. However, I’ve got it working as follows: a sphere collider is attached to a child object of the AI unit. That too is set to act as a trigger. When the player enters the trigger volume, it begins to calculate the player’s noise using the following formula:

noise = player_Speed * player_Footsteps_Volume / distance_From_Agent

That’s calculated on the physics timestep, which by default is every 50th of a second. If the noise on the current timestep is greater than it was on the previous timestep, the detection increases by a specific amount. If the detection reaches 50, the agent begins to investigate. If it reaches 100, then it has definitely spotted the player, and will begin to attack.
The sound detection will also activate for decoys. When a decoy enters the trigger volume, the agent checks if it’s playing. When it starts playing, the sound detection script informs the brain. If the agent hasn’t had enough false alerts, it will investigate the decoy. During the initial investigation, they will ignore the player.

Below is a screencap of the two in the game world. The semi-transparent blue sphere is the collider for sound detection, while the collider for line of sight is a flat green rectangle with one end wider than the other. I’ve tested these, and they both work, albeit rather slowly due to the low detection increments. Once I have player movement finished, I’ll create a demo version for people to try out.

Detection Triggers

Spamocalypse: How the AI will work

I ran into a design problem while trying to decide how to make the AI for the different enemy types. So, I posted a thread on the Unity forums asking which of the following would be the best design:

  • Reuse the same finite-state machine (FSM) for ALL the units, but adjust variables such as health & detection speeds;
  • Create a basic FSM, and then give each unit a specialised FSM that inherits or extends it;
  • Create entirely separate FSMs from scratch for each type.

Based on the responses there, I’ve come up with an idea of how to do it. For the benefit of anyone who doesn’t understand any of this, I’m going to explain it along the way.

I’ve decided to use a single finite-state machine for all the enemy types. A FSM is a program which can be in a finite number of states, such as (to use my code as an example) attacking, idle, patrolling and searching. They’re a pretty basic form of artificial intelligence for a video game, mainly because they can be easily extended to include new states. Using one single script means I will probably have less debugging to do.

Now, this does raise the issue of how give the units specific actions, such as their response to the sockpuppets/decoys. For instance, I have decided that the Admin-type enemies can trace a decoy’s launch position, and will do so after a brief period to check it’s a false alarm, or they will trace it immediately if they’ve had too many false alarms. Meanwhile, the n00b-type enemies just go over to it and stare gormlessly at it for a few seconds.

My solution to this, after somebody suggested using different methods/functions for each unit, is to use delegates. In object-oriented languages like C# (the language I use in Unity) or C++, Java and so on, a delegate is a helper object that does a particular task for another object, with specific parameters and return types. What that basically means is that class A tells class B “Right, here’s variable_name, deal with it”, and then ignores how class B actually deals with it. Borrowing two C# examples from the Unity tutorials here, with my comments added:


// define a delegate MyDelegate, of return type void
// and taking a single integer as it's parameter
delegate void MyDelegate(int num);
MyDelegate myDelegate; // create an instance of it

void Start ()
{
// this while print "Print Num: 50" when running
myDelegate = PrintNum;
myDelegate(50);

// this will print "Double Num: 100"
myDelegate = DoubleNum;
myDelegate(50);
}

void PrintNum(int num)
{
print ("Print Num: " + num);
}

void DoubleNum(int num)
{
print ("Double Num: " + num * 2);
}

When this programme runs, the delegate myDelegate would point first towards PrintNum, causing the Editor console to print out “Print Num: 50” when myDelegate(50) is executed. Right after that, myDelegate would point towards DoubleNum, and then the Editor console will print “Double Num: 100”. In each case, the Start method is saying “Just do something with the number 50, I don’t care what, and I don’t want any variables back”, and handing 50 to the two methods. The key part is that both these methods are of type void, meaning they do not return any variables or objects, and take an integer as a parameter, and therefore match the delegate. If DoubleNum had been written as follows:

int DoubleNum(int num)
{
return (num * 2);
}

Then myDelegate could not point to DoubleNum, because the return types do not match.

What’s the point of this? Quite simply, I have separate search methods which will define how each enemy type responds to the decoys. So, if I associate each particular enemy type with a variable unique to that type, then I can assign which method they use once at start-up, and they need not worry themselves with how each other acts. This means that if an enemy isn’t acting properly when in a particular state, I just have a single method to debug rather than a separate script, and I can reuse as much code as possible. Why reinvent the wheel when I have one at home?