Categories
Ambience Gamedev Grievances

Gamedev Grievances #37: Flying Over Oceans

With another hectic year of study over and exams done, I’ve finally finished my undergraduate studies in engineering! Which means I’ve also finally got back into the groove of gamedev and have been putting some of the finishing touches on Ambience. (Don’t worry, I definitely haven’t given up on Ambience at all! I’ve just been a lot quieter than usual internet-wise, which I know needs to change – I’m thinking about ways of improving that.)

In redesigning yet another cutscene, I stumbled upon a cool application of numerical methods which I thought I’d share. (For those who aren’t familiar with the term, numerical methods are a way of finding approximate solutions to complex equations which would be really hard to solve exactly.) In my case, I wanted to make a cutscene where the player is viewing a fireball travelling over water, from the perspective of the fireball.

Here, I needed an effect with a water background moving vertically down the screen to represent the water below the fireball. I already had a 32×32 water tile – check. But these water tiles needed to stretch vertically and move faster as they moved towards the bottom of the screen (“closer” to the player). So I made some horizontal “strips” of water tiles and tried scaling them and moving them down the screen. But then I realized something else: the strips of water tiles had to scale and move while also remaining attached to the strips of tiles above and below!

To achieve this, I made a data structure containing a list of y-positions on the screen. These positions would move progressively down the screen, going faster and faster as they went. When the bottom-most position went off the bottom of the screen, it would be placed back at the top. I could then draw each horizontal strip of tiles stretched vertically between two of these vertical points, and it would always remain attached to the strips above and below it. The diagram below shows how this all looks in practice:

That only left the vertical movement itself. On every frame in-game (once every 1/60 of a second), each point had to move down the screen a bit. If a point was closer to the starting point (the “horizon”), it had to move only a small distance; if it was closer to the bottom of the screen, it had to move a larger distance. So as time progressed, each point would be falling faster and faster. I chose to model this using a parabolic relationship between position (y) and time (t), like in the graph below, which is similar to how things fall under gravity.

Relationship between position (y) and time (t). The stars show the y-value points, and k is just a constant. I’ve drawn the y-axis with larger values downward, since this is how it works in-game.

But there was a problem: since I was working with multiple points, I couldn’t simply use an ever-increasing time variable to work out the position of each point every step. Instead, I had to calculate the next y-position of each point (y_{2}) based only on the current y-position (y_{1}) and the difference in time between each frame (Delta t, which in this case was 1/60 s).

Warning: Heavy Maths Ahead!

It turns out I couldn’t do this easily by playing with the exact equation for the parabola, either:

y_{1} = k t_{1}^{2}, y_{2} = k t_{2}^{2}

y_{2}-y_{1} = k left( t_{2}^{2}-t_{1}^{2} right)

y_{2}-y_{1} = k left( t_{2}+t_{1} right) left( t_{2}-t_{1} right)

y_{2}-y_{1} = k left( t_{2}+t_{1} right) times Delta t = ????

This is where the numerical methods come in! I approximated the change in y using the derivative (“slope”) of the parabola at the current value of y, like this:

y_{2}-y_{1} approx left. frac{dy}{dt} right| _{left(t_{1},y_{1} right)} Delta t

y_{2}-y_{1} approx 2kt_{1} Delta t

Then I rearranged the original formula for the parabola to get rid of that pesky, unknown time variable t_{1} :

y_{2}-y_{1} approx 2k sqrt{frac{y_{1}}{k}} Delta t

y_{2}-y_{1} approx 2 sqrt{ky_{1}} Delta t

So at every step, I updated the y-position of each point simply by adding 2 sqrt{ky_{1}} Delta t to it. (With one exception: for points at the starting point, the slope of the parabola was zero so the point wouldn’t move down at all! Instead I used the exact formula for the parabola to shift the points nicely off the starting line.)

Here’s a GIF of the numerical method in action (the points are shown as red circles):

Hey, nice! The red circles drop from the starting point and accelerate as they move downwards, as expected. And here’s what it looked like when I added the horizontal strips of water background between the points (with some other minor adjustments):

I then made some more adjustments including changing the speed and number of points and fixing that annoying “bobbing” top of the water level. And here’s the final(-ish) result:

(I guess learning all that maths in my engineering degree did come in handy, after all!)

