My First Ludum Dare Game: Olive Harvest in Ludum Dare 52

skip to play game

IntroductionπŸ”—

The weekend before last (6th–8th January 2023), I participated in Ludum Dare 52. If you haven't come across it before, Ludum Dare is a competition in which participants write a game in a short amount of time β€” 48 or 72 hours (depending on entry type). You might have heard of this format being called a 'game jam'.

There's no prize for 'winning' but of course you get to keep your game at the end of it and the experience itself has been quite fun for me.

Ludum Dare has multiple different types of entry:

  • 'Compo': solo developer, 48 hours, must release source code, have to make all your assets yourself
  • 'Jam': teams allowed, 72 hours, can use assets from elsewhere (within legal constraints) but must opt out of ranking categories if you didn't make them yourself
  • 'Extra': much more relaxed with much more time (3 weeks) β€” I'm not sure of the details since it doesn't really interest me, but seems like you don't get a score at the end. I would say that in general, the relaxed time frame is probably a negative for me, but it may appeal to some.

This was my first time taking part in a Ludum Dare β€” the closest thing being a game jam at University but it was outrageously short (8 hours, if memory serves me correctly) and I had an idea that was just way too cool and ambitious :/...

Since it was my first time (and I couldn't arrange for any friends to join me in time), I decided to treat it as a practice run and I decided to make an entry for the 'Jam' because of the 72 hour time frame. I took it a bit casually β€” I didn't skip any sleep or anything like you might expect from a timed event like this.

In retrospect, I wish I had aimed for a 'Compo' entry instead and taken it a bit more seriously. The event started on Friday night in my timezone and ended on Sunday night (Compo)/Monday night (Jam). Since I had work on Monday, I didn't end up getting that much extra time anyway! Not only that, but I created all the assets myself since, to me, that's part of the fun :).

My intention was to do future rounds with friends. I would still like to do that if the chance arises next time, but if I can't arrange anything with friends, I won't be afraid to have a crack at a 'Compo' entry :-).

IdeasπŸ”—

Every Ludum Dare has a theme, which is voted on by interested participants in the week or two leading up to the event and then announced at the start of the event. This Ludum Dare's theme was 'Harvest'.

When the clock struck eight and Ludum Dare started, I was somewhat worried; I couldn't quickly think of an interesting idea. Games like Stardew Valley and Harvest Moon popped into mind but they are obviously too complex to make in a game jam (and besides, I wanted to aim for something original). I wanted an idea for a game that wasn't trivially basic (gameplay-wise), was fun and yet was going to be achievable in the short time (knowing full well that this event was going to flash by). Maybe you call this 'simple but good' and I don't know about you, but I find it hard to come up with ideas in that category :-).

I eventually narrowed down into a game about farming olives and processing them into olive oil, whilst under attack by forces of evil (slugs, mould, thieves, ...?). I thought it'd be interesting to put some tower defence aspects in there β€” I always liked games that made you think about 'buying' structures and thinking of where to place them to defend you from the forces of evil. (I never actually got around to any tower defence aspects in the game but it was in the plans.)

In a sense, I was lucky, because the flow of ideas started and I soon had a handful of ideas that, together, sounded like a game I wanted to play and show to others. I was perhaps even luckier that these ideas were flexible enough to let me pivot away from the overcomplicated 'tower defence' aspects into a less complicated idea that meant I could still package up a game at the end of the event.

Thoughts on the Godot game engineπŸ”—

It was time to get cracking. The excellent open-source game engine Godot was my tool of choice. I had already messed around with it a few years ago (for my previous / failed game jam at University) so I had a tiny amount of familiarity, but otherwise I'd be coming at this with fairly fresh eyes. I should have learnt beforehand but I didn't β€” I only skimmed documentation on the job.

