Ludum Dare #29 Post-Mortem
May 6, 2014
12 minute read

I said I would stop! But there’s no stopping me.

When I invited Amandine Bru over to hang out around Ludum Dare and at least see what the theme was, I promised her nothing was going to happen. It was going to be a relaxed evening with no stress of any kind. Now we’re one week later and BAM, I have yet another newborn game to take care of. These things grow so fast…

If you don’t know, game jams are these crazy events where you have a given amount of time to create a completely new video game from scratch (well, almost). Ludum Dare in particular has two variants, the compo (48h, strict rules, solo work only, zero re-use allowed) and the funnier version which I always go for, the jam (72h, lax rules, group work is fine, re-use whatever!)

I’m going to quickly go over three points that I feel have been important to me during that last gamedev jam:

  • Not twine
  • Yes 3D
  • Woopsie deadline

Not twine

I don’t think there’s anything wrong with Twine. Really, I don’t. It’s a great tool and it gave a voice to many. In fact, if you follow me on Twitter you’ve probably heard me praise it, amidst many rants. But I tried using it, repeatedly (see Neverjam for example), and it doesn’t appeal to me all that much.

See, Twine lacks constraints. For instance, nobody forces you to write anything more interesting than a novel randomly divided in passages. And then well, does it even have to be a game at all?

And nobody forces you to use Twine’s powerful macro/variable system either. Or to start hacking on your game in JavaScript directly, around Twine’s engine. And with that you can do pretty much anything (and people do great things). But I lack that discipline. There’s not enough constraints. I get lost. Every project ends up too ambitious or too trivial.

So I didn’t really want to make a Twine game for Ludum Dare. But I was in the mood for something narrative anyway. What did we do? The only thing I know how to do, obviously, I made a fresh piece of architecture: a space-time interactive story engine.

Here’s how it works: there are two text files that determine the entire content of the game, in YAML format. One, map.yml, contains a list of places like this:

home:
  name: Kate and Stanley's Home
  pos: [0, 0]
  type: house
  paths:
    - left crank-path-1
    - right home-to-mayor-1
    - down huntress-path-1

crank-path-1:
  # you get the idea...

Places names appear in the upper-left corner when you navigate, the position is given in spherical coordinates, the type determines the 3D model shown at this position, and paths is a list of places you can get to by hitting the arrow keys or clicking on the arrow indicators in the game.

Movement is the first phase of each turn - you have a certain amount of movement points which you spend by moving from place to place. At any time, you can choose to spend the night.

Let’s say we spend the night at the place with the identifier home, that we just defined above: in this case, the game looks up a page named at-home, that is defined in the story.yml file, which looks like this:

at-home:
  lines:
    - Tired, Kate decides to go home for the night.
    - The fireplace is cold and the place is cold all around.
    - Home sweet home.

Pages are like Twine passages, kind of. They can have a list of lines, that’ll be displayed (the crux of the story) and then many many ways to conditionally advance the story. By default, the node above is equivalent to

at-home:
  lines: ["Tired...", "The fire...", "Home..."]
  if: true
  then: night
  else: night

Now, we can write an actual condition in that if block. For example, checking for the presence of an item.

at-home:
  lines: # etc.
  if: has crystal
  then: win-message
  else: no-win-today

win-message:
  # lines & stuff here

no-win-today:
  # same

Using if, then, and else, you can make pages keep jumping from one to another until eventually it reaches the special page night, which fades to black and back, advancing to the next day. In which case you go back to the movement phase, having spent one day.

Now, has is only one possibility — to check the inventory — another kind of conditional is is, to compare a stat with a number or a number range. For example, if something is supposed to happen only between days 3 and 6, one could write:

at-somewhere:
  if: is day 3..6
  then: something-special
  # implicit else: night

At this point we already have most of the tools we need. We just need a way to pick up items pickup: item, or pickup: [item1, item2, item3], a way to drop items drop: item or drop: [item1, item2, item3] and a way to increase/decrease stats: inc: strength, and dec: strength.