Categories
Ambience Dev Diary

Dev Diary #14: An Itch That Must Be Scratched (More on Line of Sight!)

Just a quick little rant from me this time:

For some reason, line of sight and fog algorithms have just been an itch I always need to scratch. I’ve lost count of how many times I’ve revised that algorithm (but hopefully I’ve done so for the last time now!)

In particular, over the past week I began to notice that the line of sight would take a little while to update when moving from a corridor to a room, or vice versa. This caused a little bit of lag with each step. It wasn’t intolerable, but still noticeable when playing and so definitely not ideal.

After spending several hours tweaking and fine-tuning the line of sight updating algorithm, I figured out and managed to solve the problem. For some daft reason, I had originally told the game to update every tile on the screen with each step, which was slow and computationally intensive – hence the lag. So I re-designed the system (again!) to do what I should have done in the first place – only change the tiles that had actually changed visibility with that step. And, as expected, it worked! Walking smoothly through a dungeon corridor has never felt so good.

Moral of the story? Do things right the first time (if you can), so you don’t spend hours later down the track trying to fix up bugs from poorly implemented code.

(Incidentally, the function that was chewing up most of my processing time was the function used to find the ID of a tile on a particular layer. Which was unexpected, and a bit annoying…)

Categories
Ambience Dev Diary

Dev Diary #13: Pathfinding, Line of Sight, and More

I wasn’t really sure what to call this post, since I’ve been working a lot on tidying up Ambience but haven’t really been particularly stuck on one single thing to write a “gamedev grievance” about! So I figured I’d write up a short summary of the many things I got up to this week:

  • Fixed pathfinding. Well, sort of. I noticed that in large rooms, the game would sometimes lag at the start of a turn if the A* pathfinding algorithm couldn’t find a valid path to the player. One way of fixing this would be to run the A* algorithm branching from both the player and each enemy simultaneously, but this would require rewriting a lot of my code. Instead, I modified the code I already had to make it work a lot faster and more efficiently when finding a path. For example, I changed one of my data structures from a list to a map, which runs much faster in GameMaker: Studio. There’s still a bit of lag sometimes, but only in large dungeons with lots of enemies, and even then it’s nowhere near as bad as it was.
  • Fixed the battle log. I finally decided to implement turn announcements in the battle log, so the player has a better idea of what happened when (and also to give the game a more rogue-like feel overall).  My approach was a bit unconventional – since there are a variety of ways a turn can start in Ambience, I added the announcement for a new turn at the end of the previous turn – which made things a bit tricky. In the end, though, I think I got it working okay.

  • Modified room and dungeon layout. Now floors are bigger, with more rooms and more enemies – which means more fun!
  • Improved graphics – including several mugshots and the Cactus Crawler sprite. I’ve been meaning to fix up the latter for quite some time, but again only got around to it now.
    A wild Cactus Crawler appeared!

    Fixed the line of sight algorithm… again. I implemented the system I’m currently using a while ago, but I’m still having to fine-tune it occasionally. Specifically, when the player activates an Ambience and modifies the map – say by drying up pools of water using the Ambience of Sun – the game unnecessarily modifies some of the “corridor cells” to become “room cells”, giving the player too much line of sight extending into adjacent rooms. The solution: don’t change those cells into room cells. Easy!

  • The playthrough continues… I’m playing through the game as an ordinary player would to get an authentic idea of the game’s difficulty at each stage of the game. Along the way, I’ve discovered that Ambience is actually fairly challenging – even for me as the developer. At any stage, the player could go from being perfectly fine to on the brink of defeat in as little as one or two turns. I don’t mind the challenge, though. It’s all part of the roguelike nature, after all.

Categories
Ambience Gamedev Grievances

Gamedev Grievances #35: Generating Items

In my recent escapades with developing Ambience, I’ve spent several days (!!) adding over 50 new weapons to the game, each with its own strengths and weaknesses (and quirks). It’s been a big job, but now I get to reap the rewards and finally add them to the game.

Except… how do those items get generated in-game, anyway?

This was yet another system that I had implemented very early on and had all but forgotten about. So I decided to crack open my rusty old system to see if I could improve the way items were generated.

The Original System

