Post-mortem

"The more you have, the worse it is" was the theme for the latest Ludum Dare - LD40. I made a cyberpunk surveillance simulation game called Ignorance is Bliss. Here is how that went.

Preparations

Unlike some of my previous entries, I knew I was doing the jam (72 hours) ahead of time. My friend, "eidovolta", has agreed to make some time (again) during his weekend to make music for our game. I say again because he did the same for Planet of Babel, even though I did not have time to put the music in, whoops! Not having to worry too much about the music or sounds during the jam helped a lot.

Having played Snatcher recently, having listened to a lot of Shadowrun OST, and having followed Archillect on twitter I was really feeling the cyberpunky / noir mood lately. Before the jam started I have already decided I wanted to make a game with this general genre in mind, though I was not at all sure how I would implement it.

Before going to sleep on Friday I also made sure all of my workflow was ready, as always:

  • A github repo - to keep track of my commits and make it easier to write this post-mortem.
  • tl (from shkit) - to record a timelapse / take a screenshot every 3 seconds.
  • autobuild - to globally bind the fn-A and fn-R keyboard shortcuts to "build" and "build and run" respectively.
  • psdwatch - to watch my PSD (Photoshop) files for changes and convert them to PNG whenever needed.
    • I had to add an additional post-processing step, consisting of a custom PNG decode and re-encode here, to make sure the resulting PNG has no metadata (waste of space) but, more importantly, to make sure the PNG has no gamma correction information. Some description of the problem is available here and here. Displaying a PNG differently in the JavaScript builds would not be a big problem, but if I want to read pixel colours directly (like I did in The Last Scholar for the map), I had to make sure that a 0xFFFF0000 (100% red) would be exactly the same in Photoshop and in the web build.
  • plustd - confirmed both the JavaScript and Flash builds are working.
    • Hot-reloading - reloading image assets on the fly without having to rebuild is always nice.

So everything was working, in theory. Some troubles occurred during the jam, obviously!

Day one

After having slept some 8 hours (European timezone after all), I found the theme. Once again, I had no favourites among the themes, so I was okay with anything.

"The more you have, the worse it is" was a very strong 0 for me!

I started thinking about how to implement the theme in my game. The first burst of brainstorming essentially got me to:

You take control of an AI which oversees the operation of a city via console. You are
investigating a possible rebellion. You can interrogate people by navigating a city map,
looking at buildings, and then videophoning. Every conversation is recorded on tape
and transcript. (Twist: the more human interaction you have, the worse your experience is,
because you are becoming sentient and rampant.) You can revise video recordings from
public cameras. The (apparent) end goal is to identify the source of the rebellion.

And a tiny sketch with the first design for the GUI.

So the subject of the theme would be "information" or "human interaction". I wanted to make the player feel their transformation in their dialogue choices, the screen becoming more chaotic? More realistic? Who knows. Either way, a city needed to be displayed to the player.

Rendering the city

The city renderer is very similar in many aspects to the room renderer in one rooms.

The city consist of a number of buildings, each of which has several floors. Every floor is technically a polygon, although in practice it is always a rectangle. Every line of each floor / rectangle of each building is processed and rendered as follows:

  1. The building (as a whole) coordinates in the city are offset by the current camera position (in city map coordinates).
  2. Both points of the line are added to the building coordinates, and scaled according to the scale / zoom factor.
  3. Both points are transformed using the camera angle transform.
  4. A Bresenham line is drawn between the two transformed points (now in screen coordinates).

The camera angle transform is basically a rotation matrix, with the y axis squeezed according to the "pitch" of the viewport:

$\begin{pmatrix}x'\\y'\end{pmatrix} = \begin{pmatrix}x\\y\end{pmatrix} \begin{pmatrix}-\cos{\theta} & \sin{\theta}\\-\sin{\theta} * \text{pitch} & -\cos{\theta} * \text{pitch}\end{pmatrix}$

The actual point plotting is done on a pixel-by-pixel basis. This works fine enough in Flash. Unfortunately, it made any browser die immediately in the JavaScript build. I should probably try to rethink how this should work in the JS target of plustd. (This is also how I abandoned the JS build entirely.)

