Game engines are great. They save time. Except when they don’t.
Classic Menus, Classic Implementation
I started running into points where I needed to get menus to work properly. The previous implementation relied on Godot’s automagical button and UI systems to cut some work down. However, this kept causing a number of problems. Neighboring buttons acted inconsistently, D-Pad support was inconsistent for some reason, and having to juggle signals and build things out was slowing development down and too inconsistent.
Throughout this week, I had gone in and deleted every interactable menu that was implemented, along with their scripts and behaviors. The menus we need are simple, classic RPG-type menus. We don’t need clicks or tab support or anything. Thus, I instead elected to split the menu system’s three pieces out from each other, not unlike (but not strictly) a MVC pattern.
First, window data and button data are stored into the game’s custom WRAM class. When the menu manager calls to open a menu, window data is generated by a static function tailored to the specific window it needs to create. The window and button data is just that: data. Each button has an associated static function to call, which is passed in along other data by the initial allocator function that builds this data. The menu manager then pushes this window data block to a menu stack in WRAM.
Once a window’s data is pushed to WRAM, the menu manager will build a Godot node from a template scene containing a minimal menu. The physical node is passed a reference to the window data it represents, which it then alters size and position and populates labels. There are no Godot buttons or anything fancy in use, they are simply labels with a reference to an arrow icon. The menu only reads window data and drops the arrow representative of which option is selected in window data. The menu itself basically does nothing but show things, and contains no selection code. The menu manager ticks the current window data that is focused and passes input to that instead.
Each button data is fed a callable static function which is called by window data when the accept button is set. The callable will handle game operations agnostic of the menu itself. The only thing the function knows is what button was selected, but everything else it does must rely on the pseudo-RAM classes or other global references. Upon pressing a button in physical menus, we can perform ops like using a skill or item (based on the selection id and the user’s inventory in SRAM), or we can even call the menu manager to open a subwindow and push another window in. If we needed to know something more, like which party member’s inventory or something to display, we could check the previous menu in the stack for its selection id. Once a menu’s final decision is made and its final command execution is finished, the menu manager can be called to pop all windows and kill all menus currently displayed.
This is not unlikely similar to how a lot of old RPGs would have done it, probably. I still have no time to sit down and learn proper 6502 or Z80 ASM, but the general structure here is independent of the paradigm it’s implemented in. You could pretty much write it in anything from Godot to Unreal to C to ASM. The downside is sacrificing safety from spaghetti code due to how much global data is regularly accessed, but RPGs use a lot of global data to preserve state across its modes. It would be overengineering to attempt avoiding this and ironically just add more complexity and take far longer.
What Working Menus Got Us
Surprisingly, a menu driven game is easier to make when the menu works. My first test was to make the player’s techniques and spells selectable in combat, and this now works easily. The Technique window building code can be called, but the execution can check the game state to see if we’re in combat or not, and choose whether to execute a skill’s in-combat or out-of-combat behavior.
After combat menus worked, I wanted to try creating a Dragon Quest/Mother menu on the field. This ended up being exceedingly simple, since all the behavior is the same and handled the menu manager. Decoupling the text and executable function from the button itself is liberating. Talk and Check functions replaced the general A-button interaction. NPCs now have different behaviors for if they are spoken to or checked. Some find this mechanic archaic, but it is only archaic and pointless if it adds nothing to the game. Having two forms of interaction adds to the design space, allowing to have secrets hidden behind either interaction type. This deliberate action emphasizes a more adventure game style to an RPG. This also lets us write funny gags or hide hints about a character that might be missed. Allowing stuff to be missed can make a small world feel bigger.
Now that I had a check system in place and was reworking how field entities worked to work with Talk/Check, I wanted to fix bugs and add features to the loot items.
Crop and Claw uses two loot tiles on the map: bags and chests. Bags may have gold or random items, but chests may have rare items or equipment. This lets us draw a player into wanting to solve a maze, should they see a chest in the distance, with confidence it won’t be a long and dizzying detour for 5 gold coins. I fixed a few bugs with loot and improved their state handling, as well as added a feature for monster-in-a-box ambushes.
Beats me. I don’t have a crystal ball. Throughout next week, there are other features I’ll want to get in for the menu system, both to stabilize it and to get things interfacing in-game. Using items and techniques on the field may be one of my priorities, as well as equipping items, which is a feature I had wanted to do but haven’t gotten to yet. Whether I do all that or something else remains to be seen.