Posted On: 2021-05-03
Programming can be frustrating. Rework is commonplace, as requirements, technology, and tools constantly shift under developers' feet. This is a challenge every developer faces, yet, every once in a while a solid footing in the quagmire will appear: something that, for one reason or another, doesn't actually need to change, despite everything around it shifting. Today's post is a story of just that - a message of hope from a little thing that didn't need to change.
Over the past couple of weeks, I've been working through the process of switching my dialogue engine. I don't really want to get into why in this blog post (it's an involved enough explanation that it could be a post of its own), but what's relevant here is that, while switching out the engine represented a significant rework effort, I estimated the long-term effort savings from doing so would be greater. As I worked through it, I aimed to use an incremental approach: replace a single part and see what breaks. Unsurprisingly, most everything broke, so I disabled as much as I could and worked through each piece one at a time.
When I got to commands - the mechanism by which the script controls the game engine (ie. playing animations or moving characters in a scene) - things suddenly became harder. Where my previous engine (Yarn) ran commands asynchronously*, the new engine (Ink) executes its commands (aka. "externals") synchronously. Synchronous execution makes it a much better fit for complex computation and provides built-in support for return values, but that makes it far less suitable for controlling effects that display over time (such as animations). Rather than try to modify the engine to overcome this, I instead opted to create a parser that could distinguish between lines of dialogue and my own custom command syntax**.
Having written a new command execution system, I was prepared to rewrite every single command - as I had no doubt they were tied closely to Yarn's implementation. After a bit of digging, however, I was pleasantly surprised: the way I'd implemented commands was completely engine agnostic. All I needed was a command name and an array of (string) arguments - and that was exactly the output of my new parser. I could use the exact same command objects for both Yarn and Ink, and everything would just work.
This simple spot of good luck - finding that both engines would play nice with the same command implementation - was such a welcome relief from the struggle of swapping engines. As I briefly paused in my labors, this felt like a welcome, cool breeze. There is still much more to do, but that moment of reprieve made it all feel more manageable.