Godot is very intuitive once you know that it's 'Scenes' all the way down. Its engine-specific scripting language, GDScript, can feel a bit wonky at times but it's also very fit for purpose β€” as long as you don't fall into the trap of thinking that it's Python despite its syntactic similarity. I can see it being useful to drop down into a different language at times (C#, C++ are supported but others are available, including Rust), but GDScript was perfectly adequate for me and I'd probably advise against getting too complicated in a short event like this.

Godot Traps and TricksπŸ”—

Basically every tool has quirks and a good craftsperson would do well to become aware of them. Here are a few traps I fell into and a few tricks I learnt about.

yield and SceneTree timers combined seem dangerousπŸ”—

At some point if you dig deeply enough, you'll run into GDScript's yield keyword which lets you defer execution until another event triggers.

You might be tempted, as I was, to use this sort of snippet for inserting a sleep into your code to make something happen later:

yield(get_tree().create_timer(5.0), "timeout")  # wait 5 seconds before next event

This works, but it turns out that if the current object/Scene gets removed from the world during the sleep, the resumption of the script will lead to an error: Resumed function after yield, but class instance is gone.. I didn't actually notice any ill effects from this β€” just an error in the console and everything else continued as usual, but it's poor to have errors showing up when things are working correctly and I can't guarantee that this wouldn't lead to some footguns in other situations either.

The slightly unfortunate workaround for this is to use an explicit Timer node as a child of your Scene. If necessary, this means creating a Timer and remembering to remove it afterwards.

var timer = Timer.new()
timer.one_shot = true
$Timer.add_child(timer)
timer.start(time); yield(timer, "timeout")
$Timer.remove_child(timer)

It's safe to use yield on this timer node because once the current Scene is destroyed, the Timer will also be destroyed as it is a child and the script will never be resumed as a result. Note: a Timer can't have multiple timeouts, so you need one per task... I'd be tempted to wrap this up in a neater package if I was writing a proper game and needed to make use of this, but for the purposes of the game jam I took the lazy way out.

Source for this trick on the Godot Engine Q&A site β€” thanks k2kra!

z-ordering is hard, at least in a game from this 2D perspectiveπŸ”—

A screenshot from Olive Harvest demonstrating the 'top-down oblique' view

My game is 2D but set with a perspective that I don't know the term for, but the best I've found is 'top-down oblique'. It's as though you have an orthographic camera (so no vanishing point and no concept of things getting smaller as they get further away) angled so you can see the top and front of things. It's convenient for drawing because you can draw terrain as thought it was top-down, characters as though you are looking at them from the front and structures as a bit of both β€” and everything will look fairly reasonable. Structures can be built out of reusable tiles (in Godot: use a TileMap!) which greatly cuts down on time needed to produce levels.

A slight problem emerges because characters can walk in front of and behind other characters and objects in the world. Despite thinking you had a 2D game, you now need to worry about ensuring that everything renders in the right order so things that are logically in front appear in front.

I never got this exactly right during the event itself, but I at least know some tricks.

The biggest trick: use the YSort node. YSort sits in your scene tree and re-orders its children depending on their Y-position. For example, when the player walks in front (below) of a tree, they show up in front, but when they walk above, they show up behind the tree.
For this to work properly, you absolutely must make sure the origin point of your child scenes/entities matches where you want the ordering to be decided! Examples that would seem typically correct would be to put the origin at a character's feet, or at the base of a tree, or the base of a wall. Essentially: wherever the entity touches the ground.

In my case, I started off my game with little regard for the origin positions of objects, using explicit z-indices to solve all my ordering problems and designing levels so that you never noticed any discrepancies. Eventually, I ran into a case I couldn't design away and learnt about YSort, but then it was difficult to rework all my objects and I released the game with some broken Z behaviour (switching to YSort fixed some of my issues but broke at least one case I worked around manually beforehand).

An especially tricky case, present in my game, is that of objects which can have other objects walk or sit inside: for example, the player can walk in the back of the postal van/truck and the olives can sit inside the wheelbarrow. It's easy enough to split the front and back of these container objects into different sprites (if you drew them with layers in Krita, you can just export different layers/layergroups), but z-ordering them is tricky.

I still need to research the proper way to support these 'container' objects, particularly the ones in the world: maybe there's a trick hidden by clever z-indexing or the 'Show Behind Parent' option?

I'd also note that I don't know how to make TileMaps interact with the YSort node's sorting such that you could have walls that occlude the character behind but do not cover the character in front: I just made it so that you couldn't walk into any section of the walls.

To make my game come alive a bit, I wanted at least one kind of 'enemy' character: in the time available, this turned out to be slugs. They wouldn't add much to the game if they couldn't walk around though, so I needed pathfinding. Godot provides the 'navigation' system for this purpose.

With that said, I found documentation a bit hard to come by for this.

Most online sources point you to the Navigation2D node (you'd use it as a parent of your world). At least in Godot 3.5.1, this node is deprecated with a (somewhat misleading/unhelpful) message pointing you to the Navigation2DServer. But the online sources I came across, even for Godot 3.5.1, still use that node claiming it is more configurable. I know it's a game jam, but I didn't particularly want to drop a deprecated component in my project.

I had a few cases of the navigation system randomly not working, but here are some rough notes:

  • Put a NavigationAgent2D node in your enemy (or whatever it is that needs to walk around) scene

  • In your levels (or potentially in the tiles, but I didn't figure this out in time), add some NavigationPolygons that cover the area in which the enemies are allowed to walk. Keep the gap between them quite tight and also don't overlap them too far β€” it seems like the lines need to be close together for them to join up.

    • I also set the 'Travel cost' to zero in each one, because otherwise I was having slugs walking the long way around the buildings just because there were fewer mesh polygons in the path that way(!) despite being a longer distance.
  • Because you're (likely) a human β€” who didn't make an absolutely perfect and gapless navigation mesh β€” increase the edge connection margin of the navigation system:

    # random place to put it, but needed to get our navigation working:
    var w2d: World2D = get_world_2d()
    Navigation2DServer.map_set_edge_connection_margin(w2d.navigation_map, 10.0)
    

    (In this case, this means 10 pixels is the allowed margin between two polygon's edges for an enemy to be allowed to walk across.)

    If you don't do this, your navigation polygons need to be extremely-well (perfectly?) lined up. This is the reason that some sources are still using Navigation2D nodes, because you can easily configure this in the editor there. I couldn't find anywhere to configure this on the Navigation2DServer, so I put the above in the _ready function for my UI code (which is present on every single level). Messy, but works...

  • Occasionally (on a timer) poke the NavigationAgent2D to navigate to your target (in case the target moves, like a player) and have your physics processing code steer the enemy towards the next point β€” crucially, you call NavigationAgent2D.get_next_location() to get the global position of where you need to go to follow the path.

I'd recommend that anyone interested in this feature experiment with it before a game jam if they're interested in not wasting an hour or two learning it on the job...

Structuring a long-term project is not obviousπŸ”—

I don't know enough about the right way to structure a Godot project and especially not about how to effectively reuse code/component Scenes.

For example, if I wanted to add an upgraded wheelbarrow that can hold more olives, I'm not sure about the cleanest way of doing that.

Proliferating use of collision layersπŸ”—

For my game, I used a lot of collision layers despite it being a short project. Although you can name them (which is a nice touch!), there are a finite number so I'd probably want to think more carefully about how to share layers for multiple uses in a proper game.

Other toolsπŸ”—

Here are the other tools I used:

  • Krita for digital painting.
  • jsfxr for sound effect generation (a re-implementation of sfxr usable straight from the browser).
  • lazygit for version control (a text user interface for Git).

SubmissionπŸ”—

There's an extra hour for submission. I used it for packaging up my game and writing up a description. I did not know that I would need a cover photo, so I just took a screenshot of the game, but in future I would try to make a bit more of a polished cover photo (observing the size requirements for submission).

I used Godot's 'Export' wizard to create a HTML5 export with the main name of index.html (as needed for Ludum Dare's embed). I also placed a copy on my personal website so people could play the game fullscreen. Note that there's a fairly chunky download to complete before you can export a HTML5 game for the first time; if on a slow network it'd be a good idea to have that already done in advance.

Rating other gamesπŸ”—

Don't make the mistake of thinking that the event only lasts 73 hours... because after the deadline, it's time to play other people's games and rate them! By doing so, you also encourage your game to show up at the top of the list for other players to find (which then leads to getting more ratings); note that you need 20 ratings to get a rank at the end of Ludum Dare!

I'd say, based on just my experience, you should expect to play and rate about fifty games in order to attract enough attention to obtain your basic 20 ratings β€” but I noticed other games had a much better ratio, so perhaps my game was just not very enticing (hint: maybe a better cover photo would have helped!). There were many interesting and well-polished games with interesting takes on the theme, but of course there were also some games you might consider underdeveloped, unoriginal or buggy.

Playing the other games was also a good chance to reflect on my own game. Some rough points:

  • I appreciated having HTML5 builds of games. I'd propose any participant to try and provide this option if they can. It means I don't have to download questionable executables for a start, but also saves a lot of effort.
    • That said, some games' HTML5 builds were said to be degraded compared to the main build, such as having rendering bugs.
      • I'm definitely guilty of not having done this, but it'd be a good idea to test the HTML5 build of the game earlier than the publishing hour. I didn't do anything too specialist so I got off lucky, I guess?
    • I noticed that games built with Godot 4 do not work in Firefox-based browsers, which was a shame. Easy trap to fall into β€” I just got lucky that I used Godot 3.5.1 really!
  • Many games had no audio, which is a shame. The games that had audio definitely felt more alive.
    • Even just sound effects will help with this β€” but tasteful music was good in the games that had it. (My game did not have music as it's not in my skillset and I didn't have time to experiment.)
  • Some games didn't do keybindings based on physical keys, so I had to change keyboard layout to play them. Many games did this right, though β€” so for the others, it's likely just a matter of finding out the trick to do it in their game engine of choice. Godot offers you the ability to map 'physical' keys to an input, for example.
  • Most games documented their controls in the description of the game. Some games did not and this was a shame when I couldn't figure out how to get them going. Ideally (for me) they would be within reach whilst playing the game because I can't remember more than a couple of controls in my head when I'm not used to them.
  • Some games were difficult to understand from the text alone. But some games had the right idea: they had illustrated diagrams telling you what to do. Based on this, I'd suggest taking 10 minutes to throw together illustrated instructions for your game β€” it should really only involve pasting together a few of the existing sprites and writing text, but it could really help players.
    • Many games, including mine, had planned to have an in-game tutorial but didn't get around to it. Avoid that altogether! There's basically never going to be time to do that in a jam, so take 10 minutes to put together an illustrated diagram instead.
  • Leaving off the cover photo entirely is very unfortunate! At least put a screenshot on!
  • It's unfortunate that the Ludum Dare site doesn't give you the option (that I could find) to filter out Windows-only games etc. At some points during the event, the top of the board was full of games that I couldn't even run on this machine.

Some of my favourite games from Ludum Dare 52πŸ”—

  • Poppy Defence β€” a tower defence-style game where you fight off waves of enemies by strategically planting various plants. Exceptionally well-polished and I had fun playing this one for hours!
  • Agricultural Ascension β€” an excellent puzzle game. The programmer in me liked how you would easily be able to generate challenges for this game programatically if you wished.
  • Harvest Hell β€” harvest melons. If you're too slow, they turn into monsters that need shooting down. Simple, but especially well-themed music that really brings it alive. I like how some subtle constraints of the game were put together.
  • Horticulture β€” buy seeds, then plant and harvest plants for customers. The customers give you paper slips with their orders on and you need to cut the right parts of the plants whilst trying to be efficient. I'm afraid I am much too slow for this game β€” I get drowned in paper notes β€” but it was a good idea and I found it fun!
  • No Harvest β€” rebelling against the theme in quite a fashionable way, this is a puzzle game (I guess) where you try and avoid nasty plants building up on a field, by casting spells. I liked how the games challenges you and forces you to consider your choices, as some spells will actually help some of the pesky plants grow!
  • Revengetables β€” this is a game where you control the vegetables (and fruit!) in their uprising against the farmers who want to eat them. I'm not usually a fan of voiceovers but the voiceovers in this game were hilarious (and the game would be worth it for that reason alone). But even besides that, I found it thoughtful how the different characters had different benefits and you could switch between them during the fight.

After the jamπŸ”—

It seems that some people will continue working on their games after the jam. I know I was intending to, but reality gets in the way. Ludum Dare is a special time. My recommendation (mostly to myself) is to make time for it, then make the most of it β€” because that special time is not forever. I'd suggest trying to get your game to a state you are content with, before the jam ends and real life resumes.

Play Olive HarvestπŸ”—