godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.06k stars 65 forks source link

Add first-class custom resource support #18

Closed ghost closed 1 year ago

ghost commented 4 years ago

Describe the project you are working on: Data-heavy RPG

Describe how this feature / enhancement will help your project: I have a lot of different structures in my game which represent various different things, such as healing items, equipment, party members, item/character stats, enemies, and so on. To make it easy for designers/modders to add and change this data, these are represented through extending Resource, which, combined with class_name, allows you to easily create .tres files containing exported variables that can be assigned to in the inspector. So to modify data, one can double-click some file in the file system, glance over to the inspector, and easily change properties this way, leveraging all the various options associated with export.

image

This works, but only up to a point. Let's say, I now want all of my party members, equipment, and enemies to have a set of stats associated with them. I can write a stats class like so:

extends Resource
class_name Stats

export var strength: float
export var defense: float
...more stats

And now I can just do the following in all the associated scripts, and get to modify that data as I would with any other resource:

export(Stats) var stats = Stats.new()

...except, unfortunately, that doesn't work, as Godot will throw an error about not recognizing Stats as a Resource, though we see that Stats, due to extending Resource, should count as one. This works with any resource the engine ships with:

export(Texture) var tex
export(AudioStream) var stream
export(Shape2D) var shape

And you even get a little menu to create a new resource:

image

But, sadly, the engine rejects Stats as being something entirely different. If there was support for this kind of resource, we should be able to see a menu something like this, after the menu to create a new Stats resource:

image

Which would alleviate all the work-arounds outlined below.

Show a mock up screenshots/video or a flow diagram explaining how your proposal will work:

export(Stats) var stats
or
export var stats: Stats

stats_mock

If this enhancement will not be used often, can it be worked around with a few lines of script?:

In short: no, just the opposite, it requires a lot more code to be written to work around this inhibition, or requires a tedious process to add a custom resource to the inspector.

The fundamental issue is the current inability for the user to simply export a resource of their own creation. We are left with the following options:

Brute Force

#unnamed and unstructured, no guarantees of anything
export(Array, float) var stats
export(Array, Array, String) var combat_messages
#this one's especially painful, where you need multiple types in one array
export(Array) var mixed_data
#...but nothing compares to this, besides the dictionary variation
export(Array, Array) var lots_of_mixed_data

#named but unstructured, and you have to name them for every dictionary added in the latter example, no autocomplete
export(Dictionary) var stats := {"strength" : 0.0, "defense" : 0.0, ...}
export(Array, Dictionary) var god_help_you_if_you_do_this

The OmniResource

#named, structured, autocompleted (if you cast)
#necessitates a process where you create the custom resource then copy it over (after scrolling through the ginormous list of resources this gives you), or save it to the system and drag and drop
export(Resource) var not_as_it_seems

image

Do Repeat Yourself

#player_character.gd
export var strength: float
export var defense: float
...

#enemy.gd
export var strength: float
export var defense: float
...

#equipment.gd
export var start_strength: float
export var start_defense: float
...
export var upgraded_strength: float
export var upgraded_defense: float
...

Forget About It!

#my_character.gd
func get_character() -> PlayerCharacter:
    var player_character = PlayerCharacter.new()
    player_character.stats = Stats.new()
    player_character.stats.strength = 7
    ...
    return player_character

#character_defs.gd
func get_characters() -> Array:
   ...

#loader.gd
func load_character(path_to_json) -> PlayerCharacter:
    ...turning json back into an object logic...

There's the opportunity to build an inspector plugin or use _get_property_list(), both of which require quite a bit of code and technical know-how, on a per-resource basis. Based on what people have told me in the discord, it's a painful process (at least with property list) which they seek to avoid in the future. We could also go into C++ land and build modules for the engine to understand our data types, but again, it's a lot of code, and beyond the skill level of many of us who haven't delved into the engine code (assuming we know C++ at all); even if it were easily understood by anyone, or you know what you're doing, it's still many magnitudes more work than export var my_res: MyRes working out of the box.

