How to edit variables inside Inner Classes in the Inspector

Godot Version

Godot v4.3

Question

I am trying to learn OOP in GDScript and came across this problem:

If I create the variables (price and currency) in an inner class (Cost) within my class (Cookie) for the sake of organization, they do not pop up in the Inspector despite getting no error message from using @export, meaning I cannot edit the variables for each specific node. What am I to do?

While your class Cost exports variables, and you have an instance of the class Cost, you don’t @export that instance itself.

Then what do I @export? Do you have a solution to the problem?

@export var cost: Cost


error message :confused:

Then I don’t think it’s possible with a class defined within the script, you will have to use another script extending type Resource.

Or use @export_group to bundle the two cost related values.

Depending on your ‘top level’ class name of ‘Cookie’, your Cost logic should be done differently, I don’t think it should be a class. ‘Cost’ being an “object” based on a class doesnt seem right, ‘Cost’ a simple math calculation.

BUT IF YOU REALLY WANT A CLASS FOR ‘Cost’, then I recommend putting it in a different script file. Then, you can declare a variable as a cost type: ‘var cost: Cost’

EDIT:
Multiple classes within a class file IS POSSIBLE - they are called ‘inner classes’. I dont think I would ever use them, maybe if I had multiple slightly different classes that inherit from the same parent class I can just throw them in one file.

GDScript Reference - Inner classes

Everything in there is just an example, just so I can learn how to use it in the future, so you don’t have to think about the practicality of “Cost” itself.

I’m aware about inner classes, if you read the title again, my post is exploring how they work. The point is how can I have editable variables while using Inner classes?

Now while typing this reply out, I actually figured out what seems to be a viable solution ToT. I’ll attach it in another reply for y’all to review

This is the solution I found to the problem. I create an instance of Cost and @export the variables contained in that instance. I’m then able to edit those in the Inspector.

Let me know if you see anything wrong with this solution, or if there is a better one.

I still am curious why the variables did not show up in the inspector in the first screenshot, and what exactly @export did to those variables, since it ran that code without an Error message. It could be something worth looking into (preferably by someone more skilled than me).

price and currency as floats are copied so using the @exports cost.price will result in a different number from price

Not sure your solution works. Did you verify?

At the end you may have to do the simple way by assigning an export variable to the object’s property.


@export var my_cost:int
var obj_cost=Cost.new()

class Cost:
	var cost: int
	
# Check if the key is being pressed in the _process function
func _process(delta):
	if Input.is_action_pressed("ui_up"):
		obj_cost.cost=my_cost
		print(obj_cost.cost)

If you really, really, for some reason, really want to do that then you’ll need to use getters and setters for those variables

For example:

extends Node

var cost:Cost = Cost.new()
@export var price:float:
	get():
		return cost.price
	set(value):
		cost.price = value

@export var currency:String:
	get():
		return cost.currency
	set(value):
		cost.currency = value


func _ready() -> void:
	prints(cost.price, cost.currency)


class Cost:
	var price:float = 0.0
	var currency:String = ""
1 Like

Well what exactly is so impractical about this strategy? I figured Inner Classes were useful for organizing variables without making a whole new script which seems tedious. Otherwise, what is the point of Inner Classes, and how would you use them? I’m open to learning a more efficient method, I just don’t understand how else I would go about this.

(Thank you for the fixed code, it works a lot better than what I came up with)

There is nothing impractical about your approach itself in my opinion, just that Godot doesn’t seem to fully support what you want to do. I was actually just trying the same thing, but with a few tweeks to your original code:

  1. Add “extends Resource” to the inner class
  2. Add @export to the cost variable

However, it seems this does not work as expected, since it does not give only the option to instantiate that type in the inspector, as I would expect. I thought this would behave the same as having an equivalent stand-alone script that extends Resource, which in the inspector only gives the option to instantiate that specific type.

Anybody know if there’s a way to do this or if there’s an issue for it?

Inner classes extend from RefCounted under the hood, so it’s not possible to export them to the inspector. You need to extend from Resource to be able to export and edit it in the inspector.
What you’re trying to achieve @joostonthegamer is just not possible by design. If you want to edit values in the editor, you have to create a new script that extends a Resource and then you can export that.

“Why would we need inner classes for then?” you might ask. It’s, well, for the purpose of the “inner” logic of the script. You can bundle a couple of variables together and pass these around instead of all variables separately. E.g. you might have a class Item that extends Resource, which holds basic information about an item like name, description, texture.

class_name Item
extends Resource

@export var name: String
@export var description: String
@export var texture: Texture2D

Then, in your Inventory.gd script you can have an inner class InventoryItem, which has just 2 variables:

class InventoryItem:
	var item: Item
	var count: int

You can then instantiate this class and pass around this info in your inventory, UI, etc. All should be handled internally without exporting these values to the inspector. If you ever need to export this information to the editor for whatever reason - it won’t work, you need to extend from Resource.

I searched my project to see if I ever used an inner class and this was the only instance:

class SaveFileMetadata:
	var save_file_name: String
	var game_version: String
	var timestamp: String

	@warning_ignore("shadowed_variable")
	static func create_new(save_file_name: String, game_version: String, timestamp: String) -> SaveFileMetadata:
		var new_save_file_metadata: SaveFileMetadata = SaveFileMetadata.new()
		new_save_file_metadata.save_file_name = save_file_name
		new_save_file_metadata.game_version = game_version
		new_save_file_metadata.timestamp = timestamp
		return new_save_file_metadata

I use it to create a list of information about save files, like save_file_name, game_version and timestamp, so I don’t have to load a full save before showing this information in the Save/Load screen.

func get_save_files_metadata() -> Array[SaveFileMetadata]:
	var array: Array[SaveFileMetadata]
	var config: ConfigFile = ConfigFile.new()
	config.load(saves_metadata_file_full_path)
	for save_file_name: String in config.get_sections():
		var game_version: String = config.get_value(save_file_name, "game_version")
		var timestamp: String = config.get_value(save_file_name, "timestamp")
		var metadata: SaveFileMetadata = SaveFileMetadata.create_new(save_file_name, game_version, timestamp)
		array.append(metadata)
	return array