So over the past week I’ve been working pretty hard on getting the Ambience Demo finished, which is due for release this Friday. I thought I’d share some of the biggest problems I wrestled with over that time. A bit of context for people: I use GameMaker Studio for my gamedev projects, and I’ve been using various versions of GameMaker for about 10 years now. It’s a neat little program that doesn’t require much coding knowledge to get started with, so for new game developers it’s definitely a good place to start.
However, regardless of the gamedev engine we use, we all know we’re going to run into problems that set us back. Like when, for example, you’re making a platformer and trying to work out how your character manages to hang onto platforms with his nose – as was the case for a certain platformer game I made way back in 2007.
In the end, not knowing anything about collision boxes, I decided to keep that quirk in the game, branding it (rather unconvincingly) as “not a bug but a feature”. But in the big scary world of “real” game development, the bugs we run into are often so much scarier and more difficult to track down and destroy. It’s almost like every indie game developer’s the main character in an RPG where the bosses get more and more difficult to kill each time. (Heh, “GameDev Simulator”… that’s an interesting concept.)
Anyway, here are the three most difficult gamedev “bosses” I encountered in my own gamedev adventures this week.
1. Data structure write errors
Ambience uses data structures, in particular what GameMaker calls “DS Grids”, to deal with grid-based movement and procedural dungeon generation. This means that, instead of using instances (GM:S calls classes “objects”, and objects “instances”) to make walls, values in the “room grid” are set to particular values depending on what’s at that position – whether it be a wall, water, or ground which you can walk on. The game first detects any placeholder tiles that have been manually set beforehand to be walls or water, then converts those tiles to values in the room grid. (If it’s making a random dungeon, it sets the room grid values first, then places the dummy tiles where walls or water should be). The placeholder wall tiles are then replaced with pretty autotiles which the player sees.
Besides the room grid, there are two other grids: an “item grid” for player, enemies, and items; and a “seen grid” to store which tiles are in the player’s line of sight. Over the course of the game, these grids inevitably have to be read from or written to. While this system works well most of the time, there are rare occasions when GM:S tries to write to a grid value that’s outside the grid, for whatever reason. This is usually passable when testing the game, but it’s not OK for an executable which would throw an error and end the game. As a result, I spent many painful hours hunting down the sources of mysterious outside-grid errors that seemed to turn up whenever they got the impression that programming was moving too smoothly.
Thankfully, however, I managed to get rid of them all by the time I completed the demo. (I think.)
Don’t get me wrong – shaders are awesome. GM:S has a system for implementing OpenGL-based shaders for things like recoloring, blurring, or warping sprites and backgrounds, and it saves so much time and resources. They’re especially handy when you need nice visual effects on the fly, but don’t want to chew up too many system resources or memory in the process. I started learning a bit of OpenGL over the past two weeks or so, and made a simple HSV-recolouring shader for text and mugshots that recoloured anything except the colour white.
The process, however, was painfully longer than I expected it to be, for a few reasons. First and most obvious was my lack of experience with shaders. I’d never seen OpenGL before about two weeks ago, so using it for the first time after reading a handful of shader tutorials on the internet, you can probably imagine I was a bit shaky. However, this was combined with GM:S offering very little in the way of checking my OpenGL syntax and so on in the actual GM:S engine, despite having such a system for GML (the in-built, interpreted GameMaker pseudo-language). This meant that I’d look at my OpenGL code and everything would seem fine, until I compiled and ran the game and got blown out of my chair with a long list of compile errors, all to do with the shader code I’d written.
Now, this wasn’t just a result of lazy syntax on my part – GML gives programmers the nasty habit of using whatever syntax they like and letting GameMaker work out what they meant at compile time. Having used a handful of other programming languages in the past, I already knew the importance of having good syntax and formatting code nicely (and yes, those semicolons at the end of statements really are necessary). What still surprised me, though, was how darn picky OpenGL could be. Something as seemingly trivial as a comment that was judged to be out of place would throw a big red compile error. In addition, even after I meticulously ensured every line of shader code was fine and would compile correctly, the shader would still end up doing very weird things, such as turning everything black like some weird Gothic version of the Midas Touch.
Eventually, however, I did manage to get my OpenGL code working, after a long discussion with my code in the presence of an independent mediator and a substantial out-of-court settlement. But it’s fine now. All has been forgiven, and the shader works like a charm. Oh, and yes, shaders are still awesome.
One of the big things I used this HSV shader for was in colouring mugshots. This was very valuable, since many of the characters in Ambience had the same basic form but with different eye and body colours. With my HSV shader, all I needed was a single body sprite and a single “emotions” sprite to overlay on the top of it, then applied different HSV shifts to each depending on which character I wanted to draw. Easy!
What was more difficult was integrating this mugshot system into my pre-existing textbox and option system. My aim was to have the mugshot fly into view when a character was talking, then fly out of view once they had finished. Seems simple, but there were a bunch of unexpected problems that I ran into while trying to implement this. One such problem was having mugshots alternate between flying into and out of view with each line of dialogue, or just generating another mugshot with each line of text even if it was the same character. Also, sometimes after choosing a “null option”, such as a cancel button with no associated text that went with it, the mugshot would unexpectedly hang around and never leave the screen, leaving the player incapable of moving. While I could have just put some sort of forced text after every option to make sure that didn’t happen, I knew that wasn’t really a satisfying solution, so I’m glad I eventually sorted that one out.
On the upside, changing mugshot emotions were fairly easy to implement – I used an “annotated text” system that told the textbox and mugshot when to do certain things, such as a mugshot changing its facial expression, or pausing or slowing down text.
So there we go – three gamdev grievances I did battle with this week. Of course, these are only three of many more I’ve encountered, and probably only a fraction of the many more to come.
What are your top gamedev grievances you’ve encountered recently?