Friday, April 9, 2021

Project Breach #5 - "Pawn AI, Gadgets and Performance"

 Project Breach #5
"Pawn AI, Gadgets and Performance"


Pawn AI
The new wandering system was improved in the exact way that I talked about in the last update, and it worked perfectly. Pawns now convincingly wander around, making sure not to drift too far from their starting place.




But pawns taking a casual stroll while bullets flied past? Probably not the best idea. So I implemented a series of different behaviours pawns will engage in depending on the situation.

Pawns can now seek & take cover. This may happen because they've been shot, or because they think that they might be. In this example you can see the pawn at the bottom left seeks cover after they've killed the threat, just in case someone else is around.




Pawns can also hear gunshots. This one was one of the more important changes as before, if a pawn didn't have line of sight over an enemy, it would never know they were there, even if all his allies were battling it out in the next room over.

Pawns can also decide to forget cover altogether and charge the enemy. This is used as more of a reinforcement tactic, that while your allies are pinned down, you can afford to run up and go for quick clean shots on whoever is attacking them.




And finally, pawns can investigate. If they've had line of sight or been shot they know exactly where their attacker is. But if all they heard was a gunshot, they'll only know the general area of where the attacker might've been but not exactly. If they show up and find no one there, they'll look around for awhile before eventually returning to normal.




Comparing this new system with what we had before and you truly get to see how much it changes the gameplay. Only 2 weeks ago, a pawn would just stand there, practically waiting to be shot. Now they move about, try to better defend themselves with cover, assist their allies, and work together to hunt down the one attacking them.

At some point I'd like to probably redo the pawn AI, I've learned so much since first starting on it and I think it's a little messy but for the demo I'm going to leave it how it is.

Gadgets
The gadgets system was something I just didn't want to do. And in hindsight, I really don't know why. There was just some mental block preventing me from wanting to even start on it. But with the game getting so close to demo, I finally pulled the trigger to do it. And with most things that you put off, it was much easier to do then how you thought it'd be inside your head.

I knew in-advance what gadgets I'd be making with this system so I was able to quickly identify the 2 types. Placed and thrown. After that I began adding 2 types of functionality: Triggers and Trigger Effects.

Triggers were what made the gadget go off. Right now that just means a timer, but in the future could mean things like impacting with a wall or being shot. Trigger effects were what happened when the gadget was triggered. I added 3 "Damage pawns in a radius", "Damage mod objects in a radius" and "Stun pawns in a radius". And with these 3 effects I was able to make the 3 gadgets pawns will be using:
1. Breach Charge (Damage Pawns & Damage Mod Objects)
2. HE Grenade (Damage Pawns)
3. Flashbang (Stun Pawns)

You can't yet order pawns to use these gadgets (although I'm working on that already), but here's me testing them by spawning them in the editor:






Performance:
Massive improvements have been made to how well pawns perform. The main performance draw was when pawns were within range of each other, they had to perform a line of sight check. This check can be expensive when so many pawns are doing at once, the game (on my PC) would drop below 144fps at ~80 pawns.

This annoyed me because I really want the game to be able to support a lot more. I used Unity's built-in deep profiler and discovered that, I was right, 90% of the pawns performance drop was because of that line of sight check. So I spent time doing dedicated tests to improve one part of it at a time & measure the change, the result was that after only a few hours of testing I had managed to massively reduce the performance use of the line of sight check. How much is massive? 47% less milliseconds to calculate. That's pretty massive, and I've been considering other changes that could help improve it further.

While debugging I also discovered that the pawns movement script was using up more resources than it should be. It wasn't using up much (16 pawns took 0.1ms) but I knew that number could still be improved. It turned out it was a similar performance problem that was plaguing the line of sight check, only this time it was caused by the system that made so pawns could auto-open doors. After implementing my solution 16 pawns took 0.03ms to do this check. That's a 97% improvement!

I know it's not terribly exciting to talk about performance improvements, but it's really important to me that this game run as well as possible so that I can continue to build on top of what I've already written without having to worry that it won't be able to handle it. And so as many people are able to play the game of course.

