About MrDanH


Ludum Dare 24
Ludum Dare 23

MrDanH's Trophies

MrDanH's Archive

Blob’s Gotta Eat – A Gameplay Breakdown

Posted by
Tuesday, August 28th, 2012 3:01 pm

Firstly, if you haven’t played our game yet, please do so, it would make us happy: http://www.ludumdare.com/compo/ludum-dare-24/?action=preview&uid=11523

So, with a good night’s rest and return to normalcy behind me, I figured now would be a good time to reflect on some of the details of the game. This is mostly a talk about the AI and gameplay, since Flixel did most of the heavy lifting with regards to sprite pushing. This is also fairly elementary stuff since I wasn’t going to make any breakthroughs in AI in 72 hours, although is hopefully of interest to people who’ve not had much exposure to the area before.


AI – Wander Behaviour

The standard (red top) enemies in Blob’s Gotta Eat alternate between two behaviours : standing and wandering to a nearby point. Since it doesn’t exactly take a lot of code to make an enemy stand still, let’s talk about the wander behaviour. When an enemy decides to wander, it first makes a random choice between staying in the same room or going to an adjacent room (I’ll get onto what defines a room in a bit). For the final game we decided to make it a 50/50 chance of whether the enemy would change rooms. Once the enemy has decided on a room, they’ll pick a random spot in that room which is unobstructed by furniture and then walk to it.

We went with this behaviour so the enemies would have more of an interest in staying in a general area of a level, than if they had randomly picked a point in the entire level. I.e. you’ll see them hang around a room, maybe walk to a couple of different nearby points and then move to a nearby room, giving a better impression of having some interest in the environment.



For path-finding between two points, I simply built up a graph of all unblocked squares in the level and then used Dijkstra’s algorithm to find a route from their current location to their desired target (whether it was in the same or different room made no difference, since the graph covered the whole level). For those unfamiliar with Dijkstra’s, the basic jist is starting out from the nearest node (in this case 2D tile location) to the start location, you calculate the distance to neighbouring nodes, finalising the distance to the closest node, and then re-calculate distance to the neighbouring nodes of that, and so on until you’ve reached the destination node. There are plenty of good articles on the web on it, and a lot of places will also recommend the A* algorithm (which achieves similar results quicker) although for the sake of a small game there won’t be difference in it (I only needed to generate a path about once every few seconds for each enemy, and Djikstra’s is quicker to implement).

(Here’s a crude representation of the links between nodes in a section of a level. Each red line between two tiles is a connection that can be walked by an enemy)


Room specification

In our game, a room is defined by a series of rectangular areas (so a rectangular room can be defined with one area, an L-shaped room with two areas, etc) and the indices of rooms which it considers neighbours. Interestingly, since the pathfinding is totally separate from the definition of rooms, we can actually define any two rooms to be neighbours, and the shortest route between them will be treated as the accepted passageway followed when switching rooms, be it one square or a long snaking corridor through the level. When we are reading in the areas bound to a room, we iterate through each tile in that area and add to a list of unobstructed tiles for the room. This list is used when randomly picking a point in the room for an enemy to visit and means each tile has equal probability of being chosen regardless of room shape. It also means we can guarantee that the first tile randomly picked will be unobstructed; if we’d randomly picked a tile in one of the room’s areas, then we’d have to check if it was free and then potentially pick a new tile, and re-check if it was free and so on.

(This diagram shows the actual rooms in a certain level and their connections. Enemies are allowed to walk on other parts of the level if their pathfinding is quicker that way, but they will only head towards points in the defined rooms.)


Line of sight checking

To determine if an enemy can see the player we need to perform a line of sight check through the level, to see if a straight line from the enemy to player would collide with any walls or furniture. This is done by first calculating the tile coordinates of the start and end positions, and the angle of the line between them. Then from the starting position, via some good old fashioned trigonometry, we calculate whether the line has to travel least to reach a new x or y coordinate. At that point we update the current tile coordinate and see if the tile is unobstructed (in which case we return false in the line of sight check) and if not then we continue until the next x or y change. We repeat this until we reach the destination tile, and if we have never earlied out due to passing through an obstructed tile, then we have a clear line of sight.

(In the example above, I have added lines projected out from the enemy horizontally to the next x & vertically to the next y tile boundary, and from those points back onto the line from enemy to player. As you can see, the line going horizontally from the enemy maps onto the closest point on the line, so we’ll check the tile to the right first, and then repeat as necessary until we reach the destination tile.)

The only special case is when we pass exactly through the diagonal midpoint of two tiles (which can and does happen). In these cases, we test both tiles either side (the algorithm would normally snake around, going horizontally by one tile and then vertically by one tile leading to erratic lines of sight).


Putting it all together – Gameplay decisions

So, by default enemies would alternate between their stand or wander states, also checking each frame as to whether they could see the player. Before doing the line of sight check, enemies would check that the player is within 60 degrees of their current direction, thus allowing the player to sneak up on enemies when their backs are turned. On seeing the player, enemies would allow a grace period of one second before they actually open fire (the gun draw animation and “huh” sound effect indicates the start of this), thus allowing the player to experiment with how far out of cover they can be without being instantly punished. Once an enemy has opened fire, they will stay facing the player as he/she moves and is within line of sight, to prevent the player from just circling around to the back of the enemy. Once line of sight is broken, the normal behaviour in most stealth games is for enemies to head directly for the last-seen player location, but this would likely be a fatal move for the AI since they would probably move to the player’s attack range before they regained line of sight (and when under the time restriction of a game jam, it’s one less behaviour to implement).

You’ll also see a couple of different coloured enemies, to provide some variation in the levels. White coat enemies are set to have a zero probability of deciding to leave their current room when in their wander behaviour. These were added to counteract the random nature of the red enemies occassionally causing them to all bunch up in a single room (which is near impossible for the player to tackle, and makes the rest of the level rather uninteresting). Blue coat enemies consist only of the stand behaviour, allowing me to set the areas infront of them as guaranteed kill-zones for the player until the player has followed the level around to kill them from behind, forcing some structure to how the player tackled the level.

So, to conclude, for most of the jam’s duration, I coded the AI behaviours of the enemies, since these are what make or break a stealth game. The AI have to have means of detecting the player, be deadly when they have done so but still have some identifiable flaw which allows the player to take advantage of them. Hopefully you found this interesting (I apologise for the huge length), and please play the game:


[cache: storing page]