It takes a lot of code, or unsafe code, or bad programming practices, or a tiresome process to make up for functionality that Godot already provides, leaving many of us to reinvent the wheel time and time again if we want this functionality. If exporting a custom resource Just Worked, lots of time, energy and headaches would be saved.

Is there a reason why this should be core and not an add-on in the asset library?: I don't believe first-class support for custom resources would constitute as bloat. I believe it's an essential but missing feature for all games which require any kind of user-created resource type, for anyone who needs to export a bundle of named data with type safety, for anyone who wants to associate their own resource type with their own node type, for anyone who needs complex but easily modified data saved to disk; since virtually every game made with Godot makes use of Resources, every developer would benefit from this, if not by a little, by a lot.

reduz commented 4 years ago

I have the feeling this may be a bug? would be nice if someone else give this a check as I don't see why this shouldn't work.

vnen commented 4 years ago

Related: godotengine/godot#26162

willnationsdev commented 4 years ago

This gets a massive upvote from me. As someone who spends most of their time developing data-driven code and loves Godot's resource types, not being able to export user-defined ones is one of the biggest obstacles to working in Godot by far.

@reduz it doesn't work yet just because the logic in the GDScriptParser and the EditorPropertyResource were never updated to account for globally-recognizable script classes from 3.1.

I'll try to look into the PR more / test it tonight if I can find the time. Would love for this to be mergeable as soon as the 3.2 release is done.

willnationsdev commented 4 years ago

So, I spent a little time during my lunch break to fetch the branch of the PR for this change, rebase it, merge changes, and fix some of the implementation so that it is based entirely around reducing constants, not referencing an identifier, and changing it so that it correctly iterates over the languages, asks the languages to return the name of the script in the constant, and conditionally exports it if the script both is a script class and has a base type that extends Resource.

The copy in my test project is working fully correctly as far as I can tell, and it also avoids all of the other export issues brought up in this comment of the PR. The only thing I haven't implemented yet is allowing any arbitrary preloaded constant to be able to export as a resource. I'm also not 100% sure we should even support that since the editor itself needs to be able to access the typename associated with the script in order for it to know what GUI to generate when you click on an empty exported user-defined script (e.g. "Create new MyResource").

If you use a preloaded constant, you would have to use export.class_name to store "Resource" and export.hint_string to store the script's path which isn't its intended use-case and a lot of other things would need to change to accommodate that potentially being a file path. Furthermore, only the exact script will have any awareness of the type's name and the name will even be able to change over time easily, without "script classes" updating, so I suspect you could even end up with desynchronizations between what the editor thinks the name is and what the object says the name is.

As such, I think we should, at least for now, only offer support for it with script classes. In which case, my branch is largely completed. I'll comment here again when it is cleaned up/done and either @vixelz can do a hard reset to make his branch equal to mine (he did most of the work and therefore deserves most of the credit for the merge) or, if he would prefer, I can just submit my own PR to be merged. The branch in question is my gdres branch, if anyone wants to take a look at it pre-emptively.

erodozer commented 4 years ago

Is this something that's already supported by C# custom resources, or does this functionality need to be implemented there too or made note of?

vixelz commented 4 years ago

@willnationsdev I've not been able to give time to my PR in a while, and I have no need for credit for what parts survived the fixing. If your branch is looking more viable I'm happy to close my PR for my branch to make way for yours to get merged

willnationsdev commented 4 years ago

@nhydock I suspect it is NOT supported for C# custom resources because it operates off the script class system which VisualScript and CSharpScript have yet to get support for. See #22 for more details.

Reneator commented 4 years ago

Related, but not as well formulated as this one, but wants the exact same thing!

https://github.com/godotengine/godot/issues/30694

With what this proposal would offer i could much more easily edit certain things in editor, which i currently do via code or file-loading from directory.

I think this is something unity already has working but im not sure if its exactly the same functionality. I once saw this kind of custom ressource in unity in a video by Anton from H3VR where he showed in editor, how he defined the classes for his take and hold mode. So in this way you could just export an array with your custom ressources and then you could just create as many as you want and you can dig as deep into the objects as you like and create other custom ressources within those sub-variables (like with particles2D and shaders in godot currently)

