DataAsset
August 15, 2022

In GDScript we can export properties. Or, in other words, expose them. With this we can edit their values through specialized editors within the Inspector panel.

Depending on the needs we can create a script derived (extends) from Resource meant to hold data, making those editable by exposing the relevant properties. In fact I have done something way more elaborate to hold databases, including some relational data (that is, tables that can reference other tables). This can be found in the Database Addon. Yet, sometimes all we want is an asset holding some data. No need for database tables or the likes.

As an example of this "simplified data", in a project I wanted the possibility to change the "skin" of the shown objects, which I called "theme" in the game itself. In order to "describe" each theme I created a resource script containing the required properties, like theme name, list of textures and so on. Then, for each theme, I had to create an instance of this resource.

When attempting to edit properties like that, there is a big limitation: We can't export custom resource types. Besides that, array editing is rather clunky. To solve that an editor plugin is required. And for this I bring the DataAsset plugin. Bellow an array of custom scripted resources is being rearranged:

Array Example
How to Use
How it Works

To start things it's first necessary to have a script derived from DatAsset. This base class is provided with the addon. In this tutorial we will work on a sample_dasset.gd. One important aspect that must be taken in consideration is the fact that the script must be marked as a tool one otherwise the plugin will fail to obtain certain required data:

sample_dasset.gd
tool
extends DataAsset
class_name SampleDataAsset

Lets add a few basic exported properties just so we get something to work with in the editor:

sample_dasset.gd
export var some_string: String = ""
export var some_float: float = 1.0
export(int, FLAGS, "physical", "poison", "fire", "cold", "lightning") var some_flags: int = 0
export var some_resource: Resource = null
export var some_texture: Texture = null
export var some_array: Array = []

Next we need a file resource, of the SampleDataAsset type. To do this, right click the appropriate directory within the FileSystem panel then New Resource.... In the window that pops up, select SampleDataAsset:

New Sample Data Asset

As soon as the new asset is created it should already open the new editor:

Asset Editor 1

As you can see, this asset editor is docked at the bottom part of the window, sharing space with the Output. If you scroll and/or expand the (DataAsset) editor panel you will probably notice a few things related to this specific script:

  1. What if we wanted to limit the values of the some_float property? The usual export way of working with ranges in numerical types will not work with this plugin.
  2. Clicking the New instance of... within the some_resource property will popup a huge list of possible resources that can be added here.
  3. An error message is displayed within the some_array property, telling that we must implement the get_property_info() function.

The base DataAsset class contains a get_property_info() function that will be called by the plugin when populating the window with the property editors. In our derived data asset we implement this function in order to provide the plugin with the information that it needs to properly fill the editor.

The mentioned function must return a Dictionary. Each key in it should be the name of the corresponding property. The value associated with that key must be another Dictionary, containing the relevant fields. I will exemplify the 3 aspects that I have highlighted. After that I will provide tables with the complete list of options that can be used, by property type.

So, lets begin with the some_float. First lets define that we don't want values to be negative but we can set to any other positive value. For that we tell the plugin to use the range_min option and set it to be 0.0:

sample_dasset.gd
func get_property_info() -> Dictionary:
    return {
        "some_float": {
            "range_min": 0.0,
        },
    }

Once the script is updated, reload the asset (by double clicking its file within the FileSystem panel). Now attempting to assign a value smaller than 0 will not work. At this point the value editor still shows the "spin buttons" to increase/decrease the value. If we also set a range_max then those buttons will be removed and a slider will be shown, because then we obtain a well defined range. So, lets do it and set 1.0 to be the maximum value for this property:

sample_dasset.gd
func get_property_info() -> Dictionary:
    return {
        "some_float": {
            "range_min": 0.0,
            "range_max": 1.0,
        },
    }

And so, this specific property editor should now display a slider rather than the spin buttons:

Float Slider

