I took a detour into note-taking apps (some of which are still waiting to be published) but now I’m back to the dev blog, baby!
The good news is that I have a “complete” operational pipeline from the start of the game to when the player enters the scene. Of course, this makes me completely paranoid that the way I have achieved this is A) incorrect, B) riddled with holes, and C) will not stand up in an actual production environment. But it currently “works”, with explicit air-quotes, so I’m going to run with it.
Yes, that is the main menu. It doesn’t do anything aside from allow me to start a new game, but I cannot really do much of anything else until I have had a chance to generate a new game. No, “quit to desktop” doesn’t even work.
Character creation is similarly anemic. I want to allow players to select a portrait, probably from a series of provided portraits, using a back-and-forth button. This currently does not work, but the image is a placeholder for now. The player can enter their character name which will be how they will be addressed and will also serve as the save game folder name on disk. There’s a rudimentary “difficulty selection” section as well; in the end, this will determine how much money the player starts with, the ship they start with, and maybe some faction relationship benefits or detriments, but for now it does nothing. I eventually want to add in some additional options that will be used in conversation systems (like pronouns), and as time goes on, I’m sure there’ll be other things I’ll want to prime the player with.
Here, the “Return to main menu” actually works, but the “Begin your journey” button is what kicks off the entire process.
The Secret Life of Universe Generation
I have to say, I am my own worst enemy when it comes to this project. I don’t blame Godot (entirely), but I kind of do blame the community standards which I’m sure work fine for simple mobile game platformers and shmups and stuff like that. For games of Larger Scope, some bon mottes offered by the community kind of break down, at least when they hit my brain.
After the player clicks “Begin your journey”, the data in the creation form is transferred to the GameManager singleton. This is what will gatekeep access to things like SceneManager (used to clean up and set up the current scene’s assets) and the DataManager (that which generates and holds the data for the game). The main menu emits a “signal” called “new_game_requested” and offers the character form data as payload.
Signals are broadcasts that, when emitted from code, can be picked up by other code which has been told to listen for them. Signals can carry “messages” — arguments, basically — so that we can move data from the emitter to the listener. While this is an extremely flexible system, there’s a few issues. First, it’s a one-way trip; listeners cannot return data to the emitter which is fine because that’s a confusing and pointless anti-pattern. Second, having such loosely coupled communication makes it stupidly easy to hang oneself. I have run into cases where a signal is emitted before a listener has been registered, meaning that the originator is shouting into the void and there’s no one available to act on it. Because loading and ready-up order cannot be guaranteed in many cases, it takes trial and error to find a way that reliably allows for the use of signals and listeners. They are not the panacea to interobject communication that many in the community have been presenting them as.
There are two listeners associated with the “new_game_requested” signal. The first is the script on the Player object. This will take the player data from the creation form and assign it to the Player object. The second listener is with the GameManager itself. This listener will apply the character name as the “save_game_name” value on the DataManager, and it also calls the Big Bang method, also on the DataManager.
DataManager initiates the Big Bang, emitting two signals: one to tell someone who needs to hear it that the Universe generation process has started, and one to tell folks when the Universe generation process has completed. Through this process, data is generated, saved to disk, and assigned to public properties exposed by the DataManager. There are also several methods in DataManager that make pulling targeted data from these repos easier, so I don’t have to keep filtering large data sets to get a single or a small batch of records.
The only two entities which listen for the end of the Big Bang, currently, are the main menu and the Player object. The main menu has an interstitial “Loading…” screen that is shown while the Big Bang is occurring, but the Bang currently runs so fast that the loading screen doesn’t even show for very long. The Player listener fires off a function that activates the physics processing for the Player scene and ensures that the Player is visible. By default, until a new Universe is generated, or a saved game has been loaded, the Player’s physics processing is turned off because it’s this function that drives movement based on player input. Disabling the physics on the Player prevents the player from driving away when the main menu is shown, and this on/off switch will be used later when the player docks at a station, enters combat, or lands on a planet.
Into The Void
And voila! The player is now present in the new Universe. Of course, the next development step is to actually use the Big Bang data to place the star and surrounding planets.
Vaguer and Vaguer
I had to branch the project and strip down the existing scripts, because in the process of trying to use signals to communicate between the various steps, things got way out of hand. For example, when attempting to pass the main menu form data to the Player scene, I was unable to do so because the signal carrying the data was firing before the Player started listening. This caused me to have to re-work the signal paths so that two listeners would be available to deal with the single signal, and that seems to be working OK.
I am still on the fence regarding resources and how to best use them. The Player scene has a script with an exposed property “Player_Data” that accepts a resource. EntityPlayer is the resource I want to use for the player, specifically. This is built on top of another resource, EntityBase. EntityBase has most of the properties that define “an entity” — player or NPC — while EntityPlayer refines the resource with Player specific properties (such as current system ID). I am still fighting the resource property access bogeyman, both with getting and setting, but I do have the player’s movement connected to the properties of the specific “thruster” resource assigned to the “Player_Data” property. There is a complication on the horizon, though: I wanted to make prefab “ships” that contain specific components, so that I could simply assign that ship to an NPC (EntityNPC) so I wouldn’t need to hand-craft NPCs by dragging and assigning resources every single time. However, as a prefab ship is basically the same as EntityPlayer or EntityNPC, I am not sure how to get the data from the prefab over to the Scene entity. That’s a river I’ll have to examine when it’s time to cross it.
Finally, I’m kind of unsure about the feasibility of calling methods directly versus using signals when I can. The GameManager calls the Big Bang method on the DataManager, despite both being AutoLoaded singletons. Thankfully, it “just works”, but I could have very well ended up in a state where GameManager makes its call before DataManager is even loaded. Also, I’ve already run into situations where GameManager needs to know about the Player scene. Because the Player is always in the active scene I could refer to the Player Scene by path. Instead, I have a method on the GameManager, “register_entity”, which passes a reference to the Player Scene to the GameManager from the Player Scene itself. When the Player initializes its data, it will announce itself to the GameManager so the GameManager has a hard connection to the Player Scene. This way, I should be able to pass the Player ref around when I need to in order to get or set data. I really don’t know if this is going to work out, but for the time being it’s no better or worse than if I’d set an exported property and dragged the Player Scene into that slot in the Inspector.