Note that our example above tests the day stat — it’s a built-in stat, initialized by the game to 0 and incremented automatically on each night spent. But for example, a page could inc: day explicitly to simulate “passing out”. Similarly, max movements points are a stat as well. And you can have any number of named custom stats — anything undefined is equal to 0, and can be incremented/decremented at will.

By chaining if pages, we can have complex condition chains that can already lead to quite an interesting story. But we were on a deadline, and we often had 3 or 4 different conditions to test - enter the switch instruction.

at-somewhere-else:
  switch:
    "is day ..7": week-one-event
    "is day ..14": week-two-event
    "has dagger": maybe-win
    "is day 31..": dagger-hint
  then: nothing-happens

Each condition of the switch is tested and, if true, the specified page is flipped to. If all else fails, the then clause is used.

Now, a switch is really convenient, but it has a big issue: it tends to repeat pages too much, since if the condition is true it will always flip to the given page, and the conditions will always be tested in the same order.

The solution to that was… a pool! A list of options, one of which would be picked at random, removed from the pool, and flipped to. For instance:

at-mayor:
  pool:
    -
      then: mayor-there
    -
      then: mayor-absent
  then: mayor-too-much

And as with the switch, once the pool is exhausted, it goes back to the then clause. To make things more interesting, pool options can appear conditionally by specifying only-if:

at-mayor:
  pool:
    -
      then: mayor-there
    -
      then: mayor-absent
    -
      then: mayor-pissed
      only-if: has threatened-mayor
  then: mayor-too-much

Oh and obviously we need a way for the player to make choices! That’s the choices clause, and it’s written almost exactly like a pool, except it needs a name for the buttons. In fact, that’s how the main menu was implemented in the jam version!

menu:
  choices:
   -
     name: New game
     then: at-home1
   -
     name: Skip introduction
     then: at-home4
   -
     name: Switch Fullscreen
     then: fullscreen
     only-if: has desktop
   -
     name: Quit to desktop
     then: exit
     only-if: has desktop

(Where desktop is a custom item you automatically pick up at the start of the game if.. you’re playing a desktop build of the game. Now you’re starting to get it!)

There are more built-in pages — for example, when nothing interesting is happening somewhere we didn’t want players to waste a night, so instead of flipping to night we’d flip to locked and it would switch back to the movement phase (restoring 1 movement point if the player was out of moves, to avoid being stuck).

And there are more clauses too! Like teleport, used only once in the game, but which wasn’t that hard to add in. At no point during development was I entirely sure the structure of the game engine was right, but everything turned out for the best. Maybe old coding chimps end up having some kind of sixth sense for what to avoid and what compromises to make?

And that’s about it. I usually like to iterate on things like that, but for once I sat down and wrote a spec for most of the story engine, then sat down harder and implemented it all in one go. Meanwhile, Nim had collapsed on the couch from having written off too many potatoes, and I left her a note:

Sure enough, when I woke up, she was halfway through implementing the story. Who says waterfall development never works?

Yes 3D

It had been a while since I last did anything tri-dimensional. So long in fact that most of my entourage forgot I even did such things in the first place.

3D is.. hard, inherently. Instead of working with a familiar 2D plane in which there’s only one rotation axis, you’re left stranded in a world where if you get only one camera value wrong, it’s nothing but darkness.

I used Blender to model the world and the various props, but already at the start it was a struggle. How do you UV-map a sphere and then draw paths of consistent widths on it? If you’ve done a bit of cartography, you’ll know that any projection has deformations. As a result, the world map texture (painted in Gimp), ended up looking like this:

See those cones? Those are the north and south poles of the world. While the texture is badly deformed (and that curve was the result of a long period of trial and error) it looks completely normal when mapped on the sphere. Thank God, Blender is able to quick-reload textures (Alt-R) and the Gimp can export PNGs with almost no compression (exporting a 2048x2048 PNG image is actually kinda slow).