With these two changes, the game now wouldn't drop below 144fps on my PC until over 170 pawns. That's double the amount of pawns the engine could now support! Even after that improvement, the line of sight checks is still about 80% of a pawns performance usage so improving it further will be massively beneficial.

Lastly
I didn't quite manage to take my advice last update and take much of a break. But I did manage to take most of Saturday & Sunday off, especially with Sunday being Easter I was able to enjoy talking with family which I don't do as much as I should. I've also really gotten into playing State of Decay 2 so that's helped me to not work all day. Being so close to the demo though, I can see the finish line and that's giving me massive motivation to keep working and get things done. Whereas before I was just doing things every day because I felt I should, now I'm excited to open up the project and take another step closer to that demo!

As always, thanks for reading, if you're curious about the progress of the demo I recommend reading further, and I'll see you in the next one!

-Blake Gillman

Saturday, April 3, 2021

Project Breach #4 - "Equipment, Cover Objects and Enemy AI Start"

 Project Breach #4
"Equipment, Cover Objects and Enemy AI Start"


Visuals
There's so much to this update it was hard to pick a place to start.

First, there's a new grass texture, which is probably the 8th time I've remade it, but this is the first time where I'm actually satisfied with how it turned out.

And secondly, there's now a cloud effect which you can turn on in the map editor's new "Environment" section. Here's those two things together:




The clouds are generated from a shader which uses procedural noise so that it never repeats. In the map editor you can adjust all sorts of settings so it doesn't need to just represent clouds. Want to make an apocalyptic map with green chemical filled air? You can!






After that I worked on making so that pawns bob while moving. Even though this games art style was inspired by Rimworld, I think pawns bobbing adds a bit more flavor to their movement that's missing from that game & similar ones (Like Prison Architect). Movement bobbing is a system I developed for Black Company and I thought it'd do the job perfectly. What's cool about it is that the bobbing can change speed based off of how fast the pawn is moving which can be measured client-side by measuring the distance between where the pawn was the last frame vs where they are now. That way clients can all get a similar bob without having to send any data over the network and the faster a unit moves will automatically result in matching bobbing.

I also made so pawns will adjust what direction their weapon is aimed based off of which direction they're moving.




Later in the update I'll be talking more about the gameplay aspects of equipment, but there is now equipment the pawns can use such as body armor. Because I wanted pawns to be able to show what equipment they have on, I created some new base texture atlases for pawns to use:





And here's what the three equipment texture atlas looks like (Body Armor, Helmet, Guerilla Vest):






It was important for me to make so that equipment could be shown on pawns so that way you can tell at a glance what you're dealing with when you see an enemy.

Next I added damage popups:




If you're wondering why the players pawn is taking 3 damage while the enemy pawn is taking 23 damage, it's not because player pawns get a buff to take less damage, it's because the enemy's armor is visual only and gives no armor & they're using a worse weapon. This is how the game is intended to be balanced, so that if you went up against someone with equal equipment, you'd take just as much damage as an enemy. But enemies will likely always have much worse equipment with the exception of potentially specialty units. This is important to note because I want to make it clear, your pawns do not get a cheat "Take less damage" buff. And in the campaign, as well as in the multiplayer, you will be able to customize the equipment your pawns have before going into missions.

