Resource error: "Invalid access to property or key 'speed' on a base object of type 'Nil'."

Godot Version

4.3

Question

Hello there!
I have a Finite State Machine and I’ve been trying to use a Resource to update the Player variables (for saving and loading the game) but I receive this error:
“Invalid access to property or key ‘speed’ on a base object of type ‘Nil’.”

The Player script holds all the variables and it gets referenced by each State to access them (I only copied one variable, speed, to test it):

class_name Player extends CharacterBody2D

@export var _stats: PlayerResource:  
	set(_new_stats):
		_stats = _new_stats
	get:
		return _stats

var speed = _stats.speed   # <--- this is where I receive the ERROR.

PlayerResource script:

class_name PlayerResource extends Resource

@export var speed := 160.0

Then, inside each State, for example RunningState, I access the Player variable inside the physics function:

var player: Player

func physics_update(delta: float) -> void:
	var input_direction_x := Input.get_axis("move_left", "move_right")
	player.velocity.x = player.speed * input_direction_x

I’ve already imported the PlayerResource script in the Player’s inspector var that I exported.

It seems like the engine accesses the speed too early before it gets assigned from _stats. Correct?
How do I solve by keeping this flow in the FSM?

I was checking those couple of videos but since they are not FSM there might be something I’m missing:

Thanks for any help!
Cheers

Yes, you are right, you are trying to access it too soon with the var at the top of the script. It will not access the exported var until the node is ready. You can just declare it and set it when the node is ready and the exported var is loaded.

var speed: float 

func _ready() -> void:
   speed = _stats.speed

You can only access the stats once the node is ready, so in _ready or in any other function.

PS I think you can also do it in the _init function too if you want. Was just reading this post here:

PPS No, it turns out _init is not the correct place to do it. Stick to _ready().

1 Like

Hey, thanks for the reply!

Yeah, I did try that, but it still gives me the same error, only now it’s inside the _ready function for the line:

speed = _stats.speed

I’m confused :confused:
I’ve also checked that post you mentioned, but it didn’t help for me.

Any other ideas? :')

Perhaps it is the combination of @export and the use of setters. I just checked the docs and could not find a single example of it in the @export section nor in the setters part of the docs, even though the examples are extensive.

If in the set part, you again refer to the _stats again, is this not an infinite recursion? I don’t tend to use setters like this as I just don’t like the way they look in my code, I just create separate functions for setting and getting variables directly.

Just looked at the docs and infinite recursion won’t happen I think. Excuse my ignorance here.

Still I cannot find an example in combination with @export. No other ideas, sorry. I presume you have set the resource in the editor and it is a valid resource.

Hey there, sorry for the late reply.
I also tried without set/get methods and just referencing the PlayerResource, but still the same issue :confused:

I also tried separating the set/get methods with full functions but again nothing changed.

What do you mean with this?

I presume you have set the resource in the editor and it is a valid resource.

I did add the Resource in the Inspector using export but also tried without it.
I don’t know honestly. Maybe the Resource thing doesn’t work well with FSM since a single State can be called instantly (too early)?

EDIT:
How would you do it? My objective was to create a save/load system using Resources, but if you have a better suggestion, please feel free to share. Thanks!

I cannot say about this particular example, but it seems like there’s some logical inconsistency here.

Your variable speed is dependent on the _stats. This means the only logical place to set the speed is inside the setter of _stats.

You’re saying you’re going to use this system for save and load. With your current system this would mean you would need to set the save data to stats before the node is ready, so that the speed can be updated when the node is actually ready. I don’t know if this is possible, but in any case it seems more complicated than just setting dependent variables inside the setter.

1 Like

Hi,

I am no expert at this and the way I do a save/load is probably a bit ‘manual’. I have a variety of singletons.

My file_manager which receives a path and data and deals with reading or writing to files. It converts the data (dictionary) to json and writes it. If writing, returns true or false, if reading, returns a dictionary.

I have a save_load_manager that deals with saving and loading game data, game settings and level data. I create these with dictionaries of things to save. These strings are the names of data fields in my data store.

The data store singleton stores all the data that needs to be shared around, including player stats, and level information dictionaries like enemies, collectables, bases etc. It is just a long list of variable declarations, with default values for each and current values for each. The only function in here is a reset to default function that resets all the variables back to their default value.

