"Invalid access to property or key 'custom_property' on a base object of type 'Nil'"

Godot Version

Godot 4.6.1

Issue

I’m trying to utilize exported variables to make my project easy to modify and add stuff for in the future. If possible, I want to be able to assign all of the data in the Inspector to quickly make a new instance of an object.

However, when I change my Resource structure just a little bit, it starts to cause the error shown above, and I can’t find out why my resources stop importing properly and how to fix it.

Details

  • The data causing this error:
  1. BeastCore (Resource): Contains immutable data, made to be saved to define the very core of the instance.
  2. BeastData (Resource): Contains mutable data, that helps make each monster instance unique (like stats). Has an exported variable that holds the immutable data.
  3. Beast (Node): A custom-defined extension of Node that, when initialized (the initialization requires Monster Data as an argument), contains all the relevant data for other scripts/nodes to access. I am trying to remove this node, now that I know the data can be properly initialized within the Resource.
  4. Battle Entity (New Resource): An empty resource class. I’m trying to make BeastData and another resource extend this resource, for organization. I want my enemy’s exported enemy_data variable to only accept a Battle Entity, so that I’m limited to selecting extended Resources that are made to work with that specific node.
  5. Enemy (Node): Holds the data and assigns the variables accordingly, based on what exported variable is returned. I am giving that variable BeastData.
  • When the exact project folder is copied over from my desktop, the project works just fine.
  • Any changes that I make to the resource structure will cause Godot to never show the imported data in the Stack Frames, even though I have it correctly set within the exported variable.
  • Issues with resources not properly imported only started recently, when I was having some issues with managing a bare git repository I use to hold my project between computers. I’m not sure if this can cause something like this to happen with Godot?

  1. The Enemy Script (Original):
extends Node2D

@export var enemy_data: Resource

var active_beast: Beast
## Assigns itself the beast data, depending on if the enemy is a trainer with multiple beasts, or just a wild beast.
var enemy_beast: BeastData

func _ready() -> void:
	# Get the data either the beast as a wild beast, or the first beast in a trainer's party for a trainer battle.
	if enemy_data is Trainer:
		enemy_beast = enemy_data.party[0]
	elif enemy_data is BeastData:
		enemy_beast = enemy_data
	# If the resource type is unrecognized, do not continue loading a sprite.
	else:
		printerr("Unrecognized resource type. Can only load a Trainer for a trainer battle, or a Beast for a wild beast battle.")
		get_tree().quit(1)
	
	# Set the beast's data.
	set_active_beast(enemy_beast)

@onready var health_bar_size = $BattleUI/HealthBar.size.x

func set_active_beast(beast_data: BeastData) -> void:
	if active_beast: active_beast.queue_free()
	active_beast = Beast.new(beast_data)
	$BeastSprite.texture = active_beast.sprite
	$BattleUI/NameTag.set_text(active_beast.beast_name + " lv. " + str(active_beast.level))
  1. The Beast Script (Original):
class_name Beast extends Node

var beast_name: String

var sprite: Texture

var max_health: int

var current_health: int

var level: int

var speed: int

var learned_attacks: Array[Attacks]

var beast_data: BeastData

func _init(new_beast: BeastData) -> void:
	beast_data = new_beast

	beast_name = new_beast.nickname if new_beast.nickname else new_beast.beast_core.name

	sprite = new_beast.beast_core.sprite

	max_health = new_beast.max_health

	current_health = new_beast.current_health

	level = new_beast.level

	speed = new_beast.speed

	learned_attacks = new_beast.learned_attacks
  1. Beast Data Script (Original):
## The beast's data. Wraps around the core to provide mutable data like stats, health, and experience, to make the beast whole.
class_name BeastData extends Resource

## The very bare minimum data that defines the beast. This data stays the same between any of the same beast.
@export var beast_core: BeastCore

## The beast's max health.
@export var max_health: int = 100

## The beast's current health.
var current_health: int = max_health

## The beast's level.
@export var level: int
## The beast's speed. Determines which beast goes first.
@export var speed: int

## The 4 attacks this beast currently knows.
@export var learned_attacks: Array[Attacks]

@export var nickname: String
  1. The Enemy Script (Changed):
extends Node2D

@export var enemy_data: BattleEntity

var active_beast: BeastData

func _ready() -> void:
	## Assigns itself the beast data, depending on if the enemy is a trainer with multiple beasts, or just a wild beast.
	var enemy_beast: BeastData

	# Get the data either the beast as a wild beast, or the first beast in a trainer's party for a trainer battle.
	if enemy_data is Trainer:
		enemy_beast = enemy_data.party[0]
	elif enemy_data is BeastData:
		enemy_beast = enemy_data
	# If the resource type is unrecognized, do not continue loading a sprite.
	else:
		printerr("Unrecognized resource type. Can only load a Trainer for a trainer battle, or a Beast for a wild beast battle.")
		get_tree().quit(1)
	
	# Set the beast's data.
	set_active_beast(enemy_beast)

@onready var health_bar_size = $BattleUI/HealthBar.size.x

func set_active_beast(beast_data: BeastData) -> void:
	active_beast = beast_data
	$BeastSprite.texture = active_beast.sprite
	$BattleUI/NameTag.set_text(active_beast.name + " lv. " + str(active_beast.level))
  1. Beast Data (Changed, Replacing Beast):
## The beast's data. Wraps around the core to provide mutable data like stats, health, and experience, to make the beast whole.
class_name BeastData extends BattleEntity

## The very bare minimum data that defines the beast. This data stays the same between any of the same beast.
@export var beast_core: BeastCore

## The beast's max health.
@export var max_health: int = 100

## The beast's current health.
var current_health: int = max_health

## The beast's level.
@export var level: int
## The beast's speed. Determines which beast goes first.
@export var speed: int

## The 4 attacks this beast currently knows.
@export var learned_attacks: Array[Attacks]

@export var nickname: String
var name: String

func _init(new_beast_core: BeastCore, new_max_health: int = 100, new_current_health: int = new_max_health, new_level: int = 1, new_speed: int = 1, new_learned_attacks: Array[Attacks] = [], new_name: String = ""):
	beast_core = new_beast_core
	name = nickname if nickname else beast_core.name if beast_core.name else new_name
	max_health = new_max_health
	current_health = new_current_health
	level = new_level
	speed = new_speed
	learned_attacks = new_learned_attacks

## Sets the health for the specified beast.
func set_health(health_bar: Node, max_bar_size: float, new_health: int) -> void:
	if new_health >= max_health: current_health = max_health
	else:
		current_health = new_health
		
	health_bar.size.x = (current_health * max_bar_size)/max_health
  1. Battle Entity Script (New):
class_name BattleEntity extends Resource

If you are experienced with Godot and/or Resources, may you assist with this? I don’t know if this is an error I caused and failed to understand, or if there actually is something wrong with my Godot. I created an account just to ask this, I’ve literally never been stumped so badly by an error before.

Thank you!

Don’t use _init, especially for Node types. _init will try to run before @exports, it is the least stable state to run code in.

The only plausible extends to override _init is Object or RefCounted and still only if you have a very good reason to do so.