Lets now move to the some_resource property. In order to filter out the list of possible New instance of... options we have to specify the base type of the desired resource. This is done by providing a type option within the property's dictionary. In it we must provide the path to the script that implements the resource. So, lets create a simple sample resource type to be used. Note that a class_name must be provided otherwise the plugin will fail to gather the required information. That said, a sample_resource:

sample_resource.gd
extends Resource
class_name SampleScriptedResource

export var some_color: Color = Color(1, 1, 1, 1)
export var some_vec3: Vector3 = Vector3(0, 1, 0)
export(String, FILE, "*.png, *.jpg") var some_image_file: String = ""

Now, back to the sample_dasset.gd, we specify that we want the some_resource property to be of the type defined in the sample_resource.gd file:

sample_dasset.gd
func get_property_info() -> Dictionary:
    return {
        "some_float": {
            "range_min": 0.0,
            "range_max": 1.0,
        },
        
        "some_resource": {
            "type": "res://shared/scripts/sample_resource.gd",
        },
    }

Once the script is saved and the resource reloaded, the New instance of... popup menu should be filtered to show onlySampleScriptedResource and any other that extends it:

New Scripted Resource Instance

Once the new instance is created, it should look like this:

Scripted Resource Editor

Finally we need to specify the type of entries that we would like to be added into the some_array property. Again we use the type option. But this time we can specify:

  1. A core type by assigning to type most of the TYPE_* constants.
  2. A core resource type by simply assigning its name (as a String).
  3. A scripted resource type by specifying the path to the script defining that resource.

To continue with the example we will simply specify the same sample_resource.gd mostly to see how things will look:

sample_dasset.gd
func get_property_info() -> Dictionary:
    return {
        "some_float": {
            "range_min": 0.0,
            "range_max": 1.0,
        },
        
        "some_resource": {
            "type": "res://shared/scripts/sample_resource.gd",
        },
        
        "some_array": {
            "type": "res://shared/scripts/sample_resource.gd",
        },
    }

Reloading the resource now should change to the proper array editor. There will be a big "Append" button. Clicking it will popup a list of resource types that are derived from the specified type, including the base one. Note that you can even append null into the array when the desired type is a Resource (which is the case for the example):

Append to Array

After appending a few instances of the SampleScriptedResource and editing the some_color property of each instance, the array editor looks something like the animation bellow. In it I'm also showcasing the fact that we can re-arrange array elements by simply dragging them:

Array Example

OK, that's it for the example! The tables bellow lists all options that can be used in the return value of the get_property_info() function. Each table is for a given property type. Note that the default value will be used in case the option is not provided. And if there is no "default" listed then that "option" is actually required.

  • Bool
NameDefaultDescription
label"On"A string determining the label that will be set within the checkbox.
  • Int
NameDefaultDescription
range_min0Determines the minimum value that can be assigned to the property.
range_max100Determines the maximum value that can be assigned to the property.
step1The "delta" applied to the value when clicking the spin buttons or moving the slider.
  • Float
NameDefaultDescription
range_min0.0Determines the minimum value that can be assigned to the property.
range_max1.0Determines the maximum value that can be assigned to the property.
step0.1The "delta" applied to the value when clicking the spin buttons or moving the slider.
  • Resource
NameDefaultDescription
type-The full path to a script defining a resource type.
allow_basetrueIf this is false then the popup menu with instance options will not display the specified base class, only derived ones.
  • Array
NameDefaultDescription
type-Either an integer as a subset of TYPE_*, a string with the name of a core resource or the full path to the script defining a custom resource type.

Before wrapping up this "tutorial" there are some minor things that I want to mention:

  • When you have a string meant to be a path to either a directory or a file, you can drag from the FileSystem panel into the editor box of that property.
  • It's possible to drag and drop images (from the FileSystem panel) into Texture properties.
  • Because the implementation of the DataAsset is indeed a script, you can pretty much add any other desired code into it.