It’s been an incredibly difficult year for me, so my game development has had to fall by the wayside for a while. I’m trying to find ways to remotivate myself, so I thought I would try posting about it here.
Minerva Labyrinth is a traditional dungeon crawler (chief inspirations being Wizardry and The Bard’s Tale) with magical girl and horror themes. It takes place in an unnamed medium-sized city in the southeastern USA, sometime in the mid-21st century. You control a party of magical soldiers and explore extradimensional labyrinths to eradicate a twisted horde called the Hate. The website is here and has some gameplay footage, screenshots, and devlogs. This is my second dungeon crawler, the first being a jam game that I released in 2020.
I have been working on this off and on (mostly on) for a few years, and it was getting pretty close to completion until Unity blew itself up last year. I have started converting it to Godot, but the process is extremely painful and demoralizing, and has quite a ways to go.
The blurb:
In the 1990s, humanity finally comes together on an unprecedented scale to confront the global climate and energy crises that threaten its future. Decades later, humanity thrives in a new era of cooperation and humanism, and the recent rediscovery of ancient magic promises miraculous new advancements. Yet as human strife recedes, something dark and forgotten returns from deep in the earth…
most my experience with drpgs is etrian odyssey - this seems to have something similar with front/back rows (though i imagine that’s hardly new for drpgs) and i feel like i rarely see indies in this genre at all.
id be down to hear more about what the process of porting is like, with godot. I’ve definitely seen devs that switch over, for one reason or another, but don’t necessarily go out and rebuild a project that was already in progress. Or when they do it ends up being an opportunity to change a lot of things and the end result is pretty different. Has your process been mostly to stick to the original plan? What are some of the bigger hiccups, outside of just getting into the groove of a new engine?
It does have front and back rows for both players and enemies, although the mechanic is more strict than in EO (which I actually never played until… right now, with the Steam release). I do take a little bit of general inspiration from JRPGs here and there, and I borrowed a lot from Experience’s games in designing an interface that is playable with a gamepad.
It’s true that there aren’t many indies (or anyone) making games in this style, although there are a few. Part of it is just that the gaming press completely ignores this genre. The EO Steam trilogy might have gotten a mention, but other than that, the last one I can remember most of the press even speaking about was The Bard’s Tale IV (incidentally, one of the worst RPGs I have ever played).
I have tried to convert straight over as much as possible, but there are some things that I took the opportunity to improve, such as using Godot’s signals to simplify some of my convoluted input and event code.
The most difficult part has been that Godot SUCKS at handling data-driven design. The inspector will only display classes that inherit from Node or Resource, no custom classes at all. There are also other irritating limitations to the inspector, like (as of 4.1 at least) the lack of typing on dictionaries and the fact that generics break the entire editor.
An example of how this disrupted my conversion is items. In Unity, inventory and equipment were handled entirely in code and didn’t physically exist as GameObjects. This didn’t work in Godot because, if I recall correctly, I couldn’t actually assign items to chests or enemies in the inspector, because InventoryItem was a custom class. Instead, I had to rework everything to handle items as Nodes, even though there is no reason or benefit to doing so.
There’s also a really irritating behavior around duplicating objects, either in the node editor or in the project view, in which Godot only makes shallow copies of everything. This means that, for example, if you have a node that contains a List of integers, and you duplicate that node, the duplicated node will be pointing to the same List. If you change the List on one node, you change them both. WTF would I want that behavior? You can break these links and do a deep copy, but you have to do it for each object individually. There is a “deep copy everything” button, but it doesn’t work. Note that there is no clear indication that an object reference is shared by other nodes or resources.
One thing that bothers me in the back of my mind is that Godot’s “everything is a node” mentality is directly at odds with the fact that the manual explicitly warns you not to use too many nodes at a time. How many is too many? The manual doesn’t know. I also gather that nesting nodes too deeply is a performance problem, just like in Unity. But when everything is a node and every node is composed of other nodes, given that pure data is generally unusable, how am I supposed to avoid that?
This week I’ve been picking at getting my current workspace set up for development again, a little bit at a time. This weekend I finally stepped back into the project and got a few small features migrated over. It has been a long time since I have been up for continuing, so it was nice to make some progress again.
I decided to take a break from raw code migration and try migrating the first level of the game, the subway station. This is an extremely simple level that serves as the introduction and tutorial. All of the geometry is in place, so now I need to migrate all the scripted events. These are things like dialogue and flavor text, switches, treasure boxes, and enemy encounters.
Actually building a real map as opposed to a test room has helped me better contextualize the migration work I have been doing, plus get used to what my workflow will look like in Godot and fill in some gaps there.
Today I got the save / load game functionality mostly migrated, except for the GUI and some of the validation and error handling. Line for line most of the code worked as-is, since it was mostly just serializing C# data to and from JSON. Most of the changes I had to make were because some of the data has been rearranged over the course of converting to Godot. I think it is better organized now than before, though.
I don’t do anything fancy with my saves. I just serialize only the data that is necessary to reconstruct the game state and write it to several JSON files. The files being human-readable is very helpful for testing. I don’t really care about maximizing compression or trying to prevent players from modifying them.
Godot has its own classes for file and directory access, but they’re awkward, so I opted to stick with the built-in C# classes. I am not sure if there is a reason I’m supposed to use them, or if they were just obligatorily copied over from GDScript for parity.
I wanna ask a question I hope doesn’t sound off. What was your motivation here for building this in Godot/Unity on your own as opposed to using something like RPG Maker?
Looking great so far!
I wanna ask a question I hope doesn’t sound off. What was your motivation here for building this in Godot/Unity on your own as opposed to using something like RPG Maker?
Thanks! Control, basically. I wanted total control over all of the rules, mechanics, and presentation, including the first-person perspective. I’m not that familiar with RPG Maker (the only version I ever tried was '95), so it might be more flexible than I think, but I really wanted to build my own systems from scratch. My stats, my die rolls, my GUI, my cadence of gameplay. Working out the rules is part of the fun, anyway.
From what I know, there’s an RPG Maker module out there that hacks in first-person, but I’ve played one or two games made with it, and it doesn’t seem to work very well. Which is not to criticize, I’m sure it works as well as it can.
When I developed the precursor project in 2020 that became the groundwork for this one, I had already been working with Unity for a little while, so it seemed like the natural choice to continue with. I had actually attempted a similar project years before with a Java-based engine called jMonkey, but I really don’t recommend that one. At the time, I only chose it because I was also trying to relearn Java professionally after not having touched it for many years.
After Unity blew up last year, I tried out a few other engines. I settled on Godot because despite being a paradigm shift from Unity, it still seemed like the most capable and the most able to support the code base and workflow that I had developed under Unity.
I finished migrating the “lifecycle” of the game, meaning that you can start or load a game from the main menu, save or load in-game, quit back to the main menu, transition between levels, and have your party wiped out (for which I can configure one of several results). A lot of this was GUI work, and I don’t like GUI work, but fortunately Godot has pretty good GUI tools.
One thing I particularly like in Godot is its implementation of signals. Obviously Godot did not invent this (it’s just listeners / observers), but Godot explicitly promotes it as a preferred design pattern and builds in GUI and code support to trivially define and connect signals*. As a result, I have made much more use of them than I did in my Unity code, which has helped decouple a lot of my components and simplify communication. In particular, I have a singleton that I call SignalDispatch that allows me to have any component anywhere listen to any signal it needs to hear. This has been a boon in reducing how much intercomponent wiring I need.
Not much to screenshot here, sorry.
* Granted, this is an engine dependency that would need some work to redo if I had to migrate again. Good thing you don’t need an online account to access Godot.
Shop functionality is migrated. This is one of the major components that I was dreading (and putting off). Luckily, it only took a few mornings and evenings to get it done, and with less code than before.
The item list is built from the same dynamic button panel scene and script that I use for skills and inventory (both in and out of combat) and file operations. It needed some small adjustments to work for the shop, but having a flexible prefabricated submenu for this sort of thing has saved me a lot of work.
The other major feature that was necessary for headquarters was the dialogue menu, which is now ready. There are three friendly NPCs in HQ that provide services, plot updates, and some optional background lore. The menu options fire off arbitrary scripts, usually dialogue, but this menu also lets you open the shop, so it was the priority to get working first.
The available dialogue can change as the game progresses (although I haven’t written much past the first set). One brand-new feature that I never got to in Unity is marking updated topics with a “!” and yellow highlight, which is also saved and restored with your save file. This way it’s easy to tell when someone has new information on a topic.
The menu isn’t quite as flexible as I’d like - it can’t dynamically add or remove options, for example - mainly for expediency’s sake, but it should cover my needs for now. I want to get this migration done. If I need more functionality later, it’s a pretty small class and should be easy enough to refactor.
Whew. I finished migrating the first real dungeon level (the subway doesn’t count). This took more time than I expected; almost as much as building it in the first place, minus the time spent on art and level design. Some of the time was spent filling in scenes, materials, and scripts that I didn’t have yet, so every piece is one more thing out of the way for the next level.
Hopefully I didn’t forget anything; it’s easy to overlook things when I’m just scanning over a billion objects in the Unity editor. I think building the first draft from my paper notes is actually easier, since there are fewer details. What I initially draw on paper isn’t final; a lot gets filled in or modified as I iterate on it on the computer.
This is easily the simplest of my dungeon levels, so getting the remaining ten completed levels moved over is going to take some time.
One week later, I have three more levels migrated. Pictured is level 4, the first level of the second dungeon (more brightly lit than it appears in-game). The next few levels have more complex geometry than most others, so they might take a little longer.
One irritant that keeps messing me up: Godot is finicky about saving data in the inspector. If you edit a field and then focus another object or panel, rather than focusing another field on the same object, it may not save your changes, even though the field itself will still display your edits. This really confused me until I figured out what was happening. Fortunately, the console does display a “Set _fieldName” status message when it saves, so you can watch that to make sure you don’t get phantom data.
The Godot inspector in general is a bit less ergonomic than Unity’s, so editing the large volume of data I have takes a little longer than it used to. I wonder if managing some or all of this in spreadsheets and importing it would have been easier in the long run than doing it all directly from the editor, but we are where we are.
I did write a small script to export my ~500 state variables from Unity and import them to Godot, which saved me a lot of time, but for most of my other Unity data, that would be more trouble than it’s worth.
I thought I’d do something different this time and introduce the main cast of NPCs. First we have Dr. Regine Champier, anthropologist, paranormal researcher, and current department head of magical studies at the university. The Minerva project, while partially funded by the university, is really Regine’s pet project, though what drives this lifelong pursuit isn’t entirely clear.
Very much a married-to-her-work type, Regine is dedicated and driven, but not always very personable. As the sole individual in charge of the Minerva project, she is your de facto commander and directs each mission you undertake.
Diana is a recent graduate from the university. She majored in cybersecurity and minored in magical studies, the combination of which led to her getting involved in the Minerva project. She runs the operations room at headquarters - a base of monitoring and surveillance making use of both modern networking technology and experimental magic. She can’t communicate with you directly through the dimensional interference surrounding the labyrinths, but she can still keep an eye on you and pull you out in an emergency - though doing so comes at a cost.
While cheerful and flighty in person, Diana takes her role extremely seriously while you’re in the field, and will do everything she can to keep you safe. She can also provide some general intel and hints about your next mission, though what truly lurks in the labyrinths is up to you to discover.
I’ve just finished migrating the last remaining functionality and content from Unity. All I should need to do now is a full test run through to make sure that everything is working properly and that I haven’t missed anything. Once everything is clean, I can call this conversion effort done, close the book on Unity, and get back to actually making the game.
Huge! Is the gist that you have been working on, say, parsing and unpacking .asset files from Unity? Those are… definitely a pain to work with from the outside, in my experience.
I feel like the part I’m most curious about is the maps, is there some sort of process that lets you build them out in 2D? Is adding quest points/interactables something you just do from Godot’s editor after bringing in a map or is that in your data for a map as well, so on
I actually did almost all of this by hand except for my scriptable object game state variables, which I did write a script to import, since they are very simple objects and there were a lot of them. Early on I considered trying to import maps and such from Unity, but I decided I didn’t want to go there. Instead I focused on recreating my toolbox and workflow in Godot. I would have had to do this anyway, since the game wasn’t finished yet in Unity, and because I want to make more games like this. Since the game is grid-based, it’s not that hard to reproduce a map exactly; I just have to be careful not to miss anything. I had 15 levels planned for the final game, not counting the tutorial map; of those, I had 11 completed in Unity.
I design first on grid paper, but I do all the implementation in the Godot editor. I have a big bunch of scenes representing all the typical stuff that goes into a level. Everything you see in the screenshot above is a scene - wall segments, doors, events, etc. In Unity they were prefabs. Each “room” is a 2x2x2 unit cube, so I set the grid snap to 2 units, and then just snap everything together like Legos. The scenes for event points are just templates with all the child nodes and such already assembled; I just edit a few pieces of each one to implement the specific event, like setting which state variable controls whether the event has happened yet or not, or which enemies appear in an encounter. Each component of an event (I call them “room scripts”) is a node, so I can string together as many as I want in a single event point; for example, if I want to print some flavor text after a fight, I just add a combat script followed by a text script, and then usually a state change script to mark that event as completed, so that it doesn’t fire again.
My geometry isn’t limited to two dimensions; since this is all done directly in Godot, I can draw high walls or pits for visual effect that extend above or below the playable area (the molten river in the OP is an example of this). When I first developed the movement code, it supported moving vertically as well, which would allow for multilevel maps, but since I have yet to attempt that, some things like the automap don’t properly support it. Maybe next game.
The main game scene is a bunch of singletons and GUI controls with a “Level” node that serves as the parent for the map scenes. When the player transitions to a new map, I instantiate the new map scene, delete the old one, and reposition the player scene to the requested transition point. This is an improvement from Unity, where most of the game had to be reinitialized on every scene change. You can have multiple scenes active in Unity, but it’s a huge hassle and I never did much with it. My load times in Godot seem to be significantly faster than in Unity, probably due to not having to recreate the huge GUI every time.
For now, only one level can be active at once, but for a future game I’d like to connect them seamlessly. I did this for my old RPG project that never went anywhere, but I haven’t attempted it yet here.
Obviously the downside of building all this in-editor is lack of portability, which hit me for this conversion. When I first attempted an RPG years ago, I wrote my own level editor for it. I really didn’t want to do that again, so when I made MM0 in 2020, I explored how I could make Unity do that work for me. I have no real interest in building tools, I just want to make games. I might at some point explore something like describing a level as JSON so that it can be imported and exported more easily, but that would be a project in itself.
Dr. Vivian Crowe (who doesn’t care for her first name or title) is our final NPC. Originally an electrical engineer by trade and education, working with Regine has led her to turn her talents towards something more akin to alchemy. Though Regine’s theories are the bedrock of the Minerva project, it was Crowe who created new medallions to unlock the dormant power of the crystals.
Crowe is our third and final NPC, and probably the one that players will interact with the most, as she is essentially the quartermaster. Crowe can enchant gems for your medallions to manifest new and more powerful equipment, but she’ll need plenty of magical residue from the labyrinths in order to do so.
Incidentally, Regine and Crowe were originally going to be named Vesta and Vulcan respectively, after the Roman gods of the hearth and the forge. I had toyed with these being code names based on rank or position, and the Minerva project being more widely established. I ended up demilitarizing it somewhat, and reducing it from a full-fledged organization to more of a startup or experiment, so I gave those two more ordinary names as well.
Diana was always Diana, named for the goddess of the hunt. It seemed to suit her, so I kept it.