The rendering was different for the "road" and the "parks", but it also used the line drawing as described above.

City planning

When I was happy with my renderer rendering a single building nicely, I planned out the city on paper, with some thought as to where to place buildings important to the story.

Then it was time to redraw the whole thing in Photoshop:

I did plan a power grid for the city, so that the player would be able to turn off power plants and stop power supply for large block of city, for whatever reason. This did not make the final cut.

With the city in pixels (and working magically), I had to go through each line of pixels left-to-right to tag the buildings with their ID, type, and description. This was somewhat of a painful process, even more so because I added some buildings into the digital version that were not on paper. But then it was done and I was not planning on ever changing the city layout.

Interface

Once the city was "done", I started working on the interface. I transferred the sketch from paper to an extremely rough draft:

I saved a lot of time by actually using big Photoshop brushes, combined with some magic wand trickery to select circle sections of various radii. I also used a dither mask:

  1. I created a checkerboard pattern of sufficient size in a separate layer.
  2. Using the magic wand (continuous selection: no) I selected all of its pixels.
  3. I selected the target layer (the layer where I wanted the dither effect).
  4. I inverted the selection twice, which allowed me to move it around again with the arrow keys and position it appropriately.
  5. I set the magic wand mode to "intersection" (with continuous selection this time) and selected the region / colour I wanted to dither.
  6. I filled (continuous: no) the selection with a colour and the dither was done.

Yes, I still want to switch to a different (preferably custom) program for pixel art, something more lightweight and more fine-tuned for the task. But it is still nice to be able to have little techniques like this.

For the actual interface design, I chose to do something quite commonly found in cyberpunk movies and games - "modern" technology combined with old / retro implementations. In this case, a sentient AI monitoring the entire city versus an old CRT / vector green-on-black screen with an interface consisting of big blocky buttons one could find on a tape recorder, and even a tape reel for the recordings. Skeumorphism and retrofuturism

Most of the way the interface works is quite straightforward. Sprites blitted on screen, sometimes a different one if a button is held down, etc. The tape reel is slightly more complicated - it consists of 60 (partially) code-generated frames generated when the game is loaded. The slots in the tape cylinder are used as an alpha mask which is offset to create the highlights and the shadows around the slots. This way the specular bits move around quite realistically when the reel is turning.

Interacting with the city

I needed to make sure the player can actually interact with the buildings in the city. I was quite sleep deprived and so I wasted some time trying to work out how to do this without approaching the problem properly. The problem was: how to tell which building the player is pointing at?

After a little while I came to my senses and I realised all I needed was an inverse function to the one described above for the city coordinate transformation. More or less, this is just applying the inverse steps in the reverse order. The inverse to a matrix could in general be more complicated, but in this case it is simply a rotation matrix - its reverse is a rotation matrix with the opposite angle.

Portraits, story, and jukebox

This time I wanted to make the story somewhat more complex as it is basically the core of the gameplay. I recently watched this talk by Taro Yoko. It detailed his (probably not only his) technique for writing a story - work from the end backwards. I tried to write the timeline for the story like this. I focused on the consequences (rebellion happens, meeting happens, etc) and tried to think of a reason why they can happen. For instance, the major plot point - the coup (spoiler alert I guess):

  • A coup requires a leader, a military / mercenary force, and weapons
    • The leader must be alive
    • The mercenary force must be bought
      • The leader must have the money
    • The mercenary force must have the weapons
      • The weapons must be given / transported to the mercenaries
      • The weapons must be bought
        • The leader must have the money

In the game it was simplified greatly, because of time reasons, obviously. But the principle did remain the same - phonecalls to agree on meeting times and places. Meetings to trigger plot points. Eliminating a person changes the story somewhat. Here is a small sketch I had with the timeline:

As I was working on the story, I spent a couple of hours to implement hot-loading for the story. I wrote a second "version" of the game with everything stripped but the story. Whenever I changed the scenario file, it would recompile and serialise the scenario (using the basic Haxe stdlib serialisation), then send that to the running game the same way image assets are sent in plustd, which is a simple websocket server.