When a dungeon is generated in Ambience, all the possible items for that dungeon are loaded into a grid data structure, along with their respective probabilities of appearance and the dungeon floors they should appear on. Then, an “evaluation” script looks at the grid and spits out the ID number of the item to be generated. This general idea remained unchanged, but what I did change was the way the evaluation system worked.

The original script used a long and rather dodgy iterative method. It would scroll through each item in the grid, and for each it would generate a random number from 0 to 1000 and compare this with the set probability of that item appearing. If the random number was less than the probability, that item was selected and the loop was terminated. Of course, this system had the potential to go on indefinitely without ever selecting an item, so a maximum number of trials (something like 2,000) was set to prevent this.

Even so, having to generate a random number for every item checked, as well as having to loop through the grid multiple times to find an item at all, made this system rather inefficient. In addition, if the probabilities of items was less than (or more than) 1000, some items would appear more or less often than specified. In other words, the system was just plain dodgy. So, off to work I went…

A Better Method

Getting rid of the iterations was top priority right from the start. I wanted to build a system that required the least computation time – that meant generating a single random number and scrolling through the data structure only once, while making sure that a valid item was produced every time. Here’s the solution I came up with:

  • The first thing I did was sum all the probabilities to give a basis against which I could compare the relative probabilities of each item. (Much better than having to manually make sure that all the probability values added up to 1000!)
  • I then selected a random number between zero and the probability total. That random number corresponded to the item I was going to choose.
  • To find that item, I went through each item in the grid in turn. Each item’s probability was added to a cumulative sum variable keeping track of the cumulative sum of probability values. Then, I checked: was the cumulative sum, including the latest item, bigger than the random number I chose? If so, then the latest item was the one to generate.

Here’s a diagram which probably explains everything a lot more easily than the wall of text above:

But wait! What about items on different floors?

Ohhhh, that’s right… we also have to check if the item we’re making can actually appear on that floor before generating it!

There are two ways I found to approach this. One was to choose a random number at the start, then go through the grid and check if the chosen item can be made on that floor before “approving” it. If that item can’t be generated on this floor (an “invalid item”, so to speak), choose another random item from those remaining and keep going.

Even though this method only needed to go through the grid once, the contribution of these “invalid items” to the sum of probability values was problematic. Since new items were only chosen from the list of remaining possibilities, this meant that items earlier on in the list were more likely to be skipped over in the end result – thus skewing the true probabilities. Even worse, the final item in the list could itself be an invalid item for that floor, which when chosen would result in no valid item being generated!

In the alternative method, which I ended up using, the script skimmed through the grid once at the very start to find any invalid items and set their probability values to zero – thus effectively removing them from the grid. Only then did the script choose a random number and go through the grid a second time to find the corresponding item. Although this meant that the grid had to be checked twice, it guaranteed that all items and probability values were valid every time. In any case, this was much better than checking the grid indefinitely in an iterative method!

Here’s another nice diagram summarizing the above:

The Learning Curve

For me, the biggest takeaway from this was to make systems simple, but not too simple. It’s nice to have a clean, functional item generation system that doesn’t rely on iteration – but at the same time, my original aim to “go through the grid only once” turned out to create more problems than it solved. The final script, while perhaps not as efficient as I had originally hoped, was still good enough to get the job done.

Categories
Ambience Gamedev Grievances

Gamedev Grievances #34: Taking Up Arms… Literally

A long-standing “feature” of Ambience has been the somewhat embarrassing fact that, well… none of the characters seem to have arms.

Some armless action from an earlier build of Ambience.

This was primarily because I’ve never been great at pixel art, so approaching this from a purely artistic perspective would have made for a very big and very painful job. First, I’d have to making sprites for every character walking and attacking in eight directions – arms included. Then I’d have to give the player the ability to hold and swing weapons. The thought of making a set of animated sprites for every weapon in the game was terrifying, to say the least.

Mathematically, however, this isn’t too difficult to pull off. I’d already used some clever maths to animate a hand swinging a weapon, as well as a generic creature attack. But I hadn’t yet done the same for a complete arm – mostly due to laziness.

But evenrually, I decided that enough was enough. I was tired of trying to convince myself of the normality of inexplicable floating hands. So I rolled up my sleeves and got to work making some arms for my characters.

Arms for Programmers

