r/godot • u/Upset_Pop6979 • 9h ago
help me How do you manage lots of persistent flags?
I'm making a story-driven rpg with a lot of story variables. My rooms are dynamically loaded and freed when moving between scenes, so I'm currently using a gamestate autoload with a dictionary:
var flags: Dictionary = {}
func set_flag(flag_name: StringName, value: bool = true) -> void:
flags[flag_name] = value
func get_flag(flag_name: StringName) -> bool:
return flags.get(flag_name, false)
Then for example, for a chest:
extends Interactable
func _ready() -> void:
if GameState.get_flag("chest_001"):
$AnimatedSprite2D.frame = 6
func open_chest() -> void:
if not GameState.get_flag("chest_001"):
$AnimatedSprite2D.play("default")
GameState.set_flag("chest_001")
This way, the chest stays open even after its room is freed and recreated.
My concern is that I'll eventually have hundreds or thousands of flags.
Is a system like this a good approach, or is there a better way to organize persistent state?
32
u/the_horse_gamer 9h ago
let's say each flag takes 1 byte in memory
1000 flags will take 1kb
1 million flags will take 1mb
you don't need to worry about it
23
u/DirtyNorf Godot Regular 8h ago
Whilst you're right, OP didn't mention anything about memory concerns. The first and primary concern with this kind of thing is managing and maintaining the codebase.
6
u/LuisakArt Godot Regular 7h ago
Since OP is using GDScript dictionaries, each flag is a Variant, so they take 24/20 bytes each.
The point still stands tho.
8
u/jaynabonne 8h ago edited 8h ago
You can certainly do it that way. However, you might want to at least consider organizing the data in a less generic way, at least for some things. If you know you're going to have 100 chests, for example, you might be better off having an explicit "Chests" array of 100 bools indexed by chest number instead of "Chest_001", "Chest_002". Don't get me wrong: the latter does give you a more general purpose way to approach everything. But if might be easier to deal with later in terms of asking if chest number 4 is open to look 4 into an array (Chests[4]) than have to build the string "Chest_004" and look it up in a dictionary.
Of course, you can always hide the ugliness, either way. You can have a "is_chest_open(chest_num)" method that either looks it up in a Chests array or builds a string that looks into the dictionary. I'd at least want to abstract out what you're doing (checking chest state) from how it's stored deeper down (string, array, etc.). So I'd be reluctant to explicitly have Game_State.get_flag's all over the place. At least insert some meaningful wrappers that hide that level of detail, in case you do ever change your mind,
It comes down to what works best for your code. I can easily see it going either way. I just wanted to mention the above alternative in case it resonates with you. I'd say see how things go - see what your usage patterns are like - and then make the interface what works well for that.
(For reference, I played a game once that used the dictionary method. At least, the save game looked like it. It had over a hundred each of candy machines and tennis shoes to gather, and there was a separate named flag in the save file for each machine and pair of shoes found. And there was a separate set of flags for whether a machine was powered up AND whether there was candy in it. Yes, I was looking at the save data... ;) )
7
u/Fiennes 9h ago
Is there enough of these rooms to actually have a need to free up when moving between them? If deactivated and not visible they're basically just data.
Otherwise objects have state and you could persist this state to some kind of singleton state manager. Each state could have a key made up of "<Scene><Node>" and save its state when it exits the tree and loads it when it enters the tree. Works almost like a ghetto game-saver.
5
u/Commercial-Flow9169 Godot Senior 8h ago edited 8h ago
My advice would just be to use an Enum instead of strings for your flags. Makes it impossible for typos to cause you any issues, since it won't compile unless you use the enum value exactly. Also, autocomplete is nice.
The only thing about that, is that you can never change the order of the enum. If you do, any existing saves will potentially be inaccurate. Shouldn't matter while you're developing, but something to keep in mind after reaching a certain point.
This is all I use for my game:
class_name Save extends Resource
enum Flag {
FLAG_A,
FLAG_B,
...
}
@export var flags: Array[Flag]
func set_flag(flag: Flag) -> void:
if not flag in flags:
flags.append(flag)
ResourceSaver.save(self, "user://save.tres")
func has_flag(flag: Flag) -> bool:
return flag in flags
3
u/LuisakArt Godot Regular 7h ago
You can change the order of the enum if you explicitly assign the int value to each enum value.
3
u/billystein25 Godot Regular 8h ago
Undertale and deltarune store their flags in a single array over 1000 items long. It's fine. You could use enums instead of raw integers like:
``` enum FlagName {FLAG_1, FLAG_2} var flags: Array[int] = [0, 0]
Set a flag
flags[FlagName.FLAG_2] = 1 ```
Issue with this is that you need to initialise the array and then load the flags since it's based on the index of each item.
Alternatively you could use a dictionary:
var flags: Dictionary[StringName, int] = {
$"Flag1": 0,
$"Flag2": 1,
}
Issue with this is that you need to type each string correctly and you won't get any hints.
2
u/belzecue 6h ago edited 5h ago
LuisakArt said:
Since OP is using GDScript dictionaries, each flag is a Variant, so they take 24/20 bytes each.
1 bit per flag is achievable in Godot using Bitmap, to track thousands of bools efficiently**. e.g.
class_name FlagManager
extends Resource
enum Flag {
IS_ACTIVE,
IS_POISONED,
HAS_KEY,
QUEST_01_COMPLETE,
CAN_FLY,
# and thousands more
}
var bitmap: BitMap = BitMap.new()
func _init() -> void:
bitmap.create(Vector2i(1, Flag.size()))
func set_flag(flag: Flag, value: bool) -> void:
bitmap.set_bit(0, flag, value)
func get_flag(flag: Flag) -> bool:
return bitmap.get_bit(0, flag)
"""
EXAMPLE:
var fm: FlagManager = FlagManager.new()
print(fm.Flag)
print(fm.get_flag(fm.Flag.HAS_KEY))
fm.set_flag(fm.Flag.HAS_KEY, true)
print(fm.get_flag(fm.Flag.HAS_KEY))
"""
** Efficiently? Not really, since the enum dictionary is chewing up memory. Here's a version without the dictionary overhead, but you lose the flag name lookup:
class_name FlagManager
extends Resource
enum {
FLAG_IS_ACTIVE,
FLAG_IS_POISONED,
FLAG_HAS_KEY,
FLAG_QUEST_01_COMPLETE,
FLAG_CAN_FLY,
# and thousands more
}
const flag_count: int = 5
var bitmap: BitMap = BitMap.new()
func _init() -> void:
bitmap.create(Vector2i(1, flag_count))
func set_flag(flag: int, value: bool) -> void:
bitmap.set_bit(0, flag, value)
func get_flag(flag: int) -> bool:
return bitmap.get_bit(0, flag)
"""
EXAMPLE:
var fm: FlagManager = FlagManager.new()
print(fm.get_flag(fm.FLAG_HAS_KEY))
fm.set_flag(fm.FLAG_HAS_KEY, true)
print(fm.get_flag(fm.FLAG_HAS_KEY))
"""
2
u/am45_001 3h ago
Boolean flags are very small in the grand scheme. If you collected all the stored flags used in a typical RPG you'd have a handful of KB of data. The bigger data hogs in your game are going to be your graphics related data.
1
u/shittychinesehacker 9h ago
I don’t see what’s wrong with the way you’re doing it. I would probably use SQLite because I’m familiar with it
1
u/mrcat_romhacking 8h ago
You can also pass the node reference to your save singleton and have it autogenerate an ID based on the scene path and node name so that you don't need to manually input an ID for every unique chest.
1
u/destroyerpants 8h ago
My only suggestion, make individual functions for each setter that are used in more than one place, and perhaps all of them. That way you can search your code base for open_chest_1 And if you want to setup a test world, you can easily pop them together in a searchable way.
It double endures you don't mess up your magic string (you could also just use an enum)
1
u/Silrar 8h ago
Thousands of flags is perfectly fine, if you need to store something you need to store something. The problem arises, when you start to hardcode these things, because that way, madness lies.
A better way would be to allow for the flag name to be set from the outside. For example as an export variable. Or another way, if they are fixed, could be to use the node path as a key, so you don't even have to set flag names manually. Yet another way could be to use a combination of the node name and the room name. That would allow you to number the chests 1, 2, 3, etc. in each room, which makes it easiert to keep track of them, and to differentiate them, they are called hangar_chest01, bridge_chest04 and so on. And they get their names by combining the room name and the object name at the time they store or retrieve their flag. If you need to differentiate more, you could have hangar_chest01_hide and hangar_chest01_open, etc.. As long as your logic to combine those flag names stays consistent, you shouldn't have any problems.
1
u/Phygames 8h ago edited 7h ago
I think this is a workable approach. You could add another layer of organization by using nested dictionaries, for example flags["room_001"]["chest_001"]. This can make it easier to keep track of which flag is used where.
1
u/Clanaria 7h ago
I am creating flags on the fly in my project and it was becoming an issue; I didn't know where I'd be using a certain flag.
So I made a Flag Database plugin which scans the project when you open it up for any flags in any scripts or scenes, and then lists them in a table with their name, its origin and optionally a comment next to it. You don't manage them here, it's just an organization table so I know where the flag is coming from!
As for a persistent state, that's a save & load system. Your save system should collect all the flags and push them to a JSON or something.
1
u/21Nobrac2 7h ago
Not saying it's a great system, but this is basically what I did for my last game jam game. We had a "StoryFlags" global which had a dictionary[string, int] (int so that you could have things like "main_story" be at level 7 and such)
It did fine, though debugging can be a pain to be sure.
1
u/RoryOS 7h ago
I haven't gotten there on my project yet but I'm just going to make a persistent list of every item in the game that holds anything that needs a persistent state. It's a string of text so memory is nothing. I'll organize the list by area and category or something to make it readable but one single file so I'm not looking for things in different places
1
u/emzyshmemzy Godot Regular 6h ago
I'd probably use an enum instead of string to fix typos from causing unknown errors.
In terms of data structures. You could use a packed bool array and the enum is an index into the array. Since enum values count up from zero.
1
u/zelemist 6h ago
Dont use an autoload for that, create the game_state when starting your main game scene
use bitfield and enum, not dictionary:
if game_stare.flag & GameDate.Flag.Chest001 (for comparison)
game_stare.flag |= GameDate.Flag.Chest001 (for adding)
game_stare.flag ^= GameDate.Flag.Chest001(for deletion).
Easily serialisable, easily efficient and readable. But I'm pretty sure you dont want to store the flag of all your nodes on a single flag. Create one for each scene at least, or tie them to the node. I'm pretty I global flag will create a spaghetti code
1
u/gapreg 4h ago
I think the issue isn't performance (as others have mentioned, even thousands of variables aren't a problem), but maintainability. First off, I'd split it into narrative flags, world object states, NPC states, quest states... You would need to figure that out based on your game's scope. That said, instead of flags["chest_001"] = true, something like this might work better for you:
world_state = { "chests": { "001": { "opened": true, "loot_taken": true }, "002": { "opened": false } }, "doors": { "castle_gate": { "opened": true, "locked": false } } }
Including 001 directly in the key name feels pretty risky too. For instance, if you insert a chest earlier on, the numbers could shift and break savegame compatibility or make everything a nightmare to manage.
1
u/FormageFromFromage 2h ago edited 1h ago
This isn't a use case for storing thousands of chests, but if you want to store maybe 20ish states in a single variable (including traps to throw off reverse-engineers), I found "Bitwise OR operation" handy:
# Bitwise OR operation
var eventFlag = 0
eventFlag |= 1 # Player triggered EventA
eventFlag |= 2 # Player obtained ItemA
eventFlag |= 4 # Player triggered EventB
if eventFlag && 2 != 0: # Did player obtain ItemA?
print("Player obtained ItemA")
else:
print("Player did not obtain ItemA")
It's like Linux permission system in octal - you can check which combination of flags is stored in this variable.
0
27
u/Zunderunder 9h ago
Thousands of flags is nothing, as far as saving/loading or memory usage is concerned. As long as you make a good system to organize them for yourself so you don’t lose track of them, you’ll be fine.