Just a Few Performance Optimizations

It’s not going to be productive to spend lots of time optimizing code at this stage, because things are still pretty fluid. I like to save detail work until later in the project. But today was devoted to some quick optimizations of the AI, pathing, and collision check routines.

Over the IndieCade weekend, I tried a few of the pathing exhibition levels on my laptop on battery power, and it went down to about 1 FPS once all 50 rats were on the screen. I was expecting this, since I didn’t put any effort into performance yet–I just wanted to get it working.

Visual Studio 2012 impressed me with its performance and memory profiler. I was able to quickly see what the slowdown was being caused by. It’s mostly collision detection, as usual, but the traffic field system was also grinding things down. The vision checks are pretty much the biggest culprit, because they do multiple collision tests per frame to check if one creature can see another.

There are some more robust solutions I can do later, but I only wanted to spend one day on it, so:

  • Traffic field builds itself for entire map, rather than dynamically as creatures move.
  • Vision field keeps separate lists of monsters and heroes so it doesn’t need to do checks or filters in the “detect foe” vision checks.
  • Position comparison checks reduced by bailing out earlier for most cases (early returns from functions when it doesn’t make sense to do further tests on the actor).

Just the above cut the time spent in the update routine by about half. Meaning… on battery power, the torture test levels run upwards of 2 FPS!  Okay, so there’s more to do (most likely a bucketing system to split the room into sectors, like Escape Goat). But this will do for now–I mainly wanted to make sure none of the systems I developed were going to pose such a performance threat that they would need to be removed later.

Party Composition

Today’s goal was to balance the summons and basic monsters (rats and skeletons) to the point where a diverse party composition is more effective than only using one or two of the unit types.

This is not true in all situations: there are some layouts where three archers is definitely superior to any other combination–but there should be situations like this choke point where a variety of unit strengths wins the day.

For a refresher on what the units in Soulcaster do:

  • Shaedu (green archer) does the most damage, but needs a clear line of sight to the target.
  • Aeox (blue knight) does the least damage, but has very high HP and takes reduced damage. Melee only.
  • Bloodfire (red bomber) can lob bombs over walls. They are weaker than Shaedu’s arrows against single foes, but because of a large blast radius, he can hit multiple enemies with each shot and decimate monsters when they group closely.

party_composition_sc3

  • Ranged units alone can deal good damage, but as soon as the enemies close the distance, they can’t survive at close range.
  • A stack of three tanky units (knights) does a great job of keeping the choke point closed, Spartan army style, but simply can’t deal enough damage to survive the entire wave of enemies.
  • Two of the ranged units combined with a single tank work well, but just barely–the line is broken just as the final monsters are taken out.

 

Summoner AI and Balancing

With all three summons operational again, now begins the task of finding a good starting point for balancing.

The obvious things to tune are the values for creature health, damage, attack rate, etc. I use spreadsheets to do this sort of thing:

spreadsheetThe orange headings are the creature’s attack stats, which can be used to compute the outcome of an encounter. Those are in green. For example, with these stats, Shaedu can slay 0.44 skeletons per second provided direct shots and the ability to attack at full range.

The mitigated damage value is how much the creature would damage an armored foe. I’m currently just using an armor value of 2, which reduces damage of each attack by 2. This means lower attack power is severely reduced compared with strong attack power (compare the skeleton’s DPS drop from 4.50 to 3.00 with the rat’s DPS, which is being slashed from 4.80 to 2.40).

This spreadsheet is only really useful as a starting point, because there are so many other variables that impact on a creature’s effectiveness and survivability. Choke points are where Bloodfire does best, because the bombs hit multiple foes if they are clumped up. Shaedu does great in corridors where the monster has to head directly into the line of fire–given a slow enough release rate, she can actually take out an infinite number of skeletons or rats. And Aeox is most effective in 1v1 attacks, foe example, when he is guarding a doorway.

Before fine tuning these values, I have to decide on which AI features the summons have. For example:

  • Can Aeox start an attack on an approaching monster before they are in range to strike back? This makes an enormous difference if he is just cleaning up wounded foes by last-hitting them. It means that if monsters come in one at a time with less than 6 HP, he won’t be damaged by them. (I do plan to add this pre-emptive strike, because I think this is strategically cool).
  • Do Shaedu and Bloodfire lead their shots? Until today, they just fired at the monster’s current position. Fast-moving rats, when they are running laterally, become impossible for her to hit, because in the 10 frames it takes the arrow to arrive, the rat’s already moved away.

To calculate the shot lead, it just looks at the monster’s current velocity, calculates how long it will take for the arrow to reach the target, and looks ahead that far in the future to shoot where the monster will be.

Of course, if the monster changes directions or stops, the shot might still miss. This is so effective, I’m curious if it should be a powerup, or just natural behavior.  In a scenario like this, see how much difference it makes:

Without leading the shot
Without leading the shot
With leading - See all those direct hits
With leading – See all those direct hits

The next step is to create a few test maps and see how well the summons do in a variety of room layouts. It’s important that they be considered roughly equivalent in their effectiveness.  The synergy is even more important–Aeox and Shaedu working together, with proper placement, will take out twice as many foes as they would if they were placed one after the other.

Pathfinding AI Improved

So many rats, it's almost unbelievable
So many rats, it’s almost unbelievable

Pathfinding on the pixel level works great now.

As you can see in the above screenshot, the rats will find their way to you really fast. It’s so much better than the tile based pathing that it’ll take some rebalancing. If an interior room full of fast moving monsters suddenly gets opened, they’ll swarm you really fast if they have a path to you. One archer or knight is not going to be able to take them out. AOE attacks from the bomber will be a bit more effective though, and maybe even necessary if you face something like the above scenario.

A new dimension in balancing will also be the monster AI. The red rats above are using omniscient pathing, meaning they have a psychic awareness of the summoner’s location, and will find the fastest route there. This type of behavior might need to be used sparingly, because it’ll get overwhelming. A lot of lower level mobs will wander aimlessly, use Gauntlet style movement, or need vision to be aggroed. Boss creatures can also affect the AI of the lower level mobs, for example granting them awareness of the player’s position.