I designed my characters’ arms to be relatively simple. They consisted of two “nodes”, a shoulder and a hand – basically two points which the arm had to go through. My “floating hands” engine already gave me the framework for the hand node – how it moved when rotating or swinging a weapon, and so on – so I duplicated this for the shoulder node and tweaked it slightly.

Now each character had its own four nodes – two shoulder nodes and two hand nodes which moved with each character. So far, so good.

Then came the tricky part. I made up some quick generic arm sprites which were about the right length and shape, and – using a lot of trial and error – worked out how to rotate each arm sprite to always pass through the hand and shoulder nodes. The system I ended up with used the shoulder node as an immobile “pivot”, and rotated the arm sprite so that it always pointed from shoulder to hand.

It sounds simple (and it is, actually), but it was a bit tricky to implement in practice. One of the difficulties I faced was trying to get rid of unsightly, “sticking-out” pixels caused by very slight rotation of the arms in the resting position. (Rather than try and fix this using code, I ended up modifying the arm sprites slightly so slight rotation wouldn’t cause those sticking-out pixels.)

Note that sticking-out pixel on the player’s left arm.

While testing things out, I placed the player in a test room and moved his arms around in almost every imaginable way, just to check that everything looked okay. Which it did, in the end:

I’ve highlighted the hand and shoulder nodes in this one so you can see how this system works.

Considering that this was all done using code rather than actual sprites, I was fairly happy with the end result. Of course, I could have made it look much better by making some actual sprites for each arm, but as I mentioned before, that was a bit beyond my abilities. At least it’s better than having to put up with floating hands!

Categories
Ambience Gamedev Grievances

Gamedev Grievances #33: Pain with Primitives

A long time ago, on a computer far far away… there once was a development build of Ambience which introduced a neat swirling light pattern in the prison-based dungeons.

Throwback to the good old days… September 2016, to be exact. Wow, this really is old.

More recently, I decided that the light swirl was in need of some updating. It hadn’t actually changed much since the GIF above (except I might have changed the colour a bit – I’m not exactly sure). In particular, I didn’t like seeing the sharp edges of the light swirl and decided to make a smoother fade effect at the edges. How to do this, you ask?

Enter the primitives…

I’ve never liked primitives much. Not because they’re actually not useful – they really are – but mostly because I’ve always seen them as more of a 3D thing rather than a 2D thing. It’s kinda like that obnoxious relative you always hear about at family gatherings but never really meet, and when the time comes to meet them, you’re not super keen on it. If that makes sense…

Anyway, here’s my own simplistic understanding. A primitive is kinda like a basic shape (say a point, line, polygon, 3D blob…) which you define by setting the vertices/corners and letting the game engine draw it. Say you want to draw, I dunno, a triangle. No problem! Just set three vertex positions and tell the game to fill it in. How about a segment (like a pie slice) for our swirling light? Also easy-peasy! Just draw a bunch of really thin triangles squashed up next to each other, and if they’re thin enough, no-one will notice that your pie slice is actually kinda blocky at the ends.

Fading the edges

In the GIF above, I just made a single segment primitive, drew it and rotated it around and around to make the swirling light effect. But to fade out the edges, I couldn’t just rely on a single primitive. Instead, I needed to add some more primitives (think: thin pie slices) with gradually increasing opacities at the edges to surround the bright “main” segment.

My first thought was, “Why don’t I make twenty separate primitives for the segment and just fade out the ones at the ends?” But I figured that would be inefficient. I only needed separate primitives for the edges where the opacity was changing, not for the middle segment which had constant opacity. So, I had a plan: First draw the primitives at the leading edge fading in, then a single primitive for the middle segment, and then finally some primitives on the trailing edge fading out. Easy, right?

Well… sort of. There’s a price to pay for efficiency, it turns out. I wanted to make my light swirl code as elegant and efficient as possible, so I thought up a basic algorithm to do the job using a single for-loop and minimal different cases:

  • Set the initial opacity to zero.
  • Check where we are on the light swirl segment. If we’re near the leading edge, increase the opacity a bit. If we’re near the trailing edge, decrease it a bit. If we’re in the middle, don’t do anything.
  • Check if the opacity’s changed. If it hasn’t, then fine – just add another vertex to the same primitive (this keeps drawing the middle segment as a single primitive).
  • If the opacity has changed, then we stop drawing the primitive we were drawing and start drawing a new one with a different opacity.
  • If the opacity’s about to go below zero, then don’t draw any more primitives. We’re done! (The limits of the for-loop take care of this for us.)

