thomastc #9

Joined 4 years ago


Main events

An interactive murder mystery
The Wrong Mushrooms
by thomastc
on 14th Alakajam!
A vertical scrolling bullet hell shmup
by thomastc
on 13th Alakajam!
Help Titus Tremulus escape from a dangerous cave!
by thomastc
on 12th Alakajam!
You are trapped in an ancient Egyptian tomb! Can you puzzle your way out?
Tomb Escape
by thomastc
on 11th Alakajam!
Explore, trade, and sail your ship around the world!
Around The World
by thomastc
on 10th Alakajam!
A virus has broken out that turns everyone into poets! Can you stop it?
by thomastc
on ScoreSpace x Alakajam!
Steer ball of lava deep into the Earth's mantle using nothing but a fiery rope
Core Drop
by thomastc
on 8th Alakajam!
You control the lifts in an ever-growing office tower. Can you get everyone to their meetings on time?
by thomastc
on 7th Alakajam!
A duel must be. Define your steps. And may the best moustached person win.
Code of Honour
by thomastc, martenveldthuis
on 6th Alakajam!
Cast spells, help people, earn a living!
Spell Shop
by thomastc
on 5th Alakajam!
You're the chief demolitions expert! Bring down buildings while avoiding collateral damage!
by thomastc
on 4th Alakajam!
London is burning! Can you tame the ever-expanding fire?
The Great Fire
by thomastc
on 3rd Alakajam!
Discover where the islands are hidden in the fog!
Misty Isles
by thomastc
on 2nd Alakajam!
Use your set of beakers to mix your ingredients in the right proportions and create magical artifacts!
by thomastc
on 1st Alakajam!

Never gonna break my streak 0

thomastc • 2 months ago 

13 Alakajams and counting! Even though I'm pretty tired and have a baby to care for, I'm still going to poop out some kind of game this weekend.

I'm thinking of trying out Twine because it's super easy to get something started quickly, but I might change my mind and switch to Godot depending on what game design I come up with.

Edit: briefly tried both Twine and Inkle, and I like Inkle better, so I'm going with that. Or Godot. We'll see.

World generation in Around The World 0

thomastc • 1 year ago 

"But thomastc, how is the procedural world in your latest Alakajam entry generated?" asked no-one ever. But since you're here, I might as well tell you. Here's the end result:

The world size is 300×150 tiles; each pixel in this image represents one tile. I chose 300 because it gives some margins when displaying the map at the 320×200 resolution of the game. I chose the 2:1 ratio because it's how an equirectangular projection of a sphere (like the Earth) is usually displayed, with one degree of latitude being the same size on the map as one degree of longitude.

Water depth is shown on the maps as four shades of blue, but in-game you can't see depth; only one tile sprite is used for all water. This is because I had plans to make your ship run aground if you tried to enter shallows, so you had to use maps to avoid that. I'm glad I never got round to that, because it would probably have been too hard! But the different shades looked pretty on the maps, so I kept them.

The base of the world generation is, as you might have guessed, simplex noise, powered by Godot's OpenSimplexNoise class. We can configure, among others:

  • the number of octaves: fewer octaves for a smoother result, more octaves for a more jagged result;
  • the period: a larger period gives larger continents

To make it wrap, we need to call get_seamless_image to create a 300×300 image, then crop it to 300×150. The result is an image with monochrome pixel values between 0 and 255. Most of the values are around 128. We are going to interpret these values as a height map, larger values being higher.

If we simply used 128 as the threshold to decide between water and land, about half the map would be water and half would be land, and it almost certainly wouldn't be circumnavigable! So in the code I have a variable WATER_FRACTION, which lets me tweak what portion of the map should be water. In the end I set it to 0.75, which is slightly more than the 71% of our own planet, but always resulted in at least one possible route around the world in my tests, so I didn't bother to actually code a check for this. This means there are probably seeds that are unwinnable!

To figure out the desired water level, the code first loops through all pixels and creates a histogram, counting how often each of the 256 values occurs:

[  0] 0
[126] 1894
[127] 2645
[128] 3642
[129] 3528
[130] 2974
[131] 1490
[255] 0