And finally for visuals, I created a new barbwire fence texture (Although this is I think the first time I've shown the barbwire fence in a blog post):




Gameplay
Pawns can now be given an attack order by right clicking an enemy pawn. This will make your pawn automatically run to them & attack them once they're able. This order will automatically finish when the pawn is either dead, and in the future, if the enemy pawn is no longer visible due to fog of war / sight. Pawns can also theoretically be given an order to attack a position (This is helpful for things like shooting through a wall you suspect a target might be behind), although this won't be able to be tested until I begin implementing the player UI.

I've also made so that if a pawn fires a bullet, and a friendly pawn is within 2m (2 tiles) of that bullet, it won't damage them. This lets you stack pawns up to 2 next to each other without having to worry as much about friendly fire.




Pawns can also now be headshot. Because the head of pawns is so large, if I made this calculation solely based off of where the bullet hit it's likely 50% of all shots would result in a headshot which is pretty unbalanced. So I made so if a bullet hits the upper half of a pawns body, there's a 10% chance it'll be a headshot. I can play around with that value more with testing. A headshot will result in the projectile dealing 200% damage.




This leads me perfectly into passive equipment. Equipment the pawn wears like body armor is referred to in-code as 'passive equipment' because it's things the pawn equips that just provide a permanent buff until unequiped and which the pawn can't do an action with. Right now this means armor but in the future could also mean things like extra ammo capacity, extra health, or any number of other buffs.

Passive equipment have a flag in their file called "ArmorEffectsTotal". Any equipment which has this on will add its armor value to the pawns total. The total armor value of a pawn determines how much damage they'll take normally.

How much armor protects you is equal to the value of the armor. So if you were going to take 30 damage but you're wearing armor totalling 20, you'd only take 10 damage. The exception to this are headshots which only care about what armor you have in your pawns head slot. I personally make so that helmets will have "ArmorEffecsTotal" set to false because I don't think the armor value of a helmet should effect how much damage you take from a body shot, but modders are free to turn that on.




If you're wondering why the guerilla vest has "ArmorEffectsTotal" set to false even though it has an armor value of 0, it's because then the game won't even try to check the armor when equipping & unequipping, which is unnoticeably slightly faster for the CPU but I figured I might as well.

Cover Objects
There are now three-ish new cover objects in the world!

Cover is of course an important part of a tactical shooter. But cover needs to be more than just hiding behind a wall or pillar, there need to be things that you can stand behind and be protected while still being able to shoot back at the enemy. So the first thing I worked on to get that system started was sandbags:



Cover objects you will shoot over if you're standing right next to, but have a chance to block bullets being shot at from a distance. I had to reprogram the penetration system in order to achieve this.

Game surfaces was a cool idea, but I ended up getting rid of it entirely. Game surfaces worked because it was values that all things with that surface could use (All glass should be see through, all wood can be shot through, etc.) but this became a problem quickly as I found that every object needed the values at least slightly different from each other, to the point where almost every object had its own surface, at which point it sorta defeated the purpose of having game surfaces that multiple objects could share. So I moved all the properties of game surfaces to be set on each object & I completely reprogrammed how penetration works and made those values on objects instead of surfaces as well.

Projectiles now have a penetration value which must be equal to or exceed the penetration defense of an object in order for the projectile to go through. Although objects can still define % chance of a projectile penetrating. Here's what some of that data looks like on an object:




Enemy AI (Start)
The gameplay features were coming together but it wasn't exactly exciting to fight enemies who stood still the entire time and just shot whenever you got in sight. Talking with my friend Nico about it, he helped me compile a set of things the enemy AI should be able to do as well reactions to what the player does.

This is quite intimidating as I've never had to work on this sort of more complex AI logic before. The AI in all my games usually could be boiled down to "Go toward the player, attack the player when you're able to do so". I've done minor experiences on random projects with other features but nothing to fancy. The AI in order to work for this game will need to be able to do things like wander around, have patrol paths, take cover, use equipment like grenades, react to equipment the player uses such as running away from grenades, and be alerted to the players presence when gunshots start going off.

This is a lot to do and I'm interested to see how I manage. I've begun on the AI which I call "Pawn Brains". This is because in the future if I decide to make AI that can think differently, I want the user to be able to tell pawns to use different brains such as "This is the more defensive brain that cares more about being behind cover, this is the offensive rush brain, etc." but for now it'll all be using one.

The only thing AI do now is wander. I made so that they try to stay in the area that they started at, otherwise you'd end up with pawns wandering to the other side of the map. This does create some problems though, take this example:




In this example, our pawn (the light green) should stay within 4 tiles of where they start. This means they shouldn't wander outside the wall (represented by Black). The problem is that if we just randomly tell them to go to a point within their 4x4 area, they could try to path outside the wall, which would lead them to walk out the hole in the wall which is clearly a completely different area from where they should ever be (You can see the path they'd take with the yellow):



