Post-mortem and results

I am adding this post very late, but – I participated in Alakajam! 2 over 23rd – 25th Februrary. The theme was "You can't see everything", and I made a rogue-like game set in a conspiracy-filled paper world.

Unfortunately, since I am writing this 2 months after the jam, I can't remember what happened as well as usual, but I'll try my best, basing my writing on the git commit log.

Theme reveal

Being closer to the organisational core of Alakajam than that of LD, I got a chance to stream and animate the theme reveal with Danae just before the jam. We both enjoyed the stream, seeing the top 10 themes being eliminated one by one while going through some polls and stats from the previous jam. The video is available here.

Naturally – spoiler alert! – I managed to mess up and reveal the wrong theme ("The Last One") at the very end. It's a tradition. After the actual theme was confirmed to be "You can't see everything", I got to working.

Brainstorming

I tried to stream the brainstorming process for a little bit, although clearly on my own I am not very much fun. The core idea I wanted to keep was that of censorship, i.e. you can't see everything because they don't want you to see.

I also really wanted to make a "document-based" / "stationary" game. The games that I feel belong to this genre are Papers, Please; Keep talking and nobody explodes; or even TIS-100 and Shenzhen I/O with their manual being a core part of the game, ideally printed out. I simply feel that games which require the player to actually keep a journal / notes or consult some physical documents are very cool.

After talking about this for a bit with the stream chat and my girlfriend, I was convinced not to go this way, since such a game would probably be too mechanically complex to feasibly create in 48 hours. The other alternatives were a platformer / puzzle game where the player has to essentially solve riddles (thematically shown as censored data) to progress; and a rogue-like game that would keep the paper setting, but not the mechanics.

Since I wanted to make a Rogue clone for some time, I went for the latter. The theme "You can't see everything" was to be included in the visuals – the papers the player traverses have black spots where words were censored. It was also to be included in the mechanics – items, walls, and enemies are not visible unless the player clicks in their general direction, which temporarily reveals items in a vision cone.

Field of vision

The first thing I worked on was basic paper visuals to form the rooms. As usual with tilesets, I made blocks which can be used in various places of a room (corners, sides, middle) depending on their neighbours. The plan was to have rooms shaped in interesting shapes, although in the end all rooms were simply rectangular. The only exception turned out to be the inventory, which had a nice rectangular cutout in the middle, so that the player could travel from the game area directly into the inventory.

Having some papers and text rendered on top of them, I started working on the field of vision. The algorithm is similar to how many Rogue games do this, via raycasting. For every room, there is an array of boundary tiles, i.e. walls on the top, bottom, left, and right. Normally rays are cast from the player towards all boundary tiles, and tiles are marked as visible using Bresenham's line algorithm.

To modify this such that only a 90 degree cone pointing towards where the player clicked is visible, I first check the angle of the click. Finding out what angle a given point is in respect to the origin (and the screen axes) is done via $\alpha_\text{mouse} = \text{atan2}(y - y_o, x - x_o)$.

To check whether or not every single boundary ray is within the cone, it would be possible to call $\text{atan2}$ for each, then check if the resulting angle is in an interval in relation to the angle of the mouse click. This would work, but would be very wasteful and heavy on trigonometric calls. I doubt it would make that much of a difference for the kind of dimensions I was working with (there might be up to 128 boundary tiles in extreme cases); nevertheless I wanted a nicer solution to the problem.

I generated two direction vectors, 45 degrees anticlockwise and 45 degrees clockwise from the mouse angle. The second is simply the normal of the first:

$\vec v_1 = \begin{pmatrix} \cos \alpha_\text{mouse} + \frac{\pi}{4} \\ \sin \alpha_\text{mouse} + \frac{\pi}{4} \end{pmatrix}$

$\vec v_2 = \begin{pmatrix} -v_{1y} \\ v_{1x} \end{pmatrix}$

Then, for each boundary ray, I check its dot products with $\vec v_1$ and $\vec v_2$. That is, I do an ortographic projection on the two vectors. If both dot products are positive, then the given vector is in the same halfplane as each of the two direction vectors. Given that the two direction vectors are 90 degrees offset, this ultimately checks whether the boundary ray is in the 90 degree cone or not.

After this I can simply use Bresenham lines to mark tiles on the selected boundary rays as visible. Any wall tiles next to a transparent tile that is visited are also revealed. This prevents gaps in vision on walls.

Conspiracy photos

For conspiracy-like visuals, I downloaded some public domain photos showing UFOs, aliens, etc. To make sure they fit visually, I did some post-processing.

  1. Resize to a smaller format
  2. For each pixel: - Take - HSL value - Increase L value a bit in a checkerboard pattern - Increase L value a smaller bit according to a - 16x16 Bayer dither matrix - Choose a colour from the game palette based on the resulting L value

