Simjam Postmortem + Technical Notes

I've never written a postmortem in my life, but I am told that going back through the two weeks and change of my experience jammin'  might be helpful to less experienced developers (and my future self) who need a reference point for the lifecycle of a short solo project. So let's get started!

[1] Boilerplate - The Development Environment and Basic Structure

For this one I used my favourite environment—LOVE, via the zerobrane IDE. It's served me well for years, and I can quickly build whatever I want and it'll run nice and fast. There was a tiny amount of "system" code that I migrated from a WIP shortly before the jam began, and I made it available right when it began as per the suggestions in the SimJam guidelines. It's basically a system module that handles mouse coordinate transform and fullscreen, as well as the very very simple music engine that basically just stores a list of tracks and plays the current index in the list.

It was literally two source files and an incomplete main.lua.

During the pre-jam time I decided on the basic game spec, which included a short synopsis, a definition of a Student, and Aesthetic notes. My scope was more or less decided on.

Some general notes on the System:

  • The game uses .IT modules for music. Instead of having the engine loop them, I just inserted loop macro commands in the actual modules so it is as seamless as possible
  • This game doesn't use my usual STATE_MANAGER pattern for gamestates. Instead, the game treats itself like a simplified version of a virtual desktop environment (see the Interface Code notes below)
  • Instead of layering a bunch of different graphical buffers/canvases and compositing them, I use just two, with the main one being written to and then upscaled in the draw loop so I can do fullscreen without changing the ingame resolution. I did this to keep the text from being too small to read but without breaking the aesthetic—Pimiko Plus has the text size issue (including the internal update version), so I could wind up backporting this system and windowing to a future rewrite of that game... eventually!
  • INTERFACE, SYSTEM, GAME, and MUSIC are globally-accessible module-like structures with GAME and INTERFACE having a fairly flat hierarchy of objects beneath them. I didn't worry too much about cross-cutting or message passing chains or indirection or anything like that, mainly because doing that in the past has led to some hard-to-reason spaghetti when I go back to actually look at code. That being said, the coupling between INTERFACE and GAME isn't ideal in my opinion and has caused me slight confusion when I look back at some of the final code I wrote three or so weeks ago.

[2] Interface Code - The Jam Begins

Less than an hour after the official start of the jam, I got to work on the interface. I wanted to do a better version of the nice windowed interface I did for Pimiko Plus back in 2017, but with better performance, new features, and less potential for annoying bugs. I made use of LOVE's GPU batching to draw a whole window made up of tiled 9-patch parts from a skin file, with windows getting their own batched draw call (but sharing the same spritebatch—what I didn't actually know before the jam was that I could clear and re-draw the sprite batch on a whim and this allowed me to layer windows).

A few general notes on the UI system:

  • the 9patch image that contains the window skin also contains button graphics. I wound up not using these, but I probably should have
  • windows are metatable instances, buttons are not
  • window contents are separate objects entirely, basically their own gamestate loops
  • windows "capture" the cursor if you are inside them when you click. The window list is searched from front-to-back, and so the first one that captures the cursor gets to register the input. This is why when you click a button through a window, nothing happens—the input search loop breaks out if it's captured.
  • the window list gets reordered based on which window captures the input. If the window capturing the input is not at the front, the list gets reordered with the window at the front
  • The message window for events immediately pauses the game when it is opened by the game, and the corner button cannot close it
  • Window and window content update loops do not run if the window is not visible
  • Windows will always snap to screen edges if they are dragged off
  • Window content update loops will be escaped if there is no "delta" to the window position
  • For performance reasons, the stats window actually gets its own buffer that gets flipped to the window and is only rewritten every game tick, which is 16 times/second at the fastest

One of the things I wish I did is making buttons metatables as well, since that would have resulted in a lot less repeated code—but I spent so much time on UI I really wanted to get a move on and do the actual game content.

[3] Simulation Engine

This was the smoothest and simplest part of the game to develop, by far. Partly because the structure was already in my head beforehand, and partly because the simulation engine itself is pretty simple. It's pausable real-time, with the smallest simulated unit being a "tick" or "quarter". 4 Quarters make up a day, 7 days make up a week, and 52 weeks make up a year. The Speed of the simulation directly corresponds to the amount of ticks in a second, so at the fastest speed you're blowing through 4 days/second!

Some Simulation notes:

  • The player statistics are split between Attributes, Performance, and Traits.
  • Attributes are calculated per-tick, with formulas tweaked to make it so that progress on some attribute must be sacrificed in order to have another increase quickly. Diligence is the only exception to this, since two time management quantities controls it.
  • Performance is calculated on a weekly basis. It uses whatever the value of its governing attributes are at the end of the 7th day, so this gives attributes some ramp-up/ramp-down time to their effectiveness.
  • Traits are awarded for completing projects, and they essentially function as a primary way to unlock new projects in the tree (they're also used for calculating endings).
  • Each character has a birthday! This is just an integer specifying what week they were born, and ticks their age up during the performance calculation.

[4] Miscellaneous Notes

Since that stuff above basically covers the whole game, the rest of it doesn't need discrete sections for each part. I found having an actual scope to be a pretty good goalpost, even if I didn't have a complete roadmap of the game on day 1. Unlike with Citrus, I wound up moving sideways instead of having entire features that didn't make it in, so I'd say that the mere act of setting a reasonable (if vague) expectation just straight up improved my ability to reason about my own project. I'd work on a feature, take the rest of the day to reason about the next feature, then finish implementation the day after and get started on the next.

This cycle did become a little monotonous and the sheer amount of work that went into the interface was driving me insane. Sticking to my guns and doing at least little bit each day was at least made bearable by the support of testers on the Delmunsoft server and the rapidly crystallizing project goals, though.

Some notes:

  • Originally, projects played less of an important role and the game would take you through University and each of the two characters was supposed to have an identical set of projects and outcomes. Obviously I stepped sideways on this particular goal and made projects play a bigger role
  • There is provisions for a third character—technically, you could crack open the game, increase the size of the portrait spritesheet and add another portrait that fits the dimensions in order to access this third character. However, they have no projects available to them aside from the universal ones
  • I had builds of the game tested, on average, three times per week in order to tune the performance. Just in case, wink wink (not that this ever would have been a problem, though)

[5] Final Thoughts

On the whole, I think the fun I had and the satisfaction of seeing a few people enjoy this game way more than outweighed the sag in my mood that happened about halfway through the jam. It wound up being more of a technical challenge than Citrus, which I wasn't quite expecting... but I was a lot better prepared this time.

Special shoutouts to:

  • my testers, who somehow didn't run into any bugs but gave nicely detailed performance reports! And feature requests (such as the project time allocation memory feature)
  • Vulture Party, Xezton, ellenhp, and System Logoff, for cool and inspiring projects and for the palpable camaraderie we shared through mutual pain of mid-jam slog
  • Xibanya and co. for hosting an awesome jam and giving every project an honest shot
  • Anyone who made it through this disorganized ramble long enough to see this particular bullet point

Oh yeah, and I pushed a sneaky update to School that adds a quit dialog 😉

Files 3 MB
Jun 14, 2020 344 kB
Jun 14, 2020

Get School

Leave a comment

Log in with to leave a comment.