samdze commented 4 years ago

Please make this happen, I consider it a key feature and would be a huge boost in productivity. Right now it is very inconvenient to use custom resources, yet there is so little more to do to make them extremely useful...

willnationsdev commented 4 years ago

@samdze The pull request I submitted implements this feature. Thus far, no one has mentioned any further problems with the associated changes. I'm guessing it will be merged sometime soon after the 3.2 release, at which point, anyone on the master branch will be able to use custom resources out-of-the-box (finally).

Still need to get #22 fully implemented though so that all languages can benefit from the custom resource support. I've finished a VisualScript implementation (but haven't merged it yet) and I have a WIP C# implementation, but I hit a roadblock and neikeq hasn't gotten back to me on it on Discord (probably because he's been busy with everything else in C# exports).

@Reneator

Yes, Unity has a ScriptableObject class that is similar to Godot's user-defined Resource scripts. The only difference being that we do not yet have support for rendering those resources in the Inspector via export commands. You can still do it, but you have to manually assign the script to the resource instance by editing its script property. Possible, but with really bad UX. The pull request I mentioned above solves the UX issue afaict though.

Reneator commented 4 years ago

@samdze Im currently using these changes in my project. I built the project according to @willnationsdev s instructions and now i have a project with custom Resource support. I can send you those instructions as well, if you want to. The approach of working with resources (at least the way i do) holds some "problems" because resources are meant mainly to be used as data-containers and its rather just tolerated if you use them in a more functional way (giving the resources their own methods). But for this, there are easy workarounds. So the main challenge lies in understanding how resources can be best utilized.