Floor layout

To generate individual floors and how the rooms are connected, I start with a square array of empty slots. Given the floor number I decide how many rooms should be generated, and how many should be story rooms. After choosing an arbitrary starting point, I do a randomised search, where each room is placed anywhere next to an existing room and connected to it.

If the progression of the rooms was graphed, it would be a tree-like structure (i.e. there is only one unique path between any two rooms, and in the longest cases it will cross the root – the starting room). This is quite a simple method, though it has a downside in thta there are no cycles / loops in the dungeon layout. The exit room on average is in the middle layers of the tree, but there is a small chance it appears very close to the start.

Room layout

To generate the walls inside individual rooms:

  • Start with a room that is smaller than the target size, either 7 x 7 or 3 x 3
  • Apply one of 13 patterns ("wall" / "no wall" grids) of the appropriate size to the room
  • While room is smaller than its target size:
    • Duplicate (stretch) a random column or a random row

An extremely simple process and the results are somewhat interesting. One additional step is required when forming room connections, which must always be on the edges of the room:

  • When connecting two rooms:
    • Dig a tunnel from the edge of the room towards the middle until an area without walls is encountered
    • If the centre of the room is encountered first, dig a tunnel at 90 degrees to the left and another one at 90 degrees to the right

This (rather brutally) makes sure that rooms are always traversable, given that at least one side of each of the initial patterns is not completely walled off.

Finally, gold, items, and monsters are randomly placed where there is room.

The case is different for story rooms, which have text and optionally a photo. These never have any walls. Instead, the text is rendered to their background bitmap, and the positions of "dangerous" words is marked. These words are rendered in monotype (such that they align with the room grid) and enemies are placed at their location. So they form a part of the text until the player highlights them with their vision, at which point they wake up and attack.

Speaking of text: for this game I finally got around to implement something in my library that would have saved me hours of time during LD40. Text justification. No word wrapping or anything fancy, but simply rendering text so that it fills a given width of space, filling the spaces between words as appropriate. Considering the story rooms were meant to look like newspaper clippings, and that the text was dynamically generated, I had no choice this time around. I still need to commit it to the actual plustd repository though …

Story generation

I am reasonably happy with how the "story" generation turned out, although very often it simply keeps repeating the same bits of story again and again.

I started with creating a kind of plot timeline. There is a central event, and more "canonical" events are generated before and after it. The idea was that additional events which tell a conflicting version of the story would be added as non-canonical branches of the central timeline. Contradicting a given event would probably work in this system, i.e. when describing it, I would replace "this happened" with "this did not happen" and similar. But, naturally, I did not find enough time during the jam to actually implement this, and so there is only one set of events generated for each game.

The events themselves are taken from a database that contains templates for how they should be described. For example:

/(a UFO was/a flying saucer was/an unknown object was/strange lights were) seen in the night sky?( by %agent%)
%army% claim~(army/s/) strange lights are just top secret prototype planes
autonomous plane abducted by aliens, ~(people/says/say) %people%

The various symbols invoke special behaviour – / chooses one alternative randomly, ? is optional, ~ chooses an alternative based on the form of the given agent (singular or plural), % is a placeholder for an agent. Agents are chosen from a group of names, some singular, some plural.

The above create the core of a sentence. Prefixes such as "Apparently, " are prepended to the sentence. Sometimes a suffix with information about the time of the event is added. Sentences like "Who is responsible for all of this?" are scattered randomly among the story-generated ones. Finally, some words are censored and some are marked as enemies.

Item generation

There is a number of categories of items:

  • Weapon
  • Food
  • Armour
    • Head
    • Shoulder
    • Torso
    • Hand
    • Legs
    • Feet
    • Finger

Each category has its own item names. Additionally, there are universal adjectives and modifiers. For example – Suspicious, Frozen Pizza, as Seen on TV. The stats for each item are randomly generated and hopefully somewhat balanced, although in the first release armour quickly became so strong no enemy could damage the player. Other than random chance, however, there is nothing really balancing the game, and monsters could overpower the player, or the player could quickly overpower the monsters (probably more common).

Results

That is more or less the summary of the interesting bits (for me, anyway). The source code is up on Github, and the game is playable here. I'm pretty happy with how the game placed:

  • Overall - 5th
  • Graphics - 2nd
  • Audio - 14th
  • Gameplay - 13th
  • Originality - 1st
  • Theme - 9th

(out of 45 entries in the Solo division)

I attribute a lot of the success of this game to its relatively high polish.

Food log

How could I forget!

0 comments for Post-mortem and results

Add a

Comment submitted! It will be visible once approved.

An error occurred!

Please fill in the required fields!