Defold and TypeScript Pt. 2

As of February 1, 2024, I became a maintainer of the TS-Defold project. Thanks to Justin Walsh for having me on the team!

(I have a weird affinity for doing chores like updating dependencies. It’s like cleaning a house; a little tedious, but so rewarding.)

Here’s a quick recap of everything I’ve been up to.

TS-Defold official

  • The website is now refreshed with updated info
  • I’ve maintained the official templates and template-boom: migrated them to the latest plugins
  • I’ve contributed a bucket-load of types to the Library
  • Kept hacking away at my fork of Defold types

TypeScriptToLua plugins

Defold plugins

  • Updated my PRNG plugin with some new convenience methods
  • Created setv by combining elements of two popular plugins
  • Started on a refactor of estring
  • Archived etable
  • Archived emath

Other tools

  • Made tsd-ext-type-gen to parse script_api files in Defold extensions to export TypeScript types
  • Created def-html5-opt to optimize Defold HTML5 exports

Aeroblast

I’ve released a game on itch.io: Aeroblast

Pixel art plane and title text Aeroblast on a pixelated cloud background

Aeroblast is a simple but addictive top-down auto-shooter. Move using the cursor keys or WASD. Aim at enemies – your character will automatically shoot. Collect power-ups to gain a faster fire rate, restore health, and more. Win by surviving the full ten minute play time.

The game’s full source code is available on Github.

Exploring Defold and TypeScript

The Defold game engine has been in my bookmarks folder for months now, but I’ve only just recently started to make things with the engine.

Defold fits a number of my criteria for a great game engine:

  • It exports to the web, and performance in the browser is actually good!
  • It effortlessly exports to all other platforms too
  • It’s well suited for 2D games (the editor can plop down objects at nice round X and Y coordinates)
  • It’s free and open-source (technically there are limitations in the Defold license, but nothing that affects making games)
  • It’s extremely well maintained with monthly updates

However, I was initially put off by the use of Lua as its scripting language. I had never used Lua, so that was a barrier to entry. It’s also a dynamically typed language, which is a pain to debug.

Then I discovered the TypeScript community extensions (@ts-defold) for Defold. These tools allow using Defold with TypeScript, a strongly typed language that I already know and enjoy.

It turns out I’m more productive working on tools than games, so here’s what I’ve done so far:

  • Created a TypeScriptToLua (TSTL) plugin that will strip the last extension from files that have multiple extensions. This is used to handle Defold’s specific file extensions, so a file name like `player.script.ts` is output to `player.script` instead of the incorrect `player.script.lua`. This plugin can replace the patch file that come with @ts-defold, so you’re no longer locked to early versions of TSTL.
  • Tweaked the type definitions for the Defold game engine from @ts-defold/types. I’ve been slowly describing more of the types that were left as unknown in the original output. I’m not sure how useful this is to the average developer, but I find it satisfying.
  • Created type definitions for @britzl’s new boom framework.
  • Created a project template that includes all of the above.
  • Created type definitions for @thejustinwalsh’s xmath framework. The comments are copied from Defold’s vmath library, so I’m not sure they’re 100% accurate to xmath’s implementation, but they seem pretty close.

I also played around with using Chat-GPT to generate some native extensions. My C++ knowledge is limited, but I was able to cajole the AI into creating something that actually works.

There are already community extensions that cover these use cases, so I doubt my libraries will see much use. Still, it’s fun to see how easy it is to extend the engine, despite being a complete newcomer.

Clones of Vampire Survivors are in the zeitgeist now, so I’m currently trying to create a really simple one of those. I’ll open-source the whole project when it’s done.

Development Tools

I’m preparing to release my first game soon.

There’s a particular tool that I’m waiting to reach stable release before I use it in production. In the meantime, I’ve been improving my own release workflow.

Build process

I now have a fully automated build process, running on Github Actions. With one click, the build process will take care of the following tasks:

  • Minify JSON files
  • Concatenate and minify JS files with Terser (preserving comments with author/license information)
  • Delete development files not needed in release
  • Delete plugin files that are included but turned off
  • Separate pathways for preparing files for the web versus the local version, so it will delete web-only files if the build is intended for installation, and vice-verse
  • Add the path to every game asset to a precache in the service worker
  • Build the app for Windows and Linux installation with Tauri

Tauri

All my games run in the browser – but some people prefer a download they can run offline.

Enter Tauri. Tauri is a tool written in Rust that will bundle all the assets required to run your web app with a browser engine, allowing it to be installed and run locally.