Hot-loading the story was pretty nice, because, for whatever reason, I still need to manually word wrap my text. In hindsight, it would probably have taken much less time to write an automatic justification / word wrapping into plustd than I spent wrapping my text. But oh well.

I also used the plustd console (removed in the online build naturally!), and wrote some basic debug commands which allowed me to skip to various points in the scenario, change the day, change the current music track, etc without modifying the code and rebuilding. This was also something eidovolta needed to listen to his music loops in-game (a jukebox mode, basically) to see if there were no pops or gaps.

Speaking of pops and gaps - Flash supports two audio formats for its sound assets. Uncompressed WAV / wave files are huge, but lossless. MP3 files are much smaller, but lossy. Most importantly, there is nothing in the MP3 format to encode the exact length of an audio file. In other words, a perfect audio loop, without a miniscule but audible gap in playback is impossible with MP3, without using special tags or an external data source to determine the exact file length. For my jam submission I therefore used WAVs because I could not bear the gaps in the music. This led to a huge, ~30 megabyte file. Soon after the jam I used a Haxe Ogg decoder to decode Ogg Vorbis sound assets, which are compressed but lossless. A 3 megabyte file is pretty okay in my opinion.

Wrapping up and falling asleep

Once again, I attempted to go without sleep between the second and third day of the jam. It did not go so well for Planet of Babel. This time, my solution was this:

Not the sushi or the cake (though they were pretty good too), but a poor man's standing desk. I was standing for most of the last day, kind of dancing to get the blood flowing. Sitting down would make me fall asleep extremely quickly. Somehow this really worked, since I guess I am not capable of sleeping while standing, at least not without more than 2 days of sleep deprivation.

I can certainly recommend standing to not fall asleep if you decide to do so. It also helped me avoid the helpless gloom I was feeling with Planet of Babel, when I was completely certain I could not finish enough of the game in time. Maybe I was progressing better, maybe falling asleep all the time makes you lose motivation. I don't regret the sleep management this time either way.

Flash is dead!!1!

Among the feedback for my game, criticism of Flash as a platform was very common. I absolutely want to move to JavaScript and desktop builds. I must admit I am somewhat sad to see Flash go - despite its many security flaws it was actually pretty good for making games rapidly. The scripting engine was optimised enough to run software renderers without having to resort to shaders. Historically, Flash was pretty significant in the development of the modern indie gamedev. I don't think there will ever be anything as well-suited for the task as Flash is. Unity seems to be responsible for too many terrible games to count. JavaScript / HTML 5 will hopefully take over, but there are many tiny details (gapless audio being one) that just don't or can't yet work reliably, cross-browser, and cross-platform.

But, once again - I will try to improve plustd in the future and hopefully I won't have to be ashamed for having a Flash-only game anymore!

Other feedback

It is now more than two weeks after I published my game, and I have received enough comments to know / confirm what went well and what went wrong. First of all, the worst two decisions for this game:

  • Unreadable font - I used one of my own fonts as always, but I think the shadow / outline effect was just too much. It is easy to read the text when you know what it says anyway, but this is clearly a case of developer blindness.
  • Text scrolling - The text scrolls very slowly. There definitely should have been a way to make it scroll faster, e.g. by clicking rapidly / double clicking. It might subtract a tiny bit from the "realism" of the typewriter but gameplay convenience is much more important here.

Apart from that, I notice fewer ratings in the humour and theme categories. The game takes a long time to play through, for any ending, and having no speed up option requires a lot of patience. Some people probably were nice enough not to rate the categories not having played enough to be able to tell. But either way, I am not expect very good ratings in humour or theme - admittedly it is quite fair. Ludum Dare is probably not the best for developing story-driven games.

Well, that is it for now! I hope this was fun to read. The next gamejam I will be participating in (apart from the weekly one hour gamejams!) is probably the second Alakajam! in February. Enjoy the bonus:

Some food pictures

(oh yeah, it was my birthday, too)

0 comments for Post-mortem

Add a

Comment submitted! It will be visible once approved.

An error occurred!

Please fill in the required fields!