Hello. I’m at a point in my learning where I see potential to make a big mess and want some advice before that happens. Particularly, how to organize game systems like inventory, damage calculation, level variables (eg. locked doors -> remain unlocked even after the level scene is reloaded).
For Inventory (consumable items, weapons etc), what I’ve done so far that seems to work is create a global script called PlayerInventory, within it is a list of every item as a boolean variable to indicate if the player has it or not. So now when the player travels through different level scenes, their inventory is persistent and any upgrades remain. Seems to work so far.
But how would you go about doing this for a locked door in a level scene? One way is to tie it to a key, in the player inventory - if “key” == true, “locked” = false. Ok, fine. What about a wooden crate that has been destroyed by the player? How would you keep track of the crate’s destroyed state without it being tied to a “key item” in the PlayerInventory global script? Is the solution to create more global scripts, like “EnvironmentChanges”? What script should be responsible for remembering this and where should it live?
With regards to a damage calculation system, I think the high level question is similar- how to organize this? The path I’m going down looks like, “DamageManager” global script which handles the calculations and updates, meanwhile the player and enemy scenes have an “HP” node added, with the “hp” value variable set by the parent (the player or that specific enemy).
I’m looking for high-level ideas about how to make these things work together and to keep it as easy to maintain and organized as possible. More details and specifics are welcome, too. Thanks
I have an add-on that will help with game state like locked doors and stuff. You an find it in the Godot asset library as Game State Saver Plugin. If installed directly from the Godot editor, you’ll get a watered down demo with the add-on. But a more extensive example can be obtained by downloading the entire repository. That full demo has a locked door, switches that affect things in other level scenes and remember their state, and more.
As for a more flexible, extensible system for inventory I would look into custom resources. You can add things to the custom resource like an inventory texture, display name, and other info about an inventory item. Then you can use an exported property array and add the items as needed. You can even save the individual resources as resource files for easy reuse and updating later.
Now how to connect a custom resource to the save system is something I have not gotten around to doing, so I can’t say exactly how that would work right now.
Hope this helps!
Adding boolean variables for each item only scales so far, your inventory class has to know about all the items in the game.
The next simplist implementation would be to change the inventory to a list of strings, then your level can check if the list contains a given string without the inventory code needing modification.
If you need to track item counts (e.g. the player has 5 apples), then you can use a dictionary instead of a list. Then you can use the string as a key and store an integer as the value.
I can tell you the approach to these problems in my most recent project.
In my project, I have a central “MainGame” node that is the root of the scene where the main game loop happens, and then there is an “OverworldManager”, which then hosts “OverworldMap” nodes and swaps them as required, as well as hosting the Player character node as a sibling to the OverworldMap, rather than a descendant of it, so that I can warp it around easily.
But MainGame has multiple other children, two of them being the InventoryManager and the PersistenceManager nodes. You can access these as soon as you have a reference to the MainGame node by simply calling GetChild(), altough I have wrapper methods for accessing those.
The InventoryManager node hosts a list of tuples in the form of (ItemType, amount), and it has multiple methods AddItem, RemoveItem, HasItem, etc. All of these just access this list of tuples.
The PersistenceManager is responsible for keeping track of persistent changes in the many OverworldMaps. It’s just a single wrapper for a list of NodePaths for nodes that have been “flagged”. Because OverworldManager never has more than one map loaded at any given time, every node in the map will keep the same exact NodePath relative to the scene root even if you unload and reload the map. This means that, when for example a locked door or a destructible crate is instantiated as part of the map, you can check in its _Ready function whether the game’s PersistenceManager has flagged the path to this crate, and in that case, just destroy it again or QueueFree it outright. You should then make sure to have the PersistenceManager flag this node when you open it/destroy it/etc. You can actually extend this approach to have the PersistenceManager be able to hold multiple flags with values for a given node.
Then, when you save the game, you can easily add independent Save and Load functions to each of these managers and call all of them from a SaveManager node if you want to persist the data across runs. Really, all of these managers may as well be autoload scripts, but behind the scenes autoloads are just nodes that are siblings to your root node. Personally, I avoid autoloads entirely because I’d rather manage these nodes myself, but there is nothing wrong per se with using autoloads.
As for HP and damage; I don’t actually use a node dedicated to HP. Instead, my BattleScene holds Battler nodes, which define their many attributes in battle, one of them being HP. It also has a static function CalculateDamage(Battler attacker, Technique technique, Battler target) that I use to calculate how much HP a given technique should remove, and because it is a private function of Battler, I also get access to private Battler data such as its stat boosts. For persisting the player’s HP, I have a dedicated PlayerManager node. This obviously only makes sense if you have separate Overworld and Battle scenes; if you are fighting enemies in the Overworld in real time, your approach will need to be different.
I hope that helps.
If saving node states is what you want there is a page for that in docs.
DevDuck did a little video on how he keeps his 11k loc project organized, worth a watch for some ideas: https://youtu.be/4az0VX9ApcA
I don’t have any insight about the inventory management, you might be able to look at some of the addons for inventory systems to see how they handle it