LDJam 48 Lessons Learned for a Tactics Game

tldr version:

As a long-time jammer (Godot Wild Jam primarily), I was meta-thinking about LD48 and What Went Wrong :tm:, and my game, Tank Knight, and how I ran into some serious issues toward the end due to the fact I sort of hacked a bunch of the code together at the beginning. I've come to realize that even for relatively short-time-frame jams, you have to focus on making the code good before you can turn on the afterburners and produce the full game - because if the code stinks and you turn on the afterburners, it'll just explode in your face. While my code didn't quite rise to that level, it was definitely not great; there were a few bugs toward the end I just couldn't fix without rewriting the entire game system, and so I simply could not do it. The real issue is the code was so hacked together for the most part that I don't even want to continue it now, despite not receiving a lot of negative feedback. Speaking of which, y'all are awesome, and I love and appreciate every last one of you (even the more constructive criticism has somehow been kind to my game - not something you see in every jam). Next Ludum Dare, I will create something with a little bit smaller scope, and a little bit better art.

Extended analysis:

As a fantastic Godot developer (if I do say so myself - see GodotNuts GodotFirebase library, which I created and am the primary maintainer), I was most disgusted when reviewing my code toward the end with my approach to handling buttons. Little known fact: I'm a bit obsessed with custom resources and have a whole YouTube series on them. However, something - not entirely sure what - went horribly wrong when using them to identify my spells. The problem really was I had too many things going at once: a spell button, a spell ability, and a spell resource. After much lamenting and not being able to figure out one specific bug, which later turned out to be completely unrelated but I had no way of knowing at the time, I ended up pulling out the custom resources. They were, after all, empty; I just used the base Resource class as my resource to identify spells. Despite the fact that that was definitely not the cause of the bug I was seeing, I now believe it was the primary issue in preventing me from "turning on the afterburners" as mentioned above, because to create one new spell, it required three separate classes be defined, and each one was independent. At the least, it would have been way more effective to have the resource actually be a custom resource and hold onto a packed scene of the spell ability itself, as well as the spell button. This would have made it so I could have it all in one place, load up the folder with a FolderResource (my own invention - loads all resources in a folder into memory), and when you "get" the spell, just add an instance of the spell scene to the player, and an instance of the button to the bottom row. This would have been so much simpler, and also, incidentally, given me a place to hook into them without having to use groups. I love groups and am fine using them pretty regularly, but for this game there was just too much confusion in the code around them, because I had to try to find ways to hide/show buttons and had no way to actually reference them. The custom resource for holding them would have been perfect: the resource could also retain the button instance, and then I could have made a much more important decision about how to build out the game, which is...

I should have build the game as a MainLevel (or whatever you want to call it) that loads sub-levels. The main level would be more like a global, always-running scene, and the sub-levels would simply be queue-freed and swapped out. I could then place the character (who would be part of the MainLevel) in its starting square and not have to worry about reloading the UI every single time almost any action is taken. Because of how I had to work around level load timing and rebuilding the UI, it really ended up causing me to use a lot of call_deferred, which in general sucks and makes your code more brittle (since you can't be sure what order things are going in when things are all happening "on the next frame" rather than "right now").

The main mechanic I had was pretty effective despite having way too many classes needing to be made per ability, but I think separating the abilities away from the actual player would have helped tremendously as well. It would have been a little bit harder in terms of targeting and such, but ultimately much easier to add new abilities. I had 3-4 more abilities planned that I just couldn't get to, including some pretty creative ones like rocket punch and diagonal sword throw. One issue I had was with PermaFire, which is a great idea in my mind (throws a flaming pile of fire onto the ground that sits there and damages every character that enters it). I had the ability as part of the character's tree, so when it was placed, it kept moving with the character. I suppose I could have just put a Node down first to remove the transform, but I didn't think of that at the time, so I had to find a real ugly way to to duplicate the node itself, then add it to the root tree and manipulate the collision shapes. It was painful af.

As a final note, just as a tip for you, if you ever want to make a turn-based game, it's best not to put the while loop (if that's what you're using) for the turns into your _ready() function. The best approach I found was creating a 1-shot timer to run a function which starts the turn-based part of the game, and put the while loop in that handler. While I don't know that there's a specific problem with _ready(), I found some odd things happening, I suspect because it wanted them to finish in a reasonable amount of time so it could continue with processing, but I'm not sure - just weird stuff would happen sometimes like not recognizing there were still turns left, and so on. That is all for now though.

Now, back to playing your games, you incredible monsters!