A Curse Throughout Eternity - A Post-mortem of Planting Uneasy Feelings

When I first set my goals for 2021, I had anticipated learning a lot more about Kotlin and its quirks. And what better way to put it through the test, by using it in a game jam?

A Curse Throughout Eternity - A Post-mortem of Planting Uneasy Feelings
Photo by Sebastian Unrau / Unsplash

When I first set my goals for 2021, I had anticipated learning a lot more about Kotlin and its quirks. As the year progressed, I didn't touch Kotlin at all, instead using Python for various projects (namely Candella AppDB and No Love). When I looked back at my goals a month or so ago, I questioned whether I would ever use Kotlin for a project this year. That concern was addressed pretty quickly thanks to Godot Kotlin/JVM, a module for the Godot engine that adds support for the Kotlin language a là JVM. I tested it out in early August to see what it felt like, and I was excited for it. And what better way to put it through the test by using it in a game jam?

I decided to enter the 36th Godot Wild Jam, which just turned three years old, with this newfound technology. This jam's theme was uncontrollable growth, with some additional wildcard options. I wasn't particularly enthusiastic about the theme, but it was doable nonetheless. After some serious brainstorming on the first day in Notion, I eventually settled on a game where you had to inflict a curse on as many people as possible by choosing only one person; I felt a bit inspired by the plot from Dark Shadows, and it seemed to fit the theme pretty well.

I started prototyping right away, writing all of the scripts in Kotlin. I started by creating a player that could move around the world and a basic entity that could be infected when the player presses a key (which locks afterwards to prevent re-infection). From there, I decided to design an algorithm that determined how infections would spread; the when statement in Kotlin was absolutely perfect for this. This is the snippet used to make the infections spread:

// Entities with a high immunity (0.9..1.0) don't spread the curse at all.when {    
// Infect a single individual. For cases where the immunity is greater but the list of non-infected 
// individuals is smaller, this is also executed to prevent a JVM exception.    entity.immunity in 0.61..0.8 || nonInfected.count() < 3 -> {        lastInfected.add(nonInfected.random())
lastInfected.last().infect()    
}   
// Infect two people.   entity.immunity in 0.4..0.6 -> {        nonInfected.slice(0..2).forEach {           
it.infect()         
lastInfected.add(it)        
}   
}
// Infect everyone near the entity.
entity.immunity < 0.4 -> {      
nonInfected.forEach { it.infect() }     
lastInfected.addAll(nonInfected)    }} 

The when statement can be used like a switch-case in other languages, but it offers a lot more versatility for pattern matching, which is even better (thanks to Haskell for the inspiration, I think!). Besides the when statement, doing a lot of operations with Kotlin felt just right, thanks to what collections offer (as well as the mix between functional and imperative programming). I felt just as fluent in Kotlin as I did with GDScript; compared to C#, this was pretty amazing.

Within a couple of days, I made a basic prototype of the game which worked well. I began creating the sprites for the game so I could better visualize the players and infected people. I decided to make the game randomly pick outfits for the different people that could be infected to spice things up a little, though I probably could've made more to make it more unique. For the environment, I decided to use the Modern Office tileset that I purchased from LimeZu, since that would cut down a lot on asset development. Sooner or later, I got a decent looking game up and running.

For sound effects, I grabbed some free screaming sound effects by Serles Jordi on Freesound, as well as some office ambience from Dean-Raul DiArchangeli from the same place. At this point, I discovered that WAVs don't actually loop unless you encode the data into the file, hence why some of my games don't loop their audio as expected. To fix this, I re-encoded the files as OGG, which worked like a charm. For the font selection, I opted to help a fellow jam participant by beta testing his font family, Salmon 9; I absolutely love the font, and I might use this more often because of how well the variants work with each other.

I improved the infection algorithm a little bit and began making more levels for the gam with different possibilities. I felt that I didn't have the energy to add more mechanics to the game, so I decided that it would be relatively short with five levels and a tutorial sequence. I copied over the dialogue code from an old game prototype and rewrote the script in Kotlin, which was pretty easy to pull off. The dialogue I wrote was designed so that players understood what it is they were doing, without explicitly telling them. In hindsight, unless you were familiar with how Angelique Collins talked about her curses in Dark Shadows, you probably were clueless. Thankfully, the HUD I designed makes up for this. At this point, I had a working game up and ready. I made an export of the project, created the embedded JRE, and uploaded it to Itch.

But that's when the trouble started. As I had discovered (or rediscovered, as I should say), creating an embedded JRE for the game is host-dependent; the JRE I shipped with the game only worked on macOS; specifically, Intel Macs (though I imagine you probably could play the game with an M1 Air like I do now). I kept getting a flood of comments telling me that they couldn't open the game; I tried to remedy it by writing a support document that explained the issue and potential solutions, though I didn't come up with a real solution until one of the Godot Kotlin/JVM devs told me the exact issue (they were just as interested in the game as much as everyone else). I decided that, until I could rebuild the project, to tell people how to make their own JRE for their platform. Thankfully, Neko-Monika on Itch provided some JREs for use, which helped out a lot.

This was also a submission that I couldn't get running on Ubuntu Touch (UT), because I'm still working on developing the port to that platform with @abmyii (the developer responsible for porting Godot to UT) and the Godot Kotlin/JVM team; this was when I first discovered the quirks of embedding a JRE. To this day, I am confused as to how I didn't pick this up when exporting the project the first time.

Additionally, I discovered that I forgot to apply the @RegisterSignal decorator to an important signal in the game, which prevented people from playing it. Thankfully, I was able to fix this issue before the deadline, and now the game works as intended (that is, if you don't mind embedding your own JRE). As it turns out, this seems to a bug between the editor and the JVM since this error isn't caught in the editor, but it does get caught in the exported versions of the game. The issue is actively being investigated on at the time of writing.


Overall, I had a lot of fun with creating this game, and I am extremely impressed with how polished the Kotlin/JVM module is, considering it's in an alpha state. I am unsure if I want to write my games using it in the future, but I'd certainly enjoy it.

Have a nice Kotlin ❤️.