Understanding Resources and Storing Stats in an RPG

Godot Version

4.7.2

Question

I’m trying to put together a little pokemon clone as a learning project, but I’m having some trouble figuring out how to store stats. I have a resource set up that contains all the stats they’ll need, shown below:

I have the monster’s Base_Stats, as well as a random modifier (Gene_Stat) which would be randomized for every individual monster to give them some variance. The stat that would be shown to the player and used in battles is Current_Stat, which is derived from a formula shown at the bottom. For testing, I also have the monster’s Level as a derived stat which is their XP divided by 100.

extends Resource

class_name Creatures

@export var Creature_Name = "PLACEHOLDER"
@export var Model = "placeholder"
@export var Tex = "placeholder"
@export var Type = TYPE.Normal
@export var Base_Atk = 100
#IVs
@export var Gene_Atk = 0

@export var Xp: int = 1


enum TYPE {Normal, Fire, Water, Grass, Test, Etc}

#So the goal here is that Level is derived from Xp 
#and the current_stats are derived from the other ones,
#so you don't set them manually ever
#assuming that this is how resources work
@warning_ignore("integer_division")
var Level = Xp / 100

@warning_ignore("integer_division")
var Current_Atk = (Level / 50) * Base_Atk * Gene_Atk + 5
#Add 5 so it's not super low at level 1

To try and make sure that these derived stats (Level and Current_Stat) were actually working, I created a button, customized the Base_Stat, Gene_Stat, and XP in the inspector, and tried to have the button print the monster’s derived stats and name.

extends Button
@export var stats: Creatures



func _ready():
	var button = $"."
	button.text = "Click me"
	button.pressed.connect(_button_pressed)
	add_child(button)

func _button_pressed():
    print(stats.Level)
	print(stats.Current_Atk)
	print(stats.Creature_Name)

The issue is that it always prints

0
5
fella

The name is working properly, but it seems that it’s not deriving the level from the XP value and formula I set in the inspector. Currently, no value for any of the stats causes the numbers it prints to change. Have I set up the equation wrong, or is this an incorrect way to use resources? Should I be exporting the derived stats too, even though they’re not meant to be changed manually?

You misunderstood how these things work I think.

When you write something like this:
var Level = Xp / 100
This is a one time calculation. When your game starts, it will be calculated ONCE, then never again. This is not a function that you call every time you set it’s value, you set this value a single time when your game starts, then never again.

If you want to do something like this, you must write a function that updates this value, something like:

func setLevel(Xp):
    Level = Xp / 100

You still need the var Level as a variable, but that’s now how you’re supposed to do calculations during runtime.

Ahhh, I see now. This seems to have gotten it working.

Just to make sure I’m understanding it fully, I should have a function like this, in the resource, for every derived stat, and call those functions whenever I need to use/display the stat to make sure it’s got the right value?

That is for you to decide. Depends on your game.

Normally Resources CAN execute code, but it’s better to do calculations outside of them, and just use Resources for data storage / serialization. It’s also redundant to keep data inside a Resource that can change during runtime.

I recommend keeping values that won’t necessarily change too much in your Resource, and put values that you calculate based on the values you keep inside your Resource to be in your player script or somewhere that won’t get serialized / saved.

The usual cause: the derived value is calculated once (in _init, or as a default like var attack = strength * 2) and stored, so it never recalculates when strength changes later. In the Inspector, exported values are also assigned after _init runs, so the calculation sees the defaults.

The easiest fix is to not store it at all and compute it with a getter:

class_name Stats extends Resource
@export var strength: int = 5
var attack: int:
    get: return strength * 2

Now stats.attack is always current.

If other code needs to react to changes (like a UI label), add a setter that emits changed:

@export var strength: int = 5:
    set(v):
        strength = v
        emit_changed()

and connect to stats.changed to refresh the UI.

Also watch out for shared instances: if several enemies use the same .tres, they all share one Stats object. Use stats = stats.duplicate() in _ready() or tick “Local to Scene” on the resource so each one gets its own copy.