So how do we solve this problem? My first way of solving this problem created a bug. I simply measured the distance from their spawn point to where they were, and if they went greater than 4 units, forced them to stop moving & then to pick a new location to move to, hoping that this new location wasn't an invalid one. The problem was that then when they got their new order, they were still greater than 4 units from their spawn point, so it'd once again cancel their order. This means that once they wandered greater than 4 units, they could never move again.

After a few more ideas, I boiled it down to three solutions:
1) When the pawn gets out of range, have them return to their spawn point before trying to wander to a new spot again
2) Measure the length of the pawns path, since we know 1 node on the path is 1 tile, a path 5 long is 5 meters. So we know that if the path has more nodes than we want distance traveled, we should find a new tile to wander to.
3) Measure the distance between each node in the path from the spawn point, and if any return a distance greater than the wander distance, try to find a different tile to wander to.

In an ideal world, I'd go with solution 3 since it'd always be the most accurate. The problem is that distance calls are a little expensive on the CPU because computers really don't like square root operations (which is what the distance formula you were taught in school uses):

d=(x2x1)2+(y2y1)2

So because I want the game to run smoothly, I opted to not use the most accurate solution but to instead go with something faster. #2 would be the fastest but it also is the least accurate, because measuring the path could result in the pawn thinking their remaining path is within wander distance, take this scenario for example:



The pawn should never be able to access an area that requires them going outside of their wander range to reach, yet by just measuring the length of their new path, they could end up in that sort of area. In that above example, you can see the pawn walks outside of the valid area, looks for a new path, and that new path leads them into an area only reachable by having gone outside the valid area, which isn't what we want.

So although #3 is the most accurate, and #2 is the most optimized, #1 is the only one that will be accurate and CPU efficient, as it'll only require 1 distance check (Measuring the distance between the pawn & the start position).

I haven't implemented this new solution (#1) so there are potentially unforeseen consequences, but I think it'll work perfectly. Anyway, that's as far as I am with AI!

Map Editor:
While I was making the new map for testing AI, I figured I'd hammer out a feature I'd been planning to do since I first began work on the map editor. Light Sprites:




Because most ligths in the game will be 99% if not 100% sprite lights, it'd be nice for the lights to have a variety of sprites / shapes to be able to choose from. This was always intended but I'm just now finally getting around to doing it. As with everything else, these are part of the mod and you can add your own light shapes / sprites through modding if you chose to do so.

The last thing I did for the map editor was add a quality of life feature, editor grids:




These grids are 16x16 which aligns perfectly with the chunks on the map. They're mostly there to act as helpful lines, but you can toggle them off at the top left of the map editor next to where you toggle lighting.

Other Changes:
I added a loading screen which works both for transitioning scenes (Like to the main menu) as well as for loading maps. I also made so pawn dialogue can be modified from a file so that modders can make their pawns have custom dialogue or use a variety of different dialogues.

Summary
Once again, I enjoyed livestreaming on YouTube. You guys got to see me do the initial cover object system as well as solve how to make sandbags not effect other walls. Although the cover objects system has changed quite a bit since I livestreamed (due to redoing all the penetration stuff). I'd contemplated doing more livestreams but decided to leave it to 1 per week so that I'm not flooding my YouTube uploads with livestreams videos that only a fraction of people on the channel want to watch. I'm considering creating a second channel for livestreaming so that I don't flood the main channel. Or maybe I should just use Twitch but I want a recording of the livestream to exist, we'll see.

This week was more intense than most. I'm making what is quite possibly in the literal sense, record pace on the game. Several entirely new gameplay features were added, several systems rewritten, I've been working non-stop all week!

I think I've always had a problem with working too much, I'm a bit of a workaholic. And as someone who makes a living as a programming contractor working from home, weekends don't really mean anything to me which means I end up working on those too. But I'm actually feeling like I could use a bit of a break. So I'm going to try and take a few days off. It's weird to describe but I quite literally don't know what to do with my time when I'm not working. I try playing video games but there's always that feeling to get back to work in the back of my head. So it'll be hard figuring out how to take time off but I think for my own sake that I should make an effort to do so.

Anyway, as always, thanks for reading feel free to leave a comment & I'll see you in the next one!

-Blake Gillman