But that wasn’t all. Placing houses and stops was hard as well. See, when dealing with rotations in 3D you have a few options:

  • Euler angles
  • 3x3 matrices
  • Quaternions
  • Curling up in a ball of sadness and crying yourself back to sleep

Euler angles have a well-known flaw, Gimbal lock - I won’t enter into the details here but basically, it has something to do with the fact that 3 values to determine rotation is redundant for some angles - and when you reach a certain point changing a value no longer rotates the object (in our case, the world).

Matrices are no fun to write by hand (no, seriously, 9 numbers, ugh) and are actually more redundant than Euler angles (but used internally by 3D engines because they’re very useful to apply transformation to a vector).

And finally, Quaternions I probably could’ve used in some way, especially for interpolating between points on a path, but I really didn’t have the courage to learn that much in the middle of an already-complex-looking jam.

The constraint I had was the following: for each point on the map, we should have at most 4 different directions, left right up down, no funky angles or whatever, and the camera angle should always be the same, no matter what path you took to reach that point.

Upholding these assumptions was surprisingly difficult, and I did spend perhaps too many hours tweaking the camera movement (especially that nice little effect when you spend the night somewhere where it rotates and shows an angled shot of the place), but I don’t regret it: I may not have ended up with the perfect solution, but it made the game playable. Which, during a game jam where you’re not making an FPS or a platformer, is very much an achievement.

Accessibility was an important point to me - navigation was made possible using the arrow keys or the mouse. Advancing dialogue was made possible by clicking or pressing ‘Enter’ or ‘Space’. Dialogues could be skipped, etc. I spent a lot of time making sure the only confusion stemmed from the story and not the game engine.

Alas, my disappointment was great when I found several comments talking about “very disorienting controls”: I understood only later that it was meant as a positive thing, but given the gigantic amount of time I spent on all that, I became angry and flustered.

Worse still, when I read comments about “the 3D part not adding much to the game”, and how “a pure text game would have been better”. Such is the life of a Ludum Dare participant: spending 72 hours in overdrive and coming back to heavy-handed comments about how you dun goofed, son.

Well, I have no regrets. I wanted to play around with 3D stuff, and I did. Going back to Java and Eclipse-land was almost pleasant, and libgdx had everything I needed, plus it allowed us to have a Web version and a Desktop version rather easily.

Woopsie deadline

Even after babbling on how I can’t do art (boo hoo) I really wanted to contribute to the story. In fact, the initial idea for the story is courtesy of my crazy, sleep-deprived brain. But as it turns out, I ran out of time.

Running out of time is really routine in a game jam. No matter how much you cut the scope down, no matter how much you plan ahead, no matter how hard you work and how much coffee you drink and how many freaking games you’ve already made, there’s always this “oh shit” point about 3 hours before the deadline.

I did spend two hours rewriting most of the introductory text, but being the perfectionist I am, I could have spent hundreds more just going over the story, adding options (as I’m sure Nim would have too), changing some lines slightly…

I usually compose the soundrack for my games (either alone or in collaboration with bigsylvain) but given the circumstances, I decided to spend my time implementing a music mixer (to seamlessly switch between ‘ambient’ and ‘melodic’ music contextually) and pick out a fitting music from ccMixter rather than trying to record it myself.

There was something quite unique about this game. It was my first collaboration with Nim, and her first Ludum Dare. It had been a while since I left my baby programming language ooc at peace instead of trying to do unspeakable things to it.

But mostly what was unique is that the story system was, at some point, “enough”. With every other game I have made, the team and I kept having ideas that were great in theory but a real pain to implement because of all the engine changes and testing that would be required.

With Super Duper Spatio-Temporal Storytelling Engine 6000 though? We had plenty of ideas just before the deadline and it would have taken us only 10 minutes to add them in. And it would’ve been fun and just like we had imagined. Text is a powerful thing, my friends.

Thanks for reading this far! You can find Nim’s own post-mortem on her blog.