Unlike NWJS or Electron, competing tools that ship static browser versions, Tauri runs on the most recent webview available to your OS. So you’re always shipping on the fastest, most secure platform possible. Granted, browser engines can frequently introduce new bugs with each release, so I wouldn’t necessarily recommend this approach for sensitive, commercial software. But for a hobbyist like me, it fits.

By default, RPG Maker MV ships with NWJS, so it expects access to Node and its file system module to save and load files. Accommodating Tauri required a few changes to my code.

Tauri Store is a simple to use plugin that allows Tauri apps to save local data. It can store similar data types to a browser’s local storage, the technology that RPG Maker uses in its web version. The major difference is that Tauri’s commands are always asynchronous. Luckily I had already written a new save manager that interfaces with web workers, which are asynchronous as well. So it took less than a hundred new lines of code to get it working with Tauri instead.

I also ended up including Tauri Window State in my build. It’s another easily integrated plugin that saves the app’s window position and fullscreen status, so the next time the app is launched, it starts in the same position as when it exited.

Overall my experience with Tauri has been positive. I’m new to Rust as a language, so I appreciate the official plugins having an extremely simple API that only takes a couple lines of code to run.

I experimented with including Rust crates used for compression, so I could compress data before writing it to Tauri Store. But it was a bit of a mess – Rust is very strict about data types, only serialize-able data can be transferred between the Tauri backend and the webview. I’m sure it’s do-able with a few more weeks of practice, but it’s daunting for a Rust newcomer.

Good luck to the Tauri team, who will hopefully be releasing their stable 1.0 version any day now. I’ll be publishing apps with it soon after.

Update

I’m adding this addendum some months later.

Tauri has reached stable. Congratulations to their team and all the hard work they put into the project.

Ultimately, I decided against using Tauri, and instead built a template around Electron.

There are two deciding factors that ultimately made me favour Electron.

  1. Electron’s WebGL performance is pretty good on all platforms, whereas Tauri uses a different engine for each platform, and my testing on Linux was pretty slow with WebGL content.
  2. Electron allows exporting to raw files without an installer, which is Itch.io’s preferred format.

Stardew Valley Thoughts

Originally written as feedback to the podcast Watch Out For Fireballs.

When I initially played Stardew, I dated the washed-up football player Alex with my male farmer. There’s a touching moment at the end of his romance arc where he admits he never expected himself to fall in love with a guy–but he’s glad it happened. Those themes are barely explored beyond that one line, but the implication that Alex has been struggling with his sexuality always stuck with me. His grandfather also expresses prejudice against your (same-gender) marriage, so you can imagine there’s a deeper story about this family left untold.

The second time I played, I dated the fan-favourite Shane. There’s a memorable moment where you go on a date with him to see a football game, and when your team scores, Shane gets so excited that he spontaneously kisses you. I’m sure it’s a cute scene regardless of your gender, but for two men to kiss in a traditionally hyper-masculine setting, it feels risky and subversive.

That said, I’m getting tired of games portraying all romance options as player-sexual. If you befriend these characters, there’s no hint that they might be gay or bisexual; you have to “opt-in” by initiating a same-gender romance. By siloing all queer content, it reinforces the idea that everyone is straight by default, and wholly-optional queer moments only exist to placate queer audiences. It’s a bummer.

Here’s hoping for more from Haunted Chocolatier.

The Club 0.9.5

This is likely the last pre-release build. I’ll primarily be working on graphical materials to support the launch from now on.

Updates

  • Updated fflate 0.7.1 to 0.7.2
  • Updated PixiJs from 6.1.1 to 6.1.3
  • Created a custom build of PixiJs that excludes some modules for a smaller build size
  • Created a custom build of PixiJs Filters, excluding unused filters
    • Added enhanced scanline mode, featuring the PixiJs scanline filter
  • Revamped saving and loading files completely
    • Save files are now compressed and saved (and decompressed and loaded) in a web worker, so as to not bog down regular gameplay
    • In nwjs installs, the files are written to disk using node.js modules in a worker
    • In the browser, files are stored in indexdb using idb-keyval
  • Added additional sounds and map-zoom when talking with NPCs
  • Added a unique description to each object on the map
  • Prevented NPCs from using emotes randomly while talking to you, to avoid confusion
    • They still randomly emote while not interacting with the player
    • Adjusted position of emotes so they’re not as high
  • Added custom mouse cursor

Tactical RPGs and Exploration