The outtakes…

Because stuff always messes up when you’re doing these things.

The first form of the algorithm I tried drew the light swirl perfectly – with the exception of a single segment just before the trailing edge, which it refused to draw at all. You can see this pretty clearly in the screenie below:

Note that the light swirl (centered on the player here for debugging purposes) is rotating counter-clockwise – so the “leading edge” here is below the player and the “trailing edge” is to the player’s left.

Even when I changed the number of segments, the angle of each segment, and the number of fade in/out segments, the same thing happened. The segment two positions before the start of the fading-out segments simply refused to be drawn.

Since I had set the peak opacity to be 1 (that is, the middle segment was completely opaque), I couldn’t really see clearly what was going on. So I halved the peak opacity and had another look at the leading and trailing segments.

Aha! So while the leading edge segments are drawn normally, the two visible trailing edge segments are drawn with these weird gradients! Something was going on with the trailing edge segments overall. I’m not sure exactly what it was in the end, but eventually I gave up trying to salvage my code and just re-wrote it following the algorithm above. And it worked!

In this case, the arc draws out a 120-degree segment with 12 segments (making it nice and blocky), including 1 leading-edge and 1 trailing-edge segment.

Finally, I made the light swirl sit at a random corner of the view and draw the arc from there, so that the arc would “flash” periodically across the player’s field of view, as per the GIF at the top. (I also made the colour a bit “redder” compared to the top GIF.)

This is how it turned out in the end with 30 segments. It looks a tad choppy here (you can see the individual segments kinda clearly), but that’s just the GIF recording software – it’s much smoother in-game when running at 60 fps. I could make it draw more segments for an even smoother fade, but figured it probably wasn’t worth it.

Looks good!

The Learning Curve

There’s always lessons to be learned from solving these kinds of problems, so here they are:

  • As I mentioned above, in-game efficiency comes at a cost – which is usually time spent actually implementing the system (and tearing your hair out trying to squash those bugs). Generally, I’ve found that the more sophisticated and/or efficient your algorithm is, the longer it takes to make it work. But when it does work, it’s worth it.
  • This might be a no-brainer to some, but I find it helpful to break systems down into simpler ones that are easier to deal with when trying to identify and fix problems. In this case, it would have been really hard to fix the problems I had, if I had kept the swirling light flashing at full speed in the corner of the room. Instead, I made the light smaller, rotated it slower, centered it on the player, made it blocky by changing the number of segments, and adjusted the peak opacity and total angle swept out as needed until I (finally) solved the problem. Whatever it takes, I guess.
Categories
Ambience Dev Diary

Dev Diary #12: The Hard Work’s Finally Paying Off!

It’s been a verrrrrry long time since I’ve posted here… but I’ve made it through another semester of uni, got back to programming and things are really starting to look good!

Even though it’s been quite a while since an update here on this blog, I’ve been working on a ton of stuff Ambience-wise. Here’s a quick overview:

Proper text and storyline!

One of the things I’ve been busying myself with lately is doing some proper storytelling. Initially I shovelled a whole heap of placeholder text into the game just to get the basic cutscenes working properly (see below), but now I’m replacing it all with proper text.

Placeholder text lol

It’s amazing how much of a difference that makes. Now when I playtest, the story and the characters feel so much more alive, and even the gameplay feels smoother and more polished. In other words, it feels much more like I’m actually playing a finished game rather than searching some crappy application for bugs. I dunno. It’s exciting to see how two years of work on Ambience is finally starting to pay off.

Pathfinding that actually works!

Yeah, I’ve been meaning to fix this one up for quite a while now. The original pathfinding algorithm I implemented in Ambience wasn’t very intelligent at all – it basically involved enemies either wandering around randomly, or walking in the player’s general direction and hoping it didn’t run into a wall. Which was fine, when it worked. (Which was not very often.)

But it was a start, and, well, the algorithm stayed. For a year and a half.