Then it runs through this array, adding each value to an accumulator. When the accumulator exceeds the desired number of water pixels, which is 300×150×0.75 = 33750, we have found our water level. Let's say it's 130 for this example.

Now we need to create the poles, because the top and bottom of the map must not be traversable (this world is a cylinder, after all). To do this, a bias is added to each pixel, where the bias depends on the y coordinate like this:

bias = 255 * pow((abs(2 * y / 149 - 1) - 1) * 10 + 1, 3)
if bias > 0:
    pixel += bias

Formulas like this are a very powerful tool in procedural generation, but they're harder to read than they are to write (drawing graphs on paper helps!). Yet it's built from a few basic primitives, so let's break it down from the inside out:

  • y is between 0 and 149, inclusive. So y / 149 is between 0 and 1, inclusive.
  • Multiply by 2, subtract 1, to get it between -1 (north pole) and 1 (south pole).
  • Take the absolute value to get it between 0 (equator) and 1 (either pole).
  • Subtract 1 again to get between -1 (equator) and 0 (pole).
  • Multiply by 10 (a configurable constant which decides the size of the poles) to get between -10 (equator) and 0 (pole).
  • Add 1 to get between -9 and +1.
  • Raise to the power of 3 because it looked nicer that way? I forget.
  • Multiply by 255 to make sure that we get a bias of 255 on the poles, which guarantees that they'll be impassable.

Next, the colours are assigned. Level 130 has a height of 0 above water level, so we say that this is the beach (yellow). The three levels above this (131, 132, 133) become light green, the next three become middle green, and so on. A similar thing happens for negative heights, which are below water. As an exception, if the pixel is near the poles (y close to 0 or to 149), it becomes ice (blueish white), but we add the height to y to avoid a sharp horizontal line between ice and non-ice. The result is that inland ice occurs farther from the poles than coastal ice, which makes sense because the ocean has a warming effect. Ice is not just cosmetic but also serves a gameplay purpose: it lets the player know that they are getting close to the pole and will be blocked if they go much farther in that direction.

That's it for the map. Now let's place the ports. There are up to 100 of them. I searched the web for a list of seaport names and found an Excel sheet with over 100 names, which I cleaned up a bit and copied into my code. (In jams especially, I often don't bother opening files or parsing data, I just turn the data into a literal that can be pasted directly into a script. JSON in particular is valid code in several languages!)

For each of the 100 ports, first we generate a random x, y coordinate pair as our starting point. If it's land, it might be landlocked: too bad, try again! If it's sea, we start a random walk. Each step, we move one pixel north, south, east or west. If it's now on land, it must be coast because it was previously water. We'd like to place our port here, but first we check if there's already another one within 4 tiles. If not, we place it, otherwise we give up and try again. If we haven't found land after 75 steps, we give up and try again from a different starting point, up to 100 times. As an exception to avoid lots of polar cities (which would be unrealistic and might also be game-breaking), we also abort when we get close to the poles.

Finally, the port's inventory is decided. One random cargo type is picked as supply (with 2-7 units) and two different ones are picked as demand (at twice the regular price). We need to make sure that these are all unique, because it makes no sense to both supply a good and demand it. Your first intuition might be to pick a random type until you've found one that's not used yet, but there's an easier way: simply create an array of all possible types, shuffle it, and pop items off of it. For large arrays and small numbers of samples, this is obviously rather inefficient, but for small arrays it's fine, and it makes the code a lot easier to write.

As to equipment, in 60% of cases, a map of random size is offered for sale; in 40% of cases, one of the three powerups is offered. (In the game, you might encounter ports without any equipment. This is because when you buy the Binoculars, any Binoculars in other ports are upgraded to Telescopes, and when you buy the Telescope, all remaining Telescopes are deleted.)

As the very last step, the game decides what your starting port is going to be. This is not simply a random port! I wanted to make the player start in the "easiest" part of the map, so I added some code that counts for each port how many ports are within 15 tiles distance from it. The port with the most such neighbours becomes the starting port, and the player's ship is placed in the middle of an adjacent sea tile.

And that's it! I hope you enjoyed the read; now go and play the game if you haven't already!

Progress after day 1. Working title is "Contact Tracing" 0

thomastc • 1 year ago 

Very literal connections going on here. I think this may be my most on-theme entry so far.

My game ideas for all 10 themes 0

thomastc • 1 year ago 

This is just so you can avoid making the same game as I do ;)

  • Cards: a top-down, turn-based, deck-building Kard Racing game. If that doesn't sound intriguing, I don't know what will. Hot seat multiplayer would be best for the game, but not for streamability, so I might have to add a ghost car mode.
  • Orbit or Satellite: a golfing game, but in space, with orbital mechanics.
  • Heights: going up in a hot air balloon while avoiding obstacles. Sounds boring so this idea needs more work.
  • Swap: defend buildings against enemies on a grid like Into The Breach, but you have no units; instead, you can view your enemies' moves ahead of time and swap them to your advantage.
  • Paint: a vertically scrolling bullet hell shmup where you have a paintball gun which you can use to paint enemies. Their colour affects their properties and behaviour. This idea also needs more work.
  • Connections: grow and maintain a telephone grid in the style of Mini Metro, place cables and switches, avoid dropping calls or running over capacity.
  • Teleportation: another Into The Breach inspired game, where your units have no attack but can only teleport to block/deflect enemies' attacks.
  • Time: an Asteroids clone but with special relativity and a low light speed. Space compression, time dilation, redshift/blueshift.
  • A Single Room: an escape room puzzle. No highscores and no streamers. I don't like this theme and don't know what else to do. Please don't vote for it.