Over on the official RPG Maker forums, user Frostorm asked this question: why do tactical RPGs rarely feature aspects of dungeon crawling and exploration?

I decided to weigh in on the discussion.

While it’s not impossible to combine dungeon crawling and tactics, I think each genre tends to have a different approach with different consequences.

Traditional RPGs and Tactical RPGs must have different densities of encounters.

Tactical RPGs have long, challenging battles. If they didn’t pressure you to consider your actions, they wouldn’t really be tactical.

Traditional RPGs tend to have short, simple battles that you can button-mash through. They use dungeons as an excuse to throw dozens of (often random) battles at the player. If their battles were as long and mentally intensive as a Tactical RPG, this would quickly exhaust and frustrate players.

These different approaches often result in different consequences for the player’s resource management.

Traditional RPGs tend to challenge the player during dungeon crawls by slowly whittling down their resources over dozens of battles that are individually inconsequential.

Tactical RPGs tend to make each individual battle–and even each individual action in that battle–consequential. Tactical RPGs often fully heal you after battle, because the focus is on the individual encounter and not on the long-haul.

Divinity Original Sin 2 has both exploration and combat. However, I would argue it achieves success with this formula through expertly crafted game design and a number of tightly integrated systems that are difficult to replicate in RPG Maker.

Each battle has a unique composition of enemies that have been carefully placed into the world–they never feel like tedious cookie-cutter encounters meant to pad out the length of the game. Position and elevation matters in battle, so ranged characters are usually placed on the high ground, to press the advantages that gives them. If you wander into one of these battles unawares, you’ll be at a significant disadvantage.

Conversely, if you explore carefully and notice enemies before they spot you, you can spin the situation to your benefit. You’re able to strategically place each of your party members before you initiate combat, so they also start in advantageous positions. You can even prepare the battle arena ahead of time by throwing barrels of oil onto the field, and their effects persist.

This tight integration between battles and exploration rewards player ingenuity and expression.

I didn’t mention it in my forum post, but it’s also worth noting that Traditional RPGs often transport the player to a different screen when battles start. This break in continuity can make it easy to lose track of your position in the exploration parts of the game. Which direction were you heading before the battle began? If you don’t remember, you may get frustrated as you wander around trying to re-orientate yourself.

Divinity OS2 has its battles and exploration take place on the same map with no break in between, perfectly avoiding this issue.

If you do find the battles too frequent, the game has well-integrated systems to allow the player to avoid combat. Some battles can be avoided by exploring to find a path around. Other battles can be snuck past using the stealth system. Still other battles can be avoided through taking a diplomatic stance in dialogue.

Divinity is a triumph of game design, and it’s a tough act to follow. I think most games opt for a simpler approach because it is much easier to achieve. Not everyone has a studio with 250+ employees.

The Mountain Pre-Alpha 0.0.2f

Lots of back-end changes to set the stage for future development.

But we’ve also got a new title screen!

And I’ve added a new window where players are offered 3 possible choices for an item award. This seems to be a better way of managing loot awards than getting completely random stuff with no choice.

Updates

  • New Title Screen
  • Updated PixiJs from 5.3.3 to 6.1.1
  • Switched from Pako to fflate (smaller file size, faster performance)
  • Fixed issue with tooltips where they could not contain multiple lines
  • Refactored plugins I’ve authored so I have a core set I can share between projects
  • Created new “award choice” plugin
  • Various tweaks to attempt to get better performance
  • Fixed issues that popped up with Ogg sound files on Safari
  • Fixed issues with running as a Progressive Web App (PWA)
Updated Title Screen

Known Issues

  • If you start a new game, then quit before changing maps or opening the menu, then attempt to load your save game, the save game will be corrupted – To bypass this bug, just choose New Game instead of Continue.

The Club 0.9.4k

More technical updates. I’m chasing better performance on lower powered devices.

Updates

  • Updated PixiJs from 5.3.3 to 6.1.1
  • Switched from Pako to fflate (smaller file size, faster performance)
  • Fixed issue with tooltips where they could not contain multiple lines
  • Refactored plugins I’ve authored so I have a core set I can share between projects
  • Switched from pixel movement to half-tile movement in an attempt to get better performance in the map scene
  • Various code tweaks to attempt to get better performance
  • Fixed issues that popped up with Ogg sound files on Safari
  • Fixed issues with running as a Progressive Web App (PWA)

Known Issues

  • If you start a new game, then quit before the first cutscene ends, then attempt to load your save game, the save game will be corrupted – To bypass this bug, just choose New Game instead of Continue.