But using the CustomRecources to define GameObjects for my rpg (mainly idle game that is played via menues) helped me slim down a lot of the overhead in the project, where i usually would load and handle data. By having resources and duplicating them, i can use them as if i created a new gameobject (keep in mind, duplicate(true) is still bugged (https://github.com/godotengine/godot/issues/33079)

@willnationsdev I recently had a bug regarding the debugging context (the UI that shows the variables of the current context and the global singletons) where it died with a "parser bug" because of a custom resource. I could solve the problem (by triald and error), but i couldnt yet fully reproduce the issue in an independent project. Will post it here as soon as its possible.

willnationsdev commented 4 years ago

Huh, I'm not really familiar with the debugger's code. Hopefully someone else has an idea of what the problem is. I'm guessing it would have something to do with the PropertyInfo's class_name and/or hint_string values being the name of a script class and it just not knowing to check the ScriptServer for a possible class name when inspecting the property. That's the only thing I could think of that it might notice differently given the changes.

Reneator commented 4 years ago

@willnationsdev I think its not a too high priority yet. But i will try to create a reproduction project that isolates the problem. Should i post it here or make a new ticket and reference this issue?

willnationsdev commented 4 years ago

@Reneator if it has to do with the commits specifically in the pull request, then please post all information in the pull request.

Reneator commented 4 years ago

@willnationsdev I found a "problem" that arose with the custom resources, but i think it may be happening somewhere else in the engine entirely:

I declare a Custom Resource: MyCustomResource

and then add an export of itself:

extends Resource
class_name MyCustomResource

export (MyCustomResource) var blubb

Now the editor will complain about: Expected a constant or identifier expression and Resources that are created with that resource-script dont show any fields in the inspector.

So far, relatable behaviour

But if the class gets a bit bigger the behaviour gets weird: Now I change that export to another type, so that it compiles successfully. If i now change it back to the original "self-export" I can create Resources within itself.

image

here my expectation would be, that it either doesnt work entirely or (hopefully) actually allows for me to do it without error.

At this point restarting the project or changing other code would sometimes let me keep working with the "self-exporting" Resource and sometimes not.

I could use the ability to put a Resource of the same type inside a resource for my current project.

If you think this is unrelated I will create a different ticket for this.

Thanks in advance!

willnationsdev commented 4 years ago

I feel like this is related to the GDScript parser just not explicitly checking for the current script's class name when it is checking exported resource types. Because, if you get the script in a compiling state, then it suddenly becomes a valid class type again. But the moment you have an error of some kind, then suddenly the exports don't work anymore.

I could add an exception that would enable you to export a class name of the same file, and that would at least get the GDScript to not complain, but you'd have to be aware that exported fields would only actually have data in them when the script is compiling. In fact, any exported data you edit in the Inspector might even be lost the moment you edit it into a non-compiling state (not sure if that would happen though).

It's a tricky issue to get around. I'll try investigating that when I get a chance.

dioptryk commented 4 years ago

@somnivore , I'm in the same boat as you but I implemented this differently. My idea is that, simply, do not use custom resources for the data - use Nodes for each custom property. For example, your player character would have child nodes called: Strength, Dexterity etc., all of them extending Attribute.gd, which extends Node. This makes the entire mechanism much more dynamic than just using a resource. This approach can be used for everything, like skills, items (children of Inventory node), custom effects, etc. Try it - it works nicely for me. I'd not worry about performance unless your game is about whole armies of creatures clashing. If it's more of a dungeon crawler, with not that many NPCs, the performance impact will be next to none.

I've used custom resources for something different - some entity metadata which is supposed to be indexed without loading the entire creature with all the media, for example. So, Vampire.tscn has a MonsterData property, which is a custom resource (saved as external .tres), containing: name, where can it appear, rarity etc. When starting the game I'm loading all these .tres files to create a dictionary, which I use when needed to generate monsters and items for a level. If I stored everything in a .tscn, I'd have to load everything for indexing (which would make the game load much slower) or do some preprocessing to save this somewhere before building the release version. Custom resources deal with this problem nicely.

YuriSizov commented 4 years ago

Is it related to this issue/proposal that custom resources are not properly initialized when loaded from disk?

It seems, that ClassDB does not recognize custom resources by their class_name and therefore cannot initialize them. For a custom resource get_class() returns a simple "Resource" string, and using their actual class name in ClassDB.instance() errs. This means, that custom resources loaded from disk are treated as a generic Resource, their initialization scripts are skipped, setter functions never called. At least, I think that's the reason. Should it be fixed as well?

@willnationsdev Does your PR cover it?

willnationsdev commented 4 years ago

@pycbouh That is an unrelated non-bug (probably a documentation issue). ClassDB deals exclusively with engine C++ classes. So, you could use ClassDB.instance("GradiantTexture") or something, but you can't use a script. Script class support doesn't change that.

To get an initialized Object with an initialized ScriptInstance attached to it, you have to instantiate the content from the Script (e.g. with.new()) or create an Object first and then assign it the Script resource using set_script().

For serialization, if you create a Resource instance this way with a script attached, and then you save that Resource/ScriptInstance pair using ResourceSaver or the Inspector, then you'll get a resource file (.tres) which, when loaded, comes with all the ScriptInstance's properties setup and initialized properly.

My PR just makes it so that you'll be able to export the resources to the Inspector, so that you can view custom resources as properties on another Object, rather than having to directly view a Resource in the Inspector and manually attach a script to it in order to get custom properties to show up. It will work more out-of-the-box.

YuriSizov commented 4 years ago

For serialization, if you create a Resource instance this way with a script attached, and then you save that Resource/ScriptInstance pair using ResourceSaver or the Inspector, then you'll get a resource file (.tres) which, when loaded, comes with all the ScriptInstance's properties setup and initialized properly.

@willnationsdev It does not, though. The issue I am talking about is exactly in the fact the ResourceLoader does not properly initialize a custom resource, as it uses ClassDB::instance to create instances of your serialized resources, and ClassDB is oblivious to your custom resource's class_name and type. So it creates a generic Resource. And while ScriptInstance may be loaded correctly, _init function is never called and neither are setters.

It may be a missing feature from the engine standpoint, but from usability standpoint it's a bug. I ran into this problem because sanity checks I put into setters were never called, when I loaded resources from disk. And there was no way to run any code on instance creation, as _init was not called either. This creates problems when dealing with custom resources, and something should be changed about handling deserialization.

willnationsdev commented 4 years ago

@pycbouh Hmm, I guess I'll have to do some experimentation/investigation about the Issue when I have more time. I thought for sure I'd done that before...

astrale-sharp commented 4 years ago

Wow, this would be so great! I have come across the same kinds of problem exactly, this would be a very great way to organize the workflow of projects, I myself tried to do that in my first projects before realizing it doesnt work but i would be very glad to see this come to life

Reneator commented 4 years ago

Wow, this would be so great! I have come across the same kinds of problem exactly, this would be a very great way to organize the workflow of projects, I myself tried to do that in my first projects before realizing it doesnt work but i would be very glad to see this come to life

I was working with this change in a custom build, and its sadly unstable currently and has some problems (not possible to successfully build project for release, at least for me, maybe i did something else wrong).

It does make it a lot easier to work with resources (and even encourage you) that you can even have only the children of a certain resource-script show up.

But after many problems i changed back to current release build, and with some workarounds you can come close to this:

Looking forward to when this is in stable.

astrale-sharp commented 4 years ago

I didnt see there already was a PR for this ! Oo I'll test it, lets see if its that instable ;)

Flavelius commented 3 years ago

Just leaving this here as the issue was closed to keep the discussion here: when someone tackles this please try to also address the underlying issue that surfaces in c#/mono : https://github.com/godotengine/godot/issues/39499

willnationsdev commented 3 years ago

@Flavelius The way that I would be planning to resolve this is two-fold.

  1. Enable the engine to recognize scripted properties which refer to a user-defined resource type. The solution here involves you needing to have a global name assigned for the type. In GDScript, this is the class_name keyword that was added in 3.1 which will register the name-to-filepath mapping in the project settings file (project.godot). These "script classes" can be recognized by name from the engine and editor. This will enable exported custom resources in GDScript to function properly both at parse-time and runtime.
    • Current PR to resolve this is godotengine/godot#32018.
  2. Enable other languages to also define and work with script classes in general (since, right now, it can't be done). This involves teaching C#, VisualScript, and NativeScript to be able to define script classes, access them by name, and incorporate their information for type hints / static typing (if at all possible).
    • There is not yet a PR submitted for this, but I have a WIP branch on my fork at willnationsdev/godot/tree/P22/script-classes. So far, I have added a ScriptServer singleton which will allow people to access the scripts by name (either by string with get or as generated properties with get_property_list()). I have also added the ability to define a script class name for a VisualScript and a GraphNode for pulling script classes from a dropdown. Waiting to figure out how to handle the C# part before I submit the pull request. Already have a WIP in my cs-script-classes branch, but I've had trouble with it. Modifying C# to report an error if you try to export a Resource WITHOUT that resource also being a class name would be what you have to do in that case. I support I can try to figure that out once I have the ScriptClass attribute working.
TextusGames commented 3 years ago

Bad resource export workflow is one of the main reasons that prevent me from using godot.

Typositoire commented 3 years ago

Recently started to use Godot and god (ot xD)... Scrolling down that Massive resource list when exporting a custom resource is a pain :/ This feature would be aweeeeeesome!

Reneator commented 3 years ago

@Typositoire Regarding usability of that context-menu there is also this proposal: https://github.com/godotengine/godot-proposals/issues/43

So its kind of "Which one is quicker" as either one would solve some major pains.

Calinou commented 3 years ago

@Typositoire Please don't bump issues without contributing significant new information. Use the :+1: reaction button on the first post instead.

h0lley commented 3 years ago

For people looking for a way to improve their workflow with setting and editing custom resources from the inspector, tool scripts may be helpful. Consider this:

tool
extends Node

export(Array, Resource) var custom_res setget set_custom_res

func set_custom_res(value):
    custom_res.resize(value.size())
    custom_res = value
    for i in custom_res.size():
        if not custom_res[i]:
            custom_res[i] = MyCustomRes.new()
            custom_res[i].resource_name = "custom initial name"

godotrestest

This skips the step of having to select your custom resource from the huge list of all possible resource types. However I still very much support this proposal; the ability to just do export(MyCustomRes) would be much more intuitive.

Flavelius commented 3 years ago

@Serenitor this makes an assumption that does not really help most of the time - creating new resources by default, which ignores subclasses and file references and potentially creates impossible data (abstract classes in c# case)

h0lley commented 3 years ago

@Flavelius sure, I never claimed this would cover any all cases.. not sure about the "most of the time" part though. Obviously when this is not the behavior you'd want in your particular use-case, you would not put a script like that. I'm just saying this could help in some cases. It works just fine for me.

Keep in mind though that there's nothing stopping you from adding initialization logic to your resources and even turning them into tool scripts themselves. Resources can take care of sub-resources in just the same manner.

It can't be denied that tool scripts can be helpful here. I have no experience with C# and any issues related to that though.

Shadowblitz16 commented 3 years ago

is this going to be added before 4.0? Its really annoying to have every resource type popup when you click the resource inspector item. I don't like having to scroll down all the way just to clear my resource.

also I have to use setters everywhere to ensure that no one passes a invalid resource type

willnationsdev commented 3 years ago

@Shadowblitz16 In case you haven't seen it, the most recently mentioned pull request (above this comment) is a PR for the 3.2 branch that includes script class support for all languages and has exportable custom resource types from any language to any language.

Implementing this feature for 4.0 will have to wait until after GDNative 2.0 changes are merged since I've been told it will change the way the global script class naming system fundamentally works (and custom resources rely on those names to function properly).

cgbeutler commented 3 years ago

For those who need a workaround, I use this wrapper class: https://gist.github.com/cgbeutler/6901ee99b57390b5ab7bc0761f496c1c

Example usage:

var __backer := ResRef.new(MyResource)
export var my_res :Resource  setget set_my_res, get_my_res
func set_my_res( value :MyResource ) -> void:  __backer.resource = value
func get_my_res() -> MyResource:  return __backer.resource as MyResource

The ResRef (resource reference) class tries to always keep the variable filled with a valid object of the type it's created with. Hitting the refresh arrow next to the resource will init a new obj of that type. Hitting clear will also create a new one. You can also still drag-n-drop into that slot if you need to. It won't fix the ultra-long dropdown list, though, so... sorry. Cross your fingers for 3.3.1

jabsatz commented 3 years ago

Started working with godot fairly recently and just went through a huge rabbit hole looking for this exact thing, glad to see it's in the works! It really will be a killer feature specially for those of us that work with dedicated level designers

Calinou commented 3 years ago

@jabsatz Please don't bump issues without contributing significant new information. Use the :+1: reaction button on the first post instead.

belzecue commented 3 years ago

While a solution is being worked out -- and assuming a filter box similar to elsewhere in the editor (FileSystem -> Search Files; Scene -> Filter nodes) is out of the question for the resources flyout panel/list -- can we get custom resources sorted to the top of the resource popup list, instead of at the bottom? Not having to scroll for miles to reach your custom resource would be a big help. I expect adjusting the sorting would be a small code change? If someone can point me to the right source file to begin noodling, I'd love to try to implement this sorting change to my code fork. (I'm not a cpp coder but I am a coder and can probably figure this out if someone gets me started, thx.)

CORRECTION: As pointed out by cgbeutler below, the custom resources do sort alphabetically with standard resource classes -- I had named my custom class with a lowercase first letter which is why it sorted to the bottom of the list. Using an "A_" prefix will at least get the custom classes to the top of the list.

cgbeutler commented 3 years ago

@belzecue In both the inspector dropdown and the new resource window they are sorted alphabetically (at least that's how it is for me in 3.3.) I suppose you could just do smirf-naming an AA and have your "personal library" be named off that.... If you are wanting a fix that bad, just try willnationsdev's pull request fork: https://github.com/godotengine/godot/pull/44879 I think it's been green on all but the linux check for a few weeks now, it just touches too many files to get in easily.

willnationsdev commented 3 years ago

It's been green even on the Linux checks. It just fails the "you must have scripting API changes documented". However, I just amended the commit to include those changes too, so we'll see how things pan out. There is a known C# bug that I haven't resolved yet though. See one of the later comments on the PR for details. (solved it!)

tito91 commented 3 years ago

@pycbouh Hmm, I guess I'll have to do some experimentation/investigation about the Issue when I have more time. I thought for sure I'd done that before...

@willnationsdev I feel like the topic of setters of custom resource properties not being called was dropped some time ago. Do you know if we can expect it to be addressed anytime soon?

willnationsdev commented 3 years ago

@tito91 I am unfamiliar with this issue. Will have to dig into it at some point. Was there a closed bug report about it or something?

tito91 commented 3 years ago

@willnationsdev Nothing that I know of or was able to find. I will file a new one.

tito91 commented 3 years ago

@willnationsdev My problem with the setters not being called in custom resource script is fixed by using tool keyword. I'm sorry for bothering you.

AnidemDex commented 2 years ago

While https://github.com/godotengine/godot/pull/44879 gets merged, I ended with a temporary solution that let you export custom resources in the editor (as OP mentioned), using _get_property_list function: change the hint string.

Ok, so, let's me explain: _get_property_list let you modify any Object property metadata, adding or complementing the metadata of current defined properties. So, how to implement this magic trick?

tool
# in any GDScript
var exported_resource:<CustomResourceClass>

func _get_property_list() -> Array:
var exported_resource_property:Dictionary = {
    "name":"exported_resource",
    "type":TYPE_OBJECT,
    "hint":PROPERTY_HINT_RESOURCE_TYPE,
    "hint_string": "<CustomResourceClass>",
    "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE
    }
return [exported_resource_property]

Where <CustomResourceClass> is the name of the class that you've registered with class_name.

I've implemented this trick while testing with Dialogic plugin and GodotDialogPlugin, wrote an article about that (in spanish) and discussed a bit about it in Godot Engine discord server (in #español-general channel, naturally).

The downside of this method is that it prints in your console an error related with ClassDB (I suppose it's because it can't found the class in ClassDB, no idea if i have to open an issue related to this) image But it let you export any custom resource and inherited resources too: image image

Maybe this could be helpful for future views on this issue?

Update

This doesn't works with Quick Load feature added in 3.4 This doesn't work partially with 3.4. It let you create the resource, but doesn't let you load or quick load it from the inspector.

Razoric480 commented 2 years ago

Yeah, it works, but error spam is pretty unfortunate, and if you have a more complex class, you need to edit _get_property_list whenever you export a new variable or rename one in any of the base classes. You also can't assign default values to properties and, so, can't revert those values. See godotengine/godot#30440

Gnumaru commented 2 years ago

I would like to leave here the workaround I'm currently using as it may be usefull to others.

From now on that resource file can be used as a prototype to facilitate creation of new instances of that custom resource. To use it, do the following:

  1. In the scene tab, click on a node that exports a resource variable
  2. drag and drop the custom resource file from the filesystem tab to the exported resource field in the inspector tab. the field label will change from [empty] to the name of your resource file
  3. click in the down arrow to the right of the resource field. This will open the "infinite" drop down.
  4. drag your mouse outside of the drop down, press up 4 times, then press enter. This will move the dropdown selection through the bottom of the list and choose the "make unique" list entry
  5. the label will no longer show the name of your resource file, it will only show "Resource", but it will in fact be an instance of your custom resource, with all the custom fields you expect

Please note that albeit my description was lengthy in order to be complete, once the resource file is already created, the workflow is quite simple, with only three steps:

  1. drag and drop the resource file from the file system to the inspector tab
  2. open the giant popup
  3. select "make unique" the easy way by pressing up four times then enter
Razoric480 commented 2 years ago

This is a completely unnecessary workaround.

class_name MyResourceType
extends Resource
  1. Right click in the File System
  2. Click New Resource
  3. Type MyRes... to narrow down the filter
  4. Create the new resource of that type
  5. Drag it into the export field of your choice
PoisonousGame commented 2 years ago

If godot supports export var stats: Stats, then resources can be automatically completed. I hope this feature can be implemented in 4.0.