Consider this fairly typical scenario in a fairly typical dungeon crawler: You and an ally spawn in a room with a couple of enemies standing nearby.
You choose to wait it out for a turn. The enemies move closer. So far, so good.
The next turn, you choose to attack an adjacent enemy. The enemies fight back. Damage is dealt. But… but your trusted ally is still standing next to you, staring into blank space, without having dealt a single point of damage!
Such was the very atypical scenario I was faced with in the past week, which took me a good few hours to work out – and the solution left me pretty much shaking my head in disbelief.
Let’s break down what went wrong and how I was able to fix it:
Firstly, Pontus (the guy with the orange beard) is holding a weapon with a two-tile range. This means that he can hit any enemy standing above/below him, to the left or right, or diagonally at a range of two square tiles in all directions. It’s important to note that he can’t just hit any enemy standing two tiles distant – there are actually a handful of “dead zones” which he can’t hit, as shown in the graphic below.
Secondly, the “find an enemy to attack” script cut a very important corner it shouldn’t have. This script, as I originally wrote it, completely ignored the dead zones and just said “yeah, any enemy standing in that two-tile range will do”. Specifically, I used a couple of data structure functions which picked out the grid coordinates of the “first” enemy in that grid region – a bad idea in hindsight. Here it just so happened that there were two enemies in the region, and Pontus decided to try and attack, well, the one he couldn’t actually reach.
Once I worked that out, the solution was pretty obvious: check only the tiles that Pontus could reach! I used a simple “for” loop here which starts at Pontus’ current direction and sweeps around until it finds a “reach-able” enemy in the red cells. Once such an enemy is found, the game quickly checks if Pontus can actually hit it – in other words, it’s not blocked by a wall tile or anything – and then if that passes, Pontus gets the go-ahead for an attack.
The learning curve:
There’s one thing I got out of working through this problem, and that is that sometimes, a simpler approach isn’t the best one. I actually made a huge oversimplification in checking only for a single enemy in a single, large square region, rather than taking the blue “dead zone” cells into account.
That being said, though, when I originally wrote the offending script, the whole idea of “dead zones” never actually crossed my mind. So in a way, it’s understandable that this came back to bite me later on in the gamedev process.