First, I get a new empty script and call it "global", then tell Godot this is a singleton…
Be careful with singletons. Some people go so far as to call them an antipattern—I'm not that dogmatic about anything in programming but they are worth keeping an eye on as your program grows. Anything that's both stateful and globally-accessible can breed very devious bugs above a certain scale; it's easy for something to trigger a change in state in the singleton that conflicts with some assumption made in a distant part of the codebase, and because you can't easily tell what depends on the singleton (you can reach it from anywhere) it can take a long and painful debugging process to track down. Also, if you need to change the interface to the singleton, updating all the interactions with it strewn all over the codebase can be less than fun (and be an especially fertile breeding ground for those devious bugs). This is less of a risk when the program is small enough that you can hold the whole thing in your head at once or what have you; once it gets too large for that you may want to take the singleton out of the global scope and start passing it around to the places that need it explicitly at the very least, or even breaking it up into smaller pieces that can be coralled into narrower parts of the codebase.
Note that this applies most strongly to something with state. It's less of a problem for constants to be global because they never change. Of course, a lot of things that are declared as constants do change over the course of development, and something like that should still be viewed with more suspicion than something that will never change ever (e.g. the value of pi). The things you've mentioned storing in singletons sound ilke they might be more of that nature, like a static description of all the monster's names and stats or w/e. Anything like this is still worth coding around more defensively than a "true" value-of-pi-type constant, like by wrapping the part of the code that directly accesses the data in a discrete layer that other parts of the program can make requests through. (By a "discrete layer" I mean one or more classes or functions or w/e floats your boat that each represent some part of your data and together act as the complete interface to it, so that nothing else in your program has to touch the data directly; that way you can change the structure of the data freely and all you'll need to update is the "data access layer.")
Also, as you mentioned re XML, this kind of stuff is a good candidate for storing outside the codebase, both to keep code and data separate and tidy and to make the data easier to change apart from the code. If you do go this route, you might consider a serialization language like
YAML which is more designed around human-friendliness than XML if you plan on editing the file(s) by hand—it's subjective obviously but if you compare YAML and XML side-by-side I think you'll see what I mean (there's also JSON and INI and such…YAML is my fav for hand-editing/reading though). Also, if you find yourself needing to do a lot of complicated stuff with the data (sorting it, filtering it, w/e), or you just accumulate a large enough quantity of it that's it's unwieldy to manage with a text editor, you also might consider storing it in a
SQLite database over any serialization format.
Look, I know what you're thinking, "delphonso, why are you using a dictionary when all you're doing is putting singular info in. You should clearly just be using an array. All the stuff about modding is also totally false and could be done with arrays." I get it. I know you're all thinking that all the time, and trust me - so was I.
Until I shoved an array in that bad boy.
These arrays are more similar to Pokemon attacks. They contain the name, then the damage, then a status indicator, and finally accuracy.
You might prefer using Godot's
classes over a plain array to represent a small set of structured data. Right now, there's no way to tell what the data in those arrays is in the part of your code shown in the screenshot—after all, you had to give a description of them in English for the reader to be able to tell. Same thing with the status indicators—I think it's a bit difficult to tell in this part of the code that those are indices into an array, or to figure out what those indices might correspond to. Since statuses here are basically constants that represent a certain piece of state, you might enjoy representing them via an
enum. These things could be done by e.g.:
# attack.gd
extends Node
class_name Attack
enum Status { NONE, POISONED, BURNED, BLINDED, DISORIENTED, PARALYZED }
export(String) var attack_name
export(int) var damage
export(Status) var effect
export(float) var accuracy = 100.0
Representing your ideas about the code directly in the code like this has many advantages. For instance, now everything that stores an
Attack will expose its properties nicely in the editor. Also, everywhere you have to interact with either of these types in the code will become easier to understand at a glance. Another advantage is that it gives an obvious place to gather all the behavior related to them. For example, say you decided that any attack has a 10% chance of dealing a critical hit, and that crits do double damage. You could modify
Attack as follows:
# random_util.gd
extends Object
class_name RandomUtil
static func pct_chance():
return rand_range(0.0, 1.0)
# attack.gd
class_name Attack
const RandomUtil = preload("res://random_util.gd")
# …
static func crit_dice_roll_hit():
return RandomUtil.pct_chance() < 0.1
func hit_damage():
if crit_dice_roll_hit():
return damage * 2
else:
return damage
If you represent attacks as plain arrays, this kind of logic is liable to end up strewn semi-randomly around your program, which will be a challenge to keep track of as time goes on—even the logic that reveals what the different array members are. Gathering it all into a class like this allows you to design a nice interface that both the editor and the rest of the codebase can handle pleasantly, and it gives you an obvious place to look when you want to know how something attack-related works.
The same kind of thinking applies to dictionaries, too—they're good to store "plain data" with no associated behavior, but once you start writing functions that take a certain kind of dictionary as an argument, I think it's worth asking yourself if you could make your life easier by turning that kind of dictionary into a class. As a general rule, the more explicitly you can represent your abstract ideas directly in the structure of your code, the happier you'll be.
I could also do the thing more explicitly where variables_like_this are one type, and variablesLikeThis are another. Not to mention variableslikethis...
Be really careful with stuff like this. Since GDScript is dynamic, you'll couple your variables to certain types without really needing to (and you'll have to change the name everywhere if you want to change the variable's type). Also, unless you write down the details of your variable naming style, you may not remember it later if you walk away from your code for a while. You might consider just sticking to the
GDScript style guide at least in large part, to maybe make things easier on yourself a year in the future if nothing else. If you need to distinguish a certain type of data that GDScript doesn't have built-in support for, you can get deeper support from the language machinery by definining a new type for it, like a class or an enum. (For a detailed discussion of the idea of embedding type information in variable names, see the Wikipedia article on
Hungarian notation.)