But now things have changed, as I’ve brought out the big guns with a brand new A* pathfinding algorithm. And, oh boy, does it work. I even had to tweak the first dungeon quite significantly since the (formerly daft) Henchmen became so good at finding the player that the whole dungeon became way too hard.

https://twitter.com/RhombusGameDev/status/942183691727519744

More shaders!

I added a sepia shader for showing a flashback from an earlier part of the game. And what’s more, I also got the game to display multiple shaders at once. Yeah, that was a proud moment.

Sepia… and swirls.

New Guardians!

I also finally upgraded the sprites for all four Guardians, which I think turned out quite well – at least, better than the previous sprites, I’d say. On the topic of sprites, I also changed Zephyr’s outfit from a full-length dress to a simple top and skirt, made her outfit a little brighter and her hair a little darker.

New music!

I’ve made some tracks for the final boss of the main story – two, actually, depending on a decision that you make earlier in the game. I also managed to put together a fitting encounter theme for Foss – again, something I’ve been putting off doing and have finally done.

Post-game content!

I’ve toyed with the idea of putting in some post-game content for a while, and now I’ve decided to take the plunge and do it. The main emphasis will be on exploring some challenging dungeons, true rogue-like style, as well as liberating Foss’s few remaining prisons (if the player’s left any) and taking on Foss one last time. I might also expand the Vulcan-Zephyr subplot a bit here as well, but we’ll see.

So, what’s next?

The plan is to finish fleshing out the story, add post-game content, and – finally! – start thinking seriously about release. I also need to stop putting off making blog posts about my progress! It’s easy to think I’ve done very little work, when in reality I’ve actually done a lot and I haven’t really paid too much attention to how much I’ve done. For now, I’ll make sure to post semi-regularly (at least once a week), and especially when I’ve got something new and exciting to show.

For now, though, merry Christmas and I’ll be back soon!

Categories
Ambience Gamedev Grievances

Gamedev Grievances #31: When Allies Get Distracted…

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.

Categories
Ambience Gamedev Grievances

Gamedev Grievances #30: When Corridors Get Edgy

You know you’re a truly hardened indie game developer when you spend three hours trying to figure out how to stop your roguelite from generating corridors along the edges of rooms – and are hardly surprised it took you that long.

Here’s the deal…

The dungeon generation scripts for Ambience have gone pretty much untouched since I first wrote them nearly a year and a half ago (scary, I know). The general strategy is:

  1. Choose a few centre locations for rooms.
  2. Blow up some rooms at those points.
  3. Connect all the room centres with corridors going vertically, then horizontally from A to B. Simple, but effective.

Except… sometimes the game would try and connect two rooms with a corridor which would run along the edge of another room. This doesn’t sound like much of a problem, and when the player’s standing in the middle of a room, it isn’t.

Version 1. So far, everything looks good…

But, when the player moves to the edge of the room…

Did I say everything looks good? Yeah, scratch that.

“Ahhh! What happened?! Why can’t I see? Why is everything so DARK????”
— The player (probably)

In all seriousness, though, finding yourself suddenly wading through fog in the middle of a large room is more than a bit off-putting. To try and fix this, I experimented a bit with the line of sight algorithms and tried to get the game to fix the problem after the corridors had been dug out, with little success.

So I dusted off the corridor generation script. This script was actually surprisingly simple to begin with: where there’s a wall (or water) tile, dig out a corridor – first vertically, then horizontally.

Version 1 of the passage creation code (vertical corridor part; the horizontal corridor part follows and is analogous).

While this was simple, it wasn’t very smart. It couldn’t tell what it was digging next to – sort of like a prison escapee trying to dig a tunnel to freedom, only to end up going around in circles. So I rolled up my sleeves and got to work answering the question: How do I tell this script not to dig out corridors next to pre-existing rooms?

Checking perpendicular directions

The initial solution seemed easy enough: check the tiles perpendicular to the direction of “digging”. If one or both of these adjacent tiles is a room tile, don’t dig out a new corridor tile, but move on to check the next tile instead.

Version 2 code.

This turned out – well, okayyyy, I guess

Version 2 result.

The script still didn’t know not to dig out corridor tiles at the corners of rooms – hence the isolated corner tiles at the bottom-left and bottom-right. The script also didn’t know not to dig out tiles at the “turning point” of the digging operation, when the direction switched from vertical to horizontal – hence the single-tile bite out of the wall at the bottom.

