thomastc

thomastc #9

Joined a year ago

Bio

Entries

Latest posts

thomastc

This is the second part of this postmortem. Catch up on the first part here.

Sunday morning: controls and gameplay

We have fire, we have buckets of water, we can get the buckets to the fire… now what? Throw 'em, of course! So if a peep in state PASSING sees that its destination cell is not manned, but rather flammable, it will throw the water onto the fire. (It will also do this if there is no fire. I played with the thought of keeping houses wet to prevent them from catching fire, but didn't get round to implementing this.)

The throw animation is similar to the bucket-passing animation: simply move the bucket in the direction of the destination tile. However, it moves a lot faster to make it look like it's being thrown. Also, the bucket is rotated in the direction of movement.

When the bucket reaches the edge of the tile, the peep destroys it and emits a throwing signal that the level_base.gd script listens for. Upon receiving this signal, a water animation sprite is added to the objects node, and the fire in the tile (if any) has its size reduced by 1.

I also changed the control scheme slightly, to allow the player to arbitrarily change the direction in which peeps pass buckets. This meant that changes are no longer triggered by dragging on a tile, but by dragging from one tile to the next. At the moment the mouse cursor crosses the border between two tiles, the destination of the source tile is set to the target tile, and peeps are summoned as described before. This allows for any change in bucket routing.

Unfortunately, it doesn't allow the player to liberate peeps for deploying them elsewhere. Clicking or dragging with the right mouse button might have been a logical gesture for this, but I wanted this to be workable on mobile devices as well, so I went with the slightly unintuitive "drag from unmanned cells into manned cells" instead. It's not great, but it works well once you understand it.

Now I had an actual game! While playtesting, I quickly realised that it had a major flaw: either you need to start off with a huge number of available peeps, or you lose quickly when the fire outgrows your tiny army. My quick and dirty solution was to add some windows to the houses:

A house with light behind its windows contains one peep. As soon as the house catches fire, the peep runs outside and is available for the player to deploy in fighting said fire. This works well, because it gives me a way to control difficulty by balancing the number of occupied and unoccupied houses. (I didn't do much of such balancing, so in the later levels, all houses are occupied.) Also, the new peep is spawned close to the location where it's likely to be needed, leading to a minimum of player frustration.

Sunday afternoon: chrome and polish

By "chrome" I mean all the window dressing that isn't part of the game proper, but is needed anyway. Things like menus, intros, game-over screens, level switchers and a HUD.

The first step was to detect when all fires were put out, and throw up a win screen. By counting how much of the houses were still standing, I could even assign a score. High scores are being kept in a simple INI-like file, read and written by Godot's built-in ConfigFile class.

On the win screen, I put a "next level" button, so I had to make sure there actually was a next level. While I was at it, I also put level switching buttons on the main game screen. I didn't bother locking future levels, because any level can be completed (albeit with a terrible score) just by waiting for the entire city to burn down.

I also added a pause button. I don't usually bother with this during game jams, but in this case, I saw it as a core gameplay feature. It gives the player a chance to catch their breath, and give new instructions to their peeps at leisure. Adding pause functionality was a learning opportunity for me, but Godot's built-in pause functionality turned out to be exactly what I needed. You can simply tell the entire scene tree to pause and unpause. Each individual node can be configured to be processed or not while the tree is paused, so fire stops spreading and your peeps stop moving, but the GUI continues to be responsive.

This was also when the intro text was added. Godot's built-in AnimationPlayer is absolutely wonderful for making and tweaking these sorts of transitions. Any property can be animated, including user-defined ones. Animations can call functions on cue. And all this is going to get even better in the 3.1 release.

Polishing peeps

So far, I'd used only one, static sprite for peeps:

This was in the category of "good enough for now, maybe polish later". I should have done sound effects first, but I'm always a bit hesitant because I'm inexperienced with them (and hate Audacity), so I wanted to improve the way the peeps look and feel.

I started by splitting them into a head and a body, and colouring the body grey:

By modulating (multiplying) the body with another colour, I could achieve a varied looking population with very little effort. For more variation, I also added a second head shape, based on what a quick Google image search led me to believe was appropriate headwear for women in 17th century London:

I didn't bother with a separate body sprite for women, figuring that the generic coat was sufficiently unisex. And finally, I duplicated all these and used them as a basis for sprites facing left, right and up:

It's worth noting that all these sprites are still 16×16 pixels, so I can just put them directly on top of each other without having to bother with positioning and alignment.

Implementing all these sprites in the game was easy, because peep.tscn was already its own scene, decoupled from the rest of the code. So I could just add a body and head sprite node, as well as a set_direction function to point the peep left, right, up or down.

A slight difficulty arose with the orientation of peeps passing buckets: I wanted them to pass buckets from side to side (left to right), but didn't want them to stand with their back to the player and obscure the water buckets. My current solution for this isn't perfect, and will sometimes cause peeps to stand clumsily with their back to the peep who's passing them a bucket. But it's good enough. I also added an offset so that peeps would stand back slightly, and pass buckets in front of them rather than through the middle of their sprite.

Then for the final sprite, the star of the show, these 4 pixels:

The peeps' hands are actually two separate sprite nodes, and they're being moved around depending on the state the peep is in:

  • Panicking peeps wave their hands in the air with opposite phase: both hands go inwards and outwards at the same time.
  • Cheering peeps (when you have won) do similar, but in phase: their hands go left and right at the same time.
  • Peeps heading to their post, or waiting for a bucket, have their hands at their sides.
  • Peeps holding a bucket have their hands positioned slightly above the bucket.

All this is rather hacky, last-minute code, but we were nearing the last minute, so this was entirely allowed. Fun fact: to hide the right hand when the peep is facing left and vice versa, I don't call hide(), because that would mean I have to remember to call show() again later on. Instead, I simply set its position to 99999, 99999 so the hand is drawn offscreen.

Tutorial

I realised that the control scheme wasn't extremely intuitive, and would need some explanation. What better way to convey this than having people learn while playing the first level? So I drew some arrows and slapped them onto a separate bunch of tile maps, which are only present in level_01.tscn:

The way the game detects when to proceed to the next tutorial instruction is rather horrible. It checks after every drag operation whether this ended up in a tile containing a tutorial arrowhead. These arrowheads don't have consistent naming, so it's just a hardcoded list of tile names and directions:

match [tile_name, to_cell.coord - from_cell.coord]:
    ['tutorial_5', Vector2(0, -1)], ['tutorial_7', Vector2(1, 0)], ['tutorial_8', Vector2(-1, 0)]:
        tutorials.pop_front().queue_free()
        $tutorial_timer.start()

This system is probably broken in a fair number of ways, but as long as the player does as they're told, they won't notice. And if they do differently, they probably already know how to play anyway.

Sunday evening: sound effects and tutorial

Less than two hours to go until the deadline, and the game was still completely silent. I let a mute game happen to me last time, but not again! Unfortunately I lack even a basic standalone microphone, so I used the mic in my webcam to record everything.

The fun part was, of course, the yelling voices. Six variants of "aaah" for panicking peeps (three of which contributed by my girlfriend, but I didn't deem this enough of a contribution to switch to the "team" category), and several "fire!"s for peeps being driven out of their house by the fire. I used the Equalize filter in Audacity to cut the bass tones, which gives these sounds a more "outdoors" quality. In the Godot engine, I randomly changed the pitch plus or minus 20% to get some extra variety.

The alarm bell sound was made by hitting a steel pot lid with a wooden spoon. It was difficult to make this loud enough while preventing clipping. I tried with a Compressor filter, but that didn't help much. But the perfect is the enemy of the good enough, so I moved on.

The splashing sound was just me making funny noises, with the pitch shifted down to more closely resemble a bucket full of water rather than a mouth full of tongue and saliva (you're welcome).

The hissing sound that you get when water hits fire (actually, come to think of it, also when hitting a non-burning house…) involved actual fire to make. I heated up an empty pan on the stove, carried it over to the webcam on my desk, and poured cold water into it to make it sizzle. It didn't sound nearly the way I had imagined, but with some more pitch shifting I think it turned out quite well.

The crackling sound that the fire makes is just me crumpling up a page from a newspaper. In context, it's surprisingly effective. To make it loop, I didn't even bother to do any kind of crossfade; it turned out that the loop point in a sound full of clicks, pops and crackle is undetectable anyway. In the engine, this sound is continuously looped, but its volume is changed based on how much fire there is on the screen.

Finally, two of the sounds were created entirely inside the computer. The "ta-daa" sound you hear when you win a level was made in LMMS. It's just a C major chord played over two octaves on a brass-like synthesizer instrument.

For the music that plays during the intro, I was orinigally looking for a moody droning instrument. But while browsing the LMMS instrument library, I found this deep bell sound that seemed fitting. I started experimenting with simple melodies, and soon it clicked: it had to play the Westminster Quarters. This is the melody played every hour, on the hour, by Big Ben in London. Admittedly, the tune hadn't been written yet in 1666, but I'll allow for this anachronism for the sake of awesomeness.

And that's it!

This has turned into a pretty long read, but I hope it's given you an idea of what went into making The Great Fire. And maybe you even learned something. I certainly did!

This article was cross-posted from my blog.

thomastc

I was really happy about the shortlist of potential themes, so I knew this was going to be a fun one. The winning theme, "Always growing", was my second choice.

In this article, I'll describe beat by beat how my entry, The Great Fire, came to be. It will get a bit technical in places; Godot users might be interested in reading along in the source code. I might even throw in some tips for beginning game jammers.

But first, let's take a look at the end result:

You can play the game in your browser here.

Friday night: brainstorming and planning

The jam started on Friday at 21:00 in my time zone, so there wasn't a lot of time before bed (I like to get a good night's sleep). I just did some brainstorming, which resulted in this mind map:

As you can probably tell, I was still leaning towards a plant-based theme at that point. This all changed while I was mulling things over in bed, and I went to sleep with a fairly well defined picture in my head of how I wanted the game to be.

I'm definitely a "planner" in this respect. I may not write up my plans in great detail, but I need to have a clear objective in mind and a reasonably clear path of how to get there. And the great thing about working solo is that I don't have to waste time communicating this vision with anyone else, or – eek – reaching some kind of agreement about what we're going to do.

So in this case, the control scheme (drag to create and change a route), graphics style (pixel art), AI (peep behaviour) was all thought out in advance. Even the resolution was decided on: 480×270 (a 16:9 ratio, useful for a possible mobile port later) with 16×16 pixel tiles, for a grid of 30×15 tiles plus a 30-pixel top bar. That would give me enough room for sufficiently large cities, without having to build controls for scrolling the map.

Having outlined the plan in my mind, I kicked off Saturday morning by writing down a to-do list. Very little was added to that list during the jam, and all must-have items were crossed off, which shows that I got the planning part very much right this time!

Saturday morning: artwork

I had already decided to use the Godot engine again, so I set up an empty project and created a Git repository for it. Always use version control, folks!

You're going to need final graphics at some point during the weekend, so there's no point wasting time on creating placeholder art. Just create stuff that's good enough to be final, and if you have time left over (* sarcastic laugh *) you can always polish it more. The to-do list already contained a list of sprites I was going to need, so I could just sit down with Gimp and draw them:

A few things of note:

  • The transparent blue arrow evolved into the smaller, visually less cluttered chevrons that you see in the final game.
  • The fire sprites have 5 stages, each consisting of two animation frames. These frames are just mirror images of each other. I could have mirrored them in-engine, but by doing it in Gimp, the path to a fancier animation remained simple.
  • The brown arrow was meant to indicate a wind direction. I failed to write "wind" on the to-do list though, and almost forgot about it altogether, so this remained unimplemented.

I had some more ideas on the "nice to have" list that might require additional sprites, but this set was enough to get me started with coding.

Saturday afternoon: base code and AI

Godot lets you create tile maps, although the process is rather clunky and limited compared to using an external editor like Tiled. But doing it in Godot allows for quicker iteration and saves you the trouble of writing an importer, so in a game jam it's a no-brainer. So I quickly whipped up a small testing level, which later evolved into Level 1:

This level_01.tscn scene inherits from a level_base.tscn scene, where I'll add all the scripting. Basically the only thing that changes from level to level is the tile map. The base scene is still really simple at this point:

(Note that the objects node is still a plain Node2D at this point. I later changed it to a YSort node to make peeps and other objects get rendered in the correct order. It's a nice feature to have built in, although a bit limiting in that you can't configure the sorting.)

The base scene has an exported property that lets me configure the number of peeps you start with from within the editor:

export (int) var initial_peeps = 0

I later replaced this feature by a second tile map, named init, where I could place peeps and fire manually. This map is be processed into actual nodes at startup, and then disposed of.

Data structures

As to data structures, I copied and adapted grid.gd from a previous project. This is just a 2D array, with some additional goodies, like:

  • get(coord) returns some predefined default value if the coordinates are out of bounds. Saves a lot of bounds checking code everywhere.
  • neighbors(coord) (I use American spelling in code, sue me) returns an array of neighbouring coordinates to the left, right, top and bottom of the given coordinate pair. Very useful for many things, including pathfinding and fire spreading.
  • get_cell_center(coord) to convert from grid cell coordinates to pixel locations. It's not very clean to put this here, but it is very convenient, because the grid gets passed around to many places where this information is needed.

In this case, the grid was a grid of cells, of the class cell.gd (in GDScript, a file is implicitly a class). The cell contains a lot of useful data, like:

  • is_walkable: a flag for the pathfinding algorithm.
  • is_water: whether we can draw buckets of water from this tile.
  • peeps: an array of peeps that are currently in this tile.
  • destination: coordinates of a neighbouring cell that buckets should be passed or thrown towards.

Having separate boolean flags like is_walkable and is_water is more flexible than having a single enum like tile_type. Now I can have water sources that peeps can walk on, if I wanted to. (A fire hydrant, for example.)

At the start of each level, the tile map is parsed, and these cells are created based on the names of the tiles. (Consistent naming for your tile sprites helps here.) At this point we create some peeps and add them to the objects node. Each peep is a scene, aptly named peep.tscn, with a script peep.gd attached to its root node. Inside that script is where the magic happens.

Peep AI

The AI is controlled by a sort of state machine. It starts out in the PANIC state:

enum State { PANIC, MANNING, PASSING, CHEERING }
var state = PANIC setget set_state

A panicking peep will run around in the streets randomly. But I didn't want a "pure" random walk, because it leads to a lot of back-and-forth movement. Instead, I implemented a kind of randomized pathfinding. We generate a route of 4 to 8 tiles long, from the current location, taking care not to visit any previous node on that route. This route is stored as a queue of tile coordinates (Vector2 type). The _physics_process function simply moves the peep towards the next point in the route, and pops the point off the queue once it's reached. This sort of algorithm sounds complex, but I've done such things so many times that I can often write them correctly with my eyes closed.

With panicking peeps running around in the streets, it was time to give them something useful to do. This required me to implement the _input function in the level_base.gd script. It converts mouse coordinates to tile coordinates, and toggles the manning property of the dragged tiles accordingly. (I apologise for the mild sexism in this variable name. I couldn't think of a more appropriate word.) If a tile switches from being unmanned to being manned, we run a flood-fill (breadth first search) algorithm from that tile, to find the nearest idle (panicking) peep and call its man_cell function. The peep then switches state to MANNING and executes its own pathfinding algorithm (again a BFS) to plot its course. (I could have changed the algorithm that finds the nearest peep to also calculate the peep's route, but having a separate pathfinding function seemed useful. I didn't end up using it elsewhere though.)

Next up: passing buckets of water around. Once a peep reaches its designated cell, it switches to the PASSING state. While in the PASSING state, it passes any bucket it holds on in the direction that its cell's destination indicates. If it holds no bucket, it looks for a neighbouring water tile and creates a new bucket from it. The buckets are actually child nodes of the peeps themselves, so they are being reparented each time they change hands. I suspect it might have been slightly easier to make them sibling nodes of the peeps instead.

Saturday evening: fire!

As the final thing on Saturday evening, I implemented fire. Fire nodes are also added to the objects node in the level, and a reference to the fire node is also stored in the cell for quick lookup.

Spreading of fire works as follows. Each fire has a size of 1 through 5, corresponding to the sprites you saw earlier. Every 3 seconds (plus or minus 30% randomly), the fire "grows". This doesn't mean that its size increases by 1, though. Instead, for each size, we have a predefined "growth radius". A random tile is selected within that radius, and if that tile is flammable, the fire on that tile is started or embiggened.

  • At sizes 1 and 2, this radius is 0, so the only candidate tile inside the radius is the fire itself. So up to size 3, a fire cannot spread, only grow locally.
  • At sizes 3 and 4, the radius is 1, so the fire has a 20% chance of spreading to each of four neighbouring tiles and a 20% chance of growing itself.
  • And at size 5, the radius is 2 and the fire can jump across the street — as a player, these are the fires you really want to watch out for!

Also at size 5, the fire has a 50% chance of causing collapse of its building (and itself). This prevents fires from spreading too rapidly. Allowing such collapses to happen also gives the player a dangerous, but effective way of creating fire breaks.

That's how far I got on Saturday, with still lots of work ahead of me on Sunday. But this is turning into a pretty long article, so I'm going to break it up into two halves. The second half is going live here on Wednesday. Stay tuned!

This article was cross-posted from my blog.

thomastc

thomastc

I don't always make a mind map, but when I do, I make it self-referential.

I haven't decided, but am just posting this here in case anyone is stuck for ideas. I'm leaning towards something plant-based right now.

thomastc

Got the weekend schedule all cleared.

Last time was my first game using Godot, and it was a good experience so I'll probably use Godot again.

Other tooling: Inkscape? Gimp? Krita? jfxr? Audacity? Tiled? Whatever it takes, I suppose.

thomastc

That's "I'm in!" in Dutch, as I'm sure you all know. Can't break this Alakajam streak!

I'm going to be using Godot 3 for the first time, because I want to see whether it would be suitable for larger (and possibly commercial) projects. I only did the Pong tutorial before, and made a bouncing Godot logo as a test project, but that's all, so I'll have to figure it out as I go along. On the other hand, I've done this sort of thing before: Morphing Maria was my first ever HaxeFlixel game, and that is still one of my best jam games ever.

For graphics, I'll probably be using Krita with my Wacom tablet. I have done this once before, on Happy Chicken Farm, which turned out alright as well. If I decide for a more abstract art style, it'll be good old Inkscape. Pixel art would probably be Gimp, but I'm not hugely fond of either Gimp or pixel art. One idea I've been toying with is using real-life photos (taken during the weekend, of course), but I'll have to see if that would fit with whatever game I come up with.

And then audio, my Achilles heel… I would really like to get away from the likes of sfxr (or my own spin-off, jfxr), but I don't have a proper microphone nor recording/foley skills, so we'll see how that goes. Maybe I'll do some "a capella" vocal sounds, as they usually end up somewhere between charming and hilarious – the entirety of that scale is good territory to be in.

For music, if I feel inspired, I'll make a soundtrack in LMMS on day 1 already. I find that it's a good lithmus test: if I can stand listening to my own music for the entirety of day 2, it's probably not entirely awful.

Anyway, time to tune into the announcement stream. Veel succes allemaal!

thomastc

This is a cross-post from my blog, where you'll find many more in-depth gamedev-related articles.

Mixium is a puzzle game in which you mix liquids to achieve a particular ratio. The trouble is: your beakers don't have any scale on them, so you can only fill them to the brim or empty them into a larger beaker.

Mixium was my entry for the 1st Alakajam! game jam competition. I entered in the Solo division, which means everything has to be made by a single person from scratch within 48
hours. It ranked 5th place overall, 3rd place in the Theme category, and 1st in Gameplay! Even though Alakajam! is still a relatively small event (there were 36 contestants in the Solo division), this is a new record for me, and one that will be hard to beat as the community grows.

Here's a little postmortem of how things went.

What went wrong

  • I didn't have a free schedule this weekend. The competition ran from 21:00 on Friday until 21:00 on Sunday, but all of Friday evening, Saturday morning and Sunday evening were taken up by social activities. This, plus the need to catch up on some sleep, left me with about half a weekend to make this game. If I'd had more time, I would have added a nicer background (a drawing of an alchemy lab, animated if possible). I would also have added some sound effects: just pouring and a poof sound when you make something would already have made a big difference in game feel.

  • The original idea was about mixing metals into alloys, and I had chosen the punny name Alloy Vera for that reason. But as I ended up with all sorts of random liquids, whose names mostly end in -ium, I changed the name to the more appropriate Mixium instead. I still like the original name better, because it's catchier and it implies a person whom I could have given some character through her journal entries. On the other hand, you'd have ended up with much more boring substances if all you had to mix were metals.

  • Because I was afraid of being bitten by floating-point accuracy problems, I started by implementing a fractions library (there doesn't seem to be one in Haxe). This in itself was easy enough, but because I was expecting to need a lot of operations on these, I wanted to give my Fraction class overloaded operators. Turns out, operator overloading in Haxe is stupidly limited: it can only be done on abstracts, and like most of Haxe, only documented in the form of some examples. I couldn't get it to work, and eventually just went with regular add, subtract, multiply and divide methods. I should not have wasted so much time on this; I guess I've been out of game jams for too long and was having trouble shaking off my “clean code” mentality.

  • Then it turned out that pouring fluid back and forth a lot would result in overflow in my Fraction class. Instead of also coding up (or downloading) a BigInteger class, I decided to just use regular Floats instead, and compare them with some tolerance. Yes, this means you can solve some levels by just pouring back and forth until the mixture is close enough to the target ratio! But I deemed this realistic, and a nice “think outside the box” loophole.

  • Another technical problem I encountered is that the html5 export of the game did not render the fluids correctly. Actually, it didn't render them at all. HaxeFlixel is supposed to be fully cross-platform, but (like Haxe itself), it doesn't deliver on this promise. I don't remember the exact problem, but I did spend over an hour trying various combinations of rendering sprites to other sprites in order to make it work in the browser. I really should learn to work with the underlying OpenFL library directly, which has fewer such bugs, and they're getting fixed more quickly. HaxeFlixel lets you pick between “legacy”, which gives you an old and buggy OpenFL 3, and “next”, which gives you the new and great OpenFL 4, but with lots of bugs in the HaxeFlixel wrappers instead.

  • A bug: when pouring an empty beaker that once contained something into a nonempty one, the fluid disappears in both; and when pouring out such an empty “dirty” beaker into another empty one, it crashes entirely. This turns out to be due to a division by zero bug, which I'm normally really wary of, so I don't know how this slipped through the cracks.

What went right

  • Despite the shortage of time, it's still a finished game, with a beginning, an end, several puzzles with a gradually increasing level of difficulty, consistent graphics, and some background music.

  • This is the second game that I've ever made music for. It's not exactly Mozart, but it somehow still made 13th place in the Audio category, which must mean that people didn't hate it too much. Bosca Ceoil was new for me, but it's super easy to work with.

  • The difficulty curve seems to be alright, judging from the feedback. This was largely luck. I made most of the levels by copying the previous level and changing it to add a twist, but it was tricky to make sure that the changes didn't make it trivially solvable. For example, if you have two spare beakers that differ in volume by only 1, you can make pretty much anything with them. I also found that the more beakers I added to a puzzle, the more options the player has and the easier the puzzle gets.

  • I finished the splash screen literally in the last minute before the deadline. It doubles as a tiny tutorial, because you have to click the beaker to continue, teaching the player that beakers can be clicked. It would have been better to make them light up when hovered, but there was no time for that. (I initially had them move upwards when hovered, but that got too distracting, and there was no longer a distinction between the ‘hovered’ and the ‘selected’ state.)

Thanks to everyone who played, commented and voted! Here's the results page with comments, and here's the playable game. Enjoy!

thomastc

I usually try to keep my weekends clear for game jams, but alas, sometimes life does get in the way. That left me with Saturday afternoon/evening and Sunday until mid-afternoon to build something:

The original idea was about mixing alloys, and I had chosen the punny name Alloy Vera for that reason. But as I ended up with all sorts of random liquids, whose names mostly end in -ium, I changed the name to the more appropriate, but probably less catchy Mixium instead.

Mixium is a puzzle game in which you mix liquids to achieve a particular ratio. The trouble is: your beakers don't have any scale on them, so you can only fill them to the brim or empty them into a larger beaker.

There are seven levels, and I found it difficult to create more: the more beakers you add, the easier the game tends to get, because there are n² potentially useful combinations in there. I have no idea whether the levels are too easy or too difficult. The last one gave me some pause, at least, but I promise that it's soluble—pardon, solvable.

Try it for yourself and let me know what you think!

thomastc

I missed the Feedback Forthnight, but I really hope I can make it this time. It depends on scheduling that's out of my hands though…

Anyway: I've been meaning to learn Godot, and this sounds like a good opportunity. Previous jams (LDs) have taught me the essentials of HaxeFlixel, Phaser and Three.js, with reasonably successful games coming out at the end, so unknown tech does not phase me. Bring it on!