Seeking some ocean-related inspiration? Reddit has you covered 2

thomastc • 2 years ago I didn't even know that word until this started popping up on the front page.

Hyped and ready! 0

thomastc • 2 years ago 

Going solo this time. Got rough ideas for some themes, not for all.

  • Godot 3.2 installed
  • Project created with base debug code (just Esc = exit for quicker testing)
  • Wacom tablet dusted off and connected
  • Zoom H1n portable audio recorder charged and tested
  • Digital piano hooked up to the USB port

Do I need all of this stuff? Who knows… but a little preparation won't hurt :)

November Challenge accepted! 4

thomastc • 3 years ago 

I have been working on and off on turning my award-winning AKJ 2 entry, Misty Isles, into a full-fledged mobile release. The idea is to make it about the journey of Odysseus, following the story of the Odyssey but making the characters a bit more colourful.

It still needs a fair amount of work: more levels, character art, dialogue, sound, music, and some more mechanics to keep it interesting. But it should all be doable in a month if I put my mind to it, and approach it game jam style: "don't think, just do it".

Oh, if anyone has a good idea for a title ("Misty Isles" is a bit boring), please let me know!

In, of course 0

thomastc • 3 years ago 

A clear schedule this weekend, a good shortlist of themes, what more could I ask for? Worse weather, perhaps. Or my laptop back from the repair shop so I can work out in the sun.

Using Godot again, still not hating it. Also some of the usual suspects, depending on the style: Krita, Inkscape, Gimp, jfxr, Bosca Ceoil, LMMS, Audacity…

The Great Fire postmortem, part 2/2 0

thomastc • 3 years ago 

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 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.


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)]:

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.

The Great Fire postmortem, part 1/2 5

thomastc • 3 years ago 

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 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 (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 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:

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 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.

High scores

1 x02 x73 x2
by DiningPhilosopher, Papaver
12.991 July 9th 2021
by remco
1'53"180 March 5th 2020
DIG DEEP: Artifactually Incorrect
by dorkulon, Elchao, cloakedninjas, treslapin
3265 March 24th 2020
Islands Recharted!
by remco
16 shipsSeptember 22nd 2020
Cartography-less Island
by katuiche
1159 September 25th 2020
Around The World
by thomastc
284 September 23rd 2020
Beauty and the Deep
by Subatiq
2 coins savedMarch 1st 2020
Tower Archer Defender
by LogicaLinsanity
September 29th 2019
Tower Builder
by KeithComet
23 June 14th 2020
Surviving Flock
by remiv
14588 June 14th 2020
Jolly Mapster
by TimTips
1'14"165 October 1st 2020
by yozy
173907 July 7th 2021
by BPM
198 March 21st 2021
Cargo ship Calamity!
by HuvaaKoodia
24 October 11th 2020
(Tower of) Bird
by voxel
October 22nd 2019
by Raindrinker
132 October 26th 2019