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

  • Karu 🐲@lemmy.ml
    link
    fedilink
    arrow-up
    2
    ·
    2 days ago

    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.