(You can trace out the original path of the corridor fairly easily on the minimap. Notice how the corridor comes down from the top-left, hits the bottom-left corner, and goes out the bottom-right of the room.)

I fixed the latter problem first, by checking whether there were any adjacent corridor tiles when the digging “turning point” was reached.

Version 3 code.

The result:

Version 3 result.

Hey, nice! We got rid of the isolated single corner tile and the bites out of the room edges all in one go!

The only thing that still bugged me (no pun intended) was the corridors terminating at the corners of the room. While this was passable, it made the dungeon layout a bit too labyrinth-like and not very easy to traverse. So I decided to extend the corner-corridors by a single tile to connect properly with the room.

Making connections

In my first attempt at making the room connections, I checked for no room tiles adjacent to the previous cell that had been dug out (or checked), to determine whether the current cell should be dug out as a corridor tile.

Version 4 code.

This didn’t work out quite as I had hoped, as you can see:

Version 4 result.

Yep, this is the same room as before, believe it or not! The main problem here was that the script refused to dig out the entrances to corridors along the flat faces of the room. This actually made the extended corner connections the only connections between rooms, which was perhaps a little drastic. (Yeah, just a little.)

In my next attempt (round 5), I rearranged the logic a bit and went back to checking the tiles adjacent to the current tile. But I also let it dig out a corridor if the previous tile had exactly one room tile next to it in any direction, including diagonal directions. This would (hopefully) extend the corner corridors as well as dig out the flat-wall entrances.

Version 5 code.
Version 5 result.

The result here was striking: while the upper-left corridor tile had been dug out (and the upper-right one too!), the bottom-right corridor remained stubbornly closed off. (Oh, and the script took another bite out of the wall at the bottom, too. Perhaps it was hungry.)

This was the “a-ha!” moment when I remembered that the corridors were only being dug out in a single direction. In this case, the wall-edge corridor was being dug out from top-left to bottom-left to bottom-right, in that order (remember?) But the script was only checking the current and previous tiles, and not the next tile, to work out if it should dig out the current tile as a corridor (remember that too?)

In short, this meant that the script would always dig out the corner when entering a room, but never when leaving the room.

Looking Both Ways

So for my next trick – er, I mean revision of the script, I made the script look both forwards and backwards to decide whether to dig out a corridor tile. This would ensure that all corner corridors would be dug out successfully.

Version 6 code.
Version 6 result.

Looks much better! The only thing left to do was to get rid of the little “bite” of corridor tiles at the bottom-left corner, and my work was finally done!

The final version of the code.
The final result! (The overall floor layout looks pretty neat as well!)

The Learning Curve

Three hours is a long time to spend on a single problem – certainly long enough to give it the infamous title of “gamedev grievance”. So what did I learn from this ordeal?

  1. Find a systematic way of solving problems. At first I wasted a lot of time “guessing and checking” numbers of adjacent tiles, but when I stepped back and thought about the problem logically and systematically, I found a solution more quickly – and I could explain how and why it worked, making debugging much easier later on.
  2. Remain flexible and open-minded. Sometimes the first approach to solving a problem isn’t the best one, like desperately trying to fix up pre-dug corridors. In fact, the new solution (such as cracking open an old script with the boys) might be totally different to the way you’d “usually” approach such a problem. It’s a good idea to allow for that when debugging and problem-solving.
  3. Draw stuff! Yes, get out a notepad and pen and start drawing combinations of tiles! Think about how your algorithm will respond to any particular scenario you can think of. I found that visualising the room layout and searching for patterns helped me find the solution much more quickly – and even when it didn’t, it gave me new ways of thinking about the problem and new ideas for improving the algorithm.
Categories
Gamedev Grievances General

One Year of Gamedev Grievances: The Top Five

Just last week I hit a big milestone – one year of blogging my experiences in developing Ambience! It’s been a really exciting, rewarding, and sometimes frustrating journey so far, and I’m sure that’s set to continue. As I reflected last week, it’s amazing to think how far things have come since I started working on Ambience.

Over the past year I’ve also chronicled many of the biggest hurdles I’ve overcome during my gamedev experience. And so, for my one-year bloggiversary, I decided to go down memory lane and pick my personal top five “Gamedev Grievances”.

This was actually a really hard list to compile, simply because there were so many potential contenders for the top spots! But in the end, I finally managed to pick out five grievances I look back on with pride. (Or dread.)

5. Dynamic Terrain (GDG #20)

In Ambience, players have the ability to manipulate weather conditions to dry up rivers, blow away leaves, and open up new passageways and shortcuts. I’m actually kinda proud of how this mechanic opened up so many new gameplay possibilities and made Ambience just that extra bit more interesting to play. That being said, however, it was somewhat of a pain to implement – especially in tandem with my line of sight algorithms (but more on that later.)

4. Would the Right Data Type Please Stand Up? (GDG #9)

Every time I look back on this one, I can’t help but smile wryly (while screaming internally and wishing I could bash my head against a wall). The goal: implementing a new menu system. The problem: activating Ambiences simply wouldn’t work after the new changes. After hunting through my code for a while, I eventually found it by accident: GM:S was treating an integer argument as if it were a string! 

What’s even more interesting is that after I published that post – complete with my rant about dynamic typing in GML – I received a bunch of messages from people telling me about all these other programming languages that also use dynamic typing. (Python! JavaScript!) This led to the unexpected, and very valuable, realisation that I could learn a lot from the broader community of game developers as well as from my own experiences.

3. Sword-Swinging Maths (GDG #12)

As an engineering student, I love it when I get to use maths in my gamedev stuff. One notable example was when I was designing the attack animation for characters with hands. I spent a lot of time (and had a lot of fun!) working out how to make the player’s hand travel along the loop of a polar curve. I also varied the angle of the held weapon as the hand swung along its trajectory to make it look more like a vicious slash.

Not only was this really fun to implement, but I’m still using this fundamental code in the current version of the game as it stands now. (And it also showed that the maths you learn at school and uni can come in handy, after all!)

2. Pixel Art By A Non-Pixel Artist (GDG #14)

If there’s one thing I keep saying on this blog, it’s: I Am Not A Pixel Artist!” Needless to say, as a solo indie game developer, pixel art eventually becomes a necessity to get a game up and running. So when I decided it was time to make some new mugshots to suit my new character designs, I decided to document the results and evaluate how I, a non-pixel artist, fared doing my most dreaded past time: pixel art.

The resultant write-up became my most viewed post of all time.

I’m not quite sure why this post became so popular. Most likely, people just wanted to see how I went – or more realistically, how badly I did. The funny thing, though, is that I don’t think I did that bad a job. Even the above screenshot of Zephyr’s mugshot, which I’ve since improved quite a bit, didn’t turn out as horrendously as I thought it might.

In any case, the readers seemed happy, and I was happy. Win-win.

Now, before I announce the number one gamedev grievance of the past year, I also thought I’d draw some attention to another very popular post:

Bonus: “I’m an Indie Game Developer, and I’m Proud”

Though not a Gamedev Grievance, I thought this post also deserved to be on my list of highlights. At first I always found it a bit weird telling people that I’m an indie game developer, but over time I’ve come more to terms with that idea and am more comfortable talking about it. You can read more about it in the original post (link above).

Now, without further ado, the award for number one Gamedev Grievance goes to…

1. Clearing the Fog (GDG #20)

Oh, man. This thing practically gave me PTSD – especially when it came to the days and weeks of debugging that followed.

The aim was to design a line of sight algorithm that could reliably deal with non-rectangular rooms in dungeons. I eventually came up with an algorithm that worked by drawing expanding concentric rings around the player, which generally worked okay…

Okay, that is, with the exception of a million bugs and some very strange results that appeared in room after room after procedurally-generated room…

And then, when I implemented dynamic terrain and had to work out a way of integrating it with the line of sight algorithm, things became even messier…

Procedural generation can be both a blessing and a curse. It’s a blessing when it gives the player a huge range of different scenarios to handle, keeping gameplay fresh and exciting (and also making the whole “level design” thing much easier). It’s a curse when it gives the player such strange results that gameplay becomes confusing, frustrating, and at worst unplayable.

As the game developer, the power of proc-gen is in my hands – and with it, a great and terrible responsibility has fallen on my shoulders. That responsibility is, simply put: “Don’t mess up.”

It’s harder than it sounds.