Now when I need to share a new value, like score say, I add score_default and score_current to the datastore. If I need to then save that value for game loads, I add ‘score’ to the game save dictionary.

For instance yesterday I added a ‘level of detail’ setting. This was added to the datastore, and then I added this to the settings save dictionary. All done. If I try to load a saved game that does not have that setting set, it gets set to the default value. When I save the settings, it is automatically added to the file. (I should say some of these variables are themselves dictionaries, and some of those are multi level, such as enemy groups where each group has a leader, a base, and a mix of followers)

So this feels ‘manual’, but it leaves everything completely flexible, very simple to implement, easy to maintain, and very easy to debug. It is also very fast and surprisingly light weight too.

Anyway, that is how I currently do it. You did ask. I am not holding this up as the best way, but it works for me.

1 Like

Thanks a lot for your detailed feedback! That’s surely a way to do it, but I wanted to try the Resource way since it’s a Godot advantage.

I managed to make it work like this.
Players script:

@export var _stats: PlayerResource: set = _set_stats, get = _get_stats
var speed = 0.0

func _process(delta: float) -> void:
	if _stats != null:
		speed = _stats.speed
	else:
		push_error("PlayerResource is not set! Make sure it's assigned in the Inspector")

func _set_stats(_new_stats):
	print("Enter Set new stats")
	_stats = _new_stats

func _get_stats():
	return _stats

PlayerResource script stays the same.
And of course I imported the .tres PlayerResource in the Player’s Inspector.

Also, if you’re interested, I finished the Saving Manager by adding this to the Main script:

@onready var _player = $PlayerRoot/Player
var _save_game = SaveGame

func save_and_load() -> void:
	if Input.is_action_just_pressed("save"):
		print("Save pressed")
		_save_game = SaveGame.new()
		_save_game.player_resource = PlayerResource.new()
		_save_game.player_resource = PlayerResource.new()
		_save_game.global_position = _player.global_position
		_save_game.save_game()
	elif Input.is_action_just_pressed("load"):
		print("Load pressed")
		_save_game = SaveGame.load_game()
		_player.global_position = _save_game.global_position
		_player._stats = _save_game.player_resource


func _process(delta):
	save_and_load()

and this in the SaveGame Resource (which also incorporates PlayerResource as a sub-resource):

const SAVE_GAME_PATH := "user://save.tres"

# The Resource incorporates whatever other sub-resource gets added here (eg: HUD, inventory, etc.)
@export var player_resource: Resource
# This allows to save the current Player's position.
@export var global_position := Vector2.ZERO


# Save the SaveGame resource at the pointed folder.
func save_game() -> void:
	ResourceSaver.save(self, SAVE_GAME_PATH)
	print("Game saved")


# Load the SaveGame resource. If there's no file at the pointed folder, it results in an error.
static func load_game() -> SaveGame:
	var res = ResourceLoader.load(SAVE_GAME_PATH)
	if res == null:
		push_error("Failed to load save game.")
		return null
	print("Game loaded")
	return res as SaveGame


# Check if there's already a saved file.
static func save_exists() -> bool:
	return ResourceLoader.exists(SAVE_GAME_PATH)

Hope this helps somebody!
Also, please let me know if there’s anything you’d do differently or a more efficient way to adjust the code. I’m all ears!

Thanks again!
Cheers

1 Like

That is very interesting. It certainly seems to have a lot of benefits.

I think when I came up with my saving and loading (which feels like an age ago now) I had some issues with resources I couldn’t resolve (probably just because I was new to godot and gdscript and gamedev all together) so I found it easier to do it manually. Looking at your solution, it definitely seems more robust and far cleaner than mine.

Although I don’t think I will attempt to change it now as my game is nearly finished and I want to get it finished (quite frankly I have had enough of this project already beyond deadlines) I will definitely be doing it this way in the next project.

Thank you for sharing that.

PS Just been reading about ResourceLoader and ResourceSaver in the docs. OMG did I re-invent everything! These are so much better than my “well it seems to work” scripts :slight_smile:

1 Like

Haha yeah, there’s been a lot of user-friendly updates over the years for Godot, I like how it’s being built.
Feel free to share your game if you have anything to show, I’ll be happy to take a look!
Cheers

1 Like