Accessing dictionaries inside dictionaries; is it practical/possible?

Hello everyone, so, I am trying to make a complete rewrite as fast as possible of my game as you know it;

Now, before, I used a class system for all character data, but it all got out of hand quickly because of my terrible naming conventions/programming practices previously.

As you know, if you pile good stuff on garbage, you basically ruin the good stuff,
now, I had a brilliant idea to just use a Database script that stores a bunch of dictionaries; so far i wrote this file;

extends Node
@export var region_code = “JP”
@export var cartridge_header = “Project”

@export var Party := {
“HERO”: {
“Name”: “Default”,
“HP”: 15,
“MP”: 20,
“MAX_HP”: 15,
“MAX_MP”: 5,
“Luck”: 15,
“Strength”: 20,
“Inventory”: []
,
“NesuPowers”: []
,
“Sex”: 0,
“SkinTone”: 0,
“HairShade”: 0,
“HairStyle”: 0
},
“ALLY1”: {
“Name”: “Default”,
“HP”: 15,
“MP”: 20,
“MAX_HP”: 15,
“MAX_MP”: 5,
“Luck”: 15,
“Strength”: 20,
“Inventory”: []
,
“NesuPowers”: []
,
“Sex”: 1,
“SkinTone”: 0,
“HairShade”: 1,
“HairStyle”: 0
},
“ALLY2”: {
“Name”: “Default”,
“HP”: 15,
“MP”: 20,
“MAX_HP”: 15,
“MAX_MP”: 5,
“Luck”: 15,
“Strength”: 20,
“Inventory”: []
,
“NesuPowers”: []
,
“Sex”: 0,
“SkinTone”: 0,
“HairShade”: 2,
“HairStyle”: 0
},
“ALLY3”:{
“Name”: “Default”,
“HP”: 15,
“MP”: 20,
“MAX_HP”: 15,
“MAX_MP”: 5,
“Luck”: 15,
“Strength”: 20,
“Inventory”: []
,
“NesuPowers”: []
,
“Sex”: 1,
“SkinTone”: 0,
“HairShade”: 3,
“HairStyle”: 0
}
}

(shoot, i accidentally made the max variables less than the actual values . . . human error hahhh)
however, i am not sure (even with the docs/google) how to access a dictionary inside a dictionary—let alone arrays inside these matrixes. (at this point i wanna say its a matrix)

Even if i can do this though—is it practical? Should i or anyone else even do it this way?

-thank you in advance, i hope this topic can help others besides me as well :slight_smile:

I wouldn’t do it that way.

The functionality you want can be found in Resources.

4 Likes

Hmm… OK, reading the docs; ill try it out—
what are the pros and cons of this method versus the other method?

Resources are classes. Set up the variables however you like, access them as class members. You get a portable structure that you can pass around your code as method arguments or signal data.

For instance, I have a resource that I pass around when characters get hit:

class_name DamageTaken
extends Resource

@export var done_by: String = "unknown"
@export var health: float = 0.0
@export var knockback := Vector2.ZERO

Easy to define, easy to extend. Pretty close to what you did with the node.

Looking at your party variable I see each one follows a similar pattern. I also see a problem if you decide a character needs something new. There’s always the chance you will miss something. There is no type checking, no opportunity to add methods to make things like inventory and power management easier.

You mentioned a database script, and that is what Party is. Nothing wrong with that. I could totally see that as a data file - but I would not use it in it’s current form. I would read it into resources.

My personal approach would be to define a Character resource, create instances of that resource for each party member, then add those instances to a Party resource instance which handles all management of the group.

Resources are one of my favorite things about Godot. As a fan of DRY programming and typed method arguments, resources make my life easier. I suspect they will do the same for you.

Your original approach was the correct one. Don’t abandon it because you did it wrong once. Try again with resources.

5 Likes

I like your answer. I do the same thing but use Refcounted instead of Ressource. Do you see any disadvantages?

1 Like

I agree with @that_duck that you should use Resources.

But to answer your original question, yes you can use a Dictionary of Dictionaries. You access them the same way you would a Dictionary item.

var cleric: Dictionary[String, int] = {
	"INT": 10,
	"WIS": 16,
}

var wizard: Dictionary[String, int] = {
	"INT": 16,
	"WIS": 10,
}

var classes: Dictionary[String, Dictionary] = {
	"cleric": cleric,
	"wizard": wizard,
}

var wisdom: int = classes["cleric"]["WIS"]

In the above example, we have a cleric and wizard with stats. Both Dictionaries are stored inside the classes Dictionary. We want the Wisdom stat for a Cleric, so we access the "cleric" key of the classes Dictionary, which returns the cleric Dictionary. We they access the WIS key of the cleric Dictionary that’s returned, and assign that to the wisdom variable which is typed as an int.

2 Likes
  1. You can edit Resource objects in the editor. Just double-click on them and they open in the Inspector. This is super helpful, especially if you want to store things like PackedScenes that are not just text values.
  2. Resource objects can be saved and loaded from disk with ResourceSaver and ResourceLoader, which means it is trivial to change values in a Resource and save them to disk during runtime.
  3. You can assign icons to Resource objects, making them very easy to differentiate in the FileSystem window. They will also show this icon in the Inspector if the Resource is used as an @export variable.
  4. It’s a lot easier to edit a Color in the Inspector (see above) because you can use the color picker and eyedrop selector.
  5. You can mark a Resource as Local to Scene, allowing you to modify a single instance of a Resource without affecting all the other RefCounted versions.
  6. Resources have a UID. This means less issues with moving them around in your FileSystem if you want to reorganize things. If they are assigned through an @export variable for instance, you don’t have to re-link them.
  7. You cannot assign a RefCounted to an @export variable.
  8. Resources can be built-in, which means they can be stored on disk as part of they Node they belong to and do not have to exist anywhere else on disk.
  9. You can use the duplicate() and deep_duplicate() methods on Resources.
3 Likes

You could even do this to make it cleaner:

var wisdom: int = classes.cleric.WIS

Nonetheless, I agree with everyone above - Resource is the way to go.

6 Likes

I did not know you could do that with Dictionary keys in GDScript. That’s crazy.

1 Like

Pros of using resources are that they can be statically typed, they provide autocomplete when you try to set properties of resources, and when you use @export variables, you can edit your resources in the inspector!

A con is that resources use a little more memory than dictionaries since they’re full-blown objects, and not just data containers. But unless you’re planning on having tens of thousands of these resources loaded in simultaneously, that won’t be a problem.

I, as others have done too, really recommend learning how to use Resources. They’re super powerful.

1 Like

Someone posted a breakdown of the actual memory sizes somewhere. IIRC, a Dictionary is about half the size of a Resource in memory. Measured in bytes, it’s about ~350 bytes for an empty Dictionary vs ~800 bytes for a Resource.

An empty String is 32 bytes in GDScript, plus 1-4 bytes per character; and an int uses 8 bytes. So once you have more than ~9 keys in a Dictionary the data it contains is larger than the container’s size in memory. Likewise, for a Resource, containing a Dictionary, the data stored is going to exceed the size of the container once you have ~12 keys in the Dictionary.

So size in memory of either should not be a consideration. Especially using Godot, people’s bottlenecks with memory are always graphics related before they are data structure related.

1 Like

I know about this topic, but it doesn’t have a calculation for the Dictionary, not sure if I’ve ever seen one.

1 Like

Looks like my memory was off anyway, a Resource only takes up ~535 bytes, give or take 35 bytes.

Not only powerful, they are fundamental to the engine. You can’t really make anything substantial without custom resources. Well, you could but you’d needlessly suffer a lot and waste a lot of time.

1 Like