Devlog 2021-11-21

  • Built a 2d block-pushing game with mediocre puzzles

Grab the Gems

You can play Grab the Gems online. – at least in theory. As I’m writing this my webhost seems to be having issues so if it doesn’t seem to be loading very fast, try again later I guess!

Grab the Gems is the name of a number of games I’ve made in the past. I come back to it and make “a new version” every now and then, usually as a way to try out a new programming environment or tool. I might come back and revisit this one some day because there is actually a certain amount of (very dumb) “lore” that I did not include in this game at all.

This particular version of the game is based heavily around simply Sokoban-style block pushing puzzles. Every level has a door that needs to be opened by pressing down a switch, usually with a block. Blocks that fall in the lava turn into platforms you can walk on. There are, in fact, gems to grab, but there isn’t… actually any particular point to getting them.

But Why?

So if I didn’t get around to adding in that stuff, why did I make it? Especially when it took two weeks!?

Well, first of all, you’re not the boss of me. I’ve been kind of exhausted and needed to pull back a bit on this stuff to keep from burning myself out too much.

Second of all, I specifically chose it to learn more about two areas of Godot that I want to get better at: controlling asynchronous behaviour and building editor tools, so I focused on those instead.

Asynchronous Behaviour

The original Grab the Gems was “turn-based” in the sense that it was a QuickBasic game where nothing happened at all until you pressed a key. So far, most of the Godot work I’ve done has been with real-time stuff, and I wanted a simple-ish game to dig into to try and nail a turn-based loop. It SEEMS like it should be easy, but it turns out that the standard way of doing things in Godot leans very heavily into these real-time systems. Even in a more fluid movement/real time game, I want to have a better understanding of event loops and such because I’ll need them for things like cutscenes and textboxes.

I already have some of this stuff kind of worked out for Black Mountain but it’s always felt a bit hacky and hand-wavey. I was hoping to figure out a more solid way of doing it! I had perhaps moderate success. I feel like I’m coming to a better understanding of it, mostly, but I still don’t feel like I know how to update the systems in Black Mountain appropriately to make things more controlled and predictable. I’m sure I’ll figure it out, though.

Editor Tools

Since I kept talking about it, I decided to use this as an excuse to try building some tools and then actually use them.

One of the problems that I’ve had a number of times in making these games is getting things in the right place in the scene hierarchy. In this case, all of the “entities” in the level (the player, blocks, switches, gems, etc) all live in a YSort node (called “Entities”) sandwiched between under and over tilemaps.

Standard Godot UI gives you a file explorer. As you can see I’ve got all of my Entity .tscn files tucked into a scene/entity directory. I can grab any of those .tscn files and drag them into the currently edited scene to place one of those entities. That’s all well and good, but it will create the entity as a child of whatever node you happen to have currently selected. I want them all to land under Entities, but if I happened to just be dragging around “Gem” to reposition it and forget to click on Entities in the scene tree again, if I drag another Gem.tscn onto the map, it’ll become a child of “Gem”. That will look correct, and might even work most of the time, but I don’t want to just have my nodes strewn all over the place willy-nilly.

It’s also a bit of a pain, because I want to grab the .tscn files, not the .gd files. Yeah, I could put the script files in a different location than the scene files, but I prefer this layout.

So I set out to make myself a palette of entities that will just always drop them into the right place.

And I did. Getting it working probably took half of the total development time, in the end, but it DID actually make creating levels faster. It was 100% not actually worth it for this project, since I only bothered to make three levels, but it’s definitely something I’m going to be cribbing in the future for other projects. The code isn’t quite just drop-in and it works, but it shouldn’t be hard to rework as needed. So that’s cool!

The basic parts of it are:

  • Create a new plugin
    • Plugins are very cool but plugin development is a pain in the ass. I had to frequently reload the editor, since an error in some code might lead to having UI panels left over that I can’t ever clean up.
    • Another annoying thing is that when you change the code of a loaded plugin, it appears to reinitialize all your member variables but it doesn’t re-run _ready() and so on. If you have a saved reference to something, it just throws it away, and now you get a bunch of errors.
    • It is incredibly obnoxious that Godot’s editor doesn’t save your window size and position. I am using a single large 4k monitor, and Godot really wants you to use it maximized. There are apparently workarounds you can set up with a plugin, so I’m going to look more into that, but it’s offensive that I have to.
  • Instance some UI and add it to the docks
  • Add a refresh button
    • This saved me from SOME reloading at least.
  • Get a list of all the .tscn files in the scene/entity directory
  • Generate a preview of each one
    • Previews are built into Godot! Accessing them is awful. You must have access to the (global but NOT singleton) EditorInterface object to get access to the (global but NOT singleton) ResourcePreviewer object, but you can ONLY get the EditorInterface in the actual EditorPlugin class, NOT in the UI scenes that it instances.
    • I used a disgusting hack to get around this – since the EditorInterface is global for all EditorPlugin objects, I just instantiate a new EditorPlugin and get it from there. This trick is documented in Godot Q&A but it is another thing that is offensive to me.
    • Also worth noting that once you DO get the ResourcePreviewer, you can call queue_resource_preview() on it – this takes a path, an object and function to callback to once it has the preview ready, and a “userdata” for you to pass info through. It turns out that queue_resource_preview() will not actually call that callback, if you pass null as the userdata. Pass 0 instead.
  • Add drag and drop to preview buttons
    • This was surprisingly not too bad, but I didn’t bother to use the built-in drag interface. I just used button down and button up signals on TextureButton controls.
  • Make UndoRedo work
    • This was kind of annoying to get right. You’ve got to supply both “do” and “undo” functions, and in this case since I was instancing a new version of the packed scene, I needed to make sure the lifetime on the instance was right. Instead of calling queue_free() on it for undo, you just want to remove it. Instead, instance the scene before you set up the do/undo methods, and use add_do_reference(instanced_scene); that way it’ll be around for redo to add it back to the scene, but it’ll get cleaned up it falls out of the UndoRedo history.

I’m probably forgetting some pieces. Like I said, it was probably the hardest part of this whole project. It’s very nice to have it working, though, so it may just be some pain I have to put up with, in order to have a nicer time of building the rest of the game.

Have a good week!