godotengine/godot

When C# script extends Resource class, the resource doesn't show up in the "Create New Resource" menu

sudoman281 opened this issue ยท 45 comments

Godot version:
3.1

OS/device including version:
Arch Linux - newest

Issue description:
When C# script extends Resource class, the resource doesn't show up in the "Create New Resource" menu

Steps to reproduce:
Create a C# script, make it extend Resource class and then try to create a new resource of that type.

Please add this, people looking to swith from unity will have a much easier time adapting to godot

Extending Resource doesn't automatically register it as a resource usable in the editor.

class_name can be used in GDScript to register node types, but I don't know if it works for resources. There's no equivalent for C# yet that I know of.

CJNA commented

I have tackled this issue and this problem is harder than I think. Currently, there exists circular dependency between resourcePreview and Resource class, and thus at least in editor, it is unlikely to implement a way to bypass this circular dependency and add a generator on constructor of Resource.

Unless there is other way of calling resourcePreview when extending Resource class, it is required to restructure resource class implementation and hierachy to resolve this issue.

Is there a workaround for C# defined resources in the meantime?

In other words, how do I create a resource externally from my scene and save it as my resource type? Support for loading the resource via the editor is already in place it looks like. I just don't know how to create it in the first place.

I believe you can create a generic resource within Godot then attach your C# source which extends Resouce to the created asset.

Doesn't appear assigning a C# source to the Resource actually allows it to be edited.

Maybe this issue is relevant:
godotengine/godot-proposals#18 (comment)

With a manually compiled godot project with the pull request by willnationsdev i am currently able to work with custom-resources in gdscript, but I am thinking about switching to C# (as its a language with features i might need for my project)

He stated that he has a C# implementation of it in the works.

I have a WIP implementation of this ready-to-go (the latest commit on this branch of my fork), but I've hit a roadblock in that the ScriptClass class attribute I've added never gets detected by the CSharpScript class. I've asked for @neikeq's help on the matter previously, but I never followed up when he didn't reply because I saw that he was busy trying to work on Android/Web exports for the 3.2 release and didn't want to bother him.

If anyone can take the work I've done in that commit and figure out how to make C# actually parse and detect the attribute, then I can continue developing it and hopefully finish it for godotengine/godot-proposals#22. I already have a separate vs-script-classes branch for VisualScript and another WIP branch to export the ScriptServer as an engine singleton so that NativeScript/PluginScript bindings can presumably access the script class data too. At that point, every language would have full access to the script class system. There is also a pull request I made for allowing script classes to be exported as Resources in the Inspector (#32018).

This is a huge limitation of C# and force us to change code structs to workaround it. How's the development on it issue?

For anyone stuck on this, one workaround is to declare the class in GDScript extending Resources and call it in C#
example:

// data.gd
extends Resource
class_name Model_test
var Speed : int = 0;
var Size : int = 0;

// UseData.cs
[Export] public List dataList; // set data in inspector

But it's not a final solution since you will need to use Object.Get() to read variables and you can't manipulate it as a C# class. So is quite important that this feature be implemented anytime soon to improve the overall support of C# in Godot.

This is a huge limitation of C# and force us to change code structs to workaround it. How's the development on it issue?

There's no ETA for fixing this issue, as nobody has made a fully functional fix yet.

This is a huge limitation of C# and force us to change code structs to workaround it. How's the development on it issue?

There's no ETA for fixing this issue, as nobody has made a fully functional fix yet.

That's sad but thanks for reply.

@vitorbalbio Just an FYI, I've been gradually putting together a branch on my fork to implement various parts of godotengine/godot-proposals#22 (my P22/script-classes branch). The C# part hasn't been done yet, but I've updated my ScriptServer singleton inclusion and my VisualScript script class support. Just need to take the time to get a Mono build going again and see if I can't get things working in 4.0 (I never managed to make it work in 3.2 either since the script class attribute would never get detected. But we'll see what happens).

@vitorbalbio Just an FYI, I've been gradually putting together a branch on my fork to implement various parts of godotengine/godot-proposals#22 (my P22/script-classes branch). The C# part hasn't been done yet, but I've updated my ScriptServer singleton inclusion and my VisualScript script class support. Just need to take the time to get a Mono build going again and see if I can't get things working in 4.0 (I never managed to make it work in 3.2 either since the script class attribute would never get detected. But we'll see what happens).

Hi willnationsdev! Thanks for your hard work, i'm a game developer working on indie studios in Brazil for about a decade (with Unity) and now we're giving a chance to Godot. Most of what we do can be done in godot (C# support is a must) but some paper cuts like this are stopping us to make a full jump on board. I will keep following Godot close and see how things evolve with time. Thank you.

@vitorbalbio Just curious why just creating a generic resource and manually adding the resource script to the tres file not working for you?

@vitorbalbio Just curious why just creating a generic resource and manually adding the resource script to the tres file not working for you?

It kinda works and is what i'm using mostly of the time. But once your data struct is too heavy you will need tons of tres files in your project. In my case: I have files for "units" (spaceships) each unit have "skills", each skill have "pattern". For each spaceship i have something like 15 tres. My game will have something like 20 of those so hundreds of .tres files to manage while it would be just 20 files if i was using GDScript and i quite doubt that i won't need more until the end.

@vitorbalbio You should be able to create Resource properties ON resources which you can attach scripts to. Then, when you load the resource, it also loads the other resources embedded within it.

So, e.g. you could have a SpaceshipData resource that has exported Resource properties. You create a resource in the Inspector, add the SpaceshipData script to it. Suddenly, those exported resource properties (for, e.g. skills, pattern, etc.) are displayed. Then you create new resources in each of those and drag the respective resource scripts onto each of those properties (SkillData, PatternData, etc.). The sub-inspectors for those properties now have the scripted properties displayed. Once you have everything configured, you save the resource as a single file. And now, if you do GD.Load("spaceship1.tres"), you end up with a full SpaceshipData that contains all of the embedded resources.

"You should be able to create Resource properties ON resources"
I can

"which you can attach scripts to. Then, when you load the resource"
I can't. This option is not exposed

"You create a resource in the Inspector"
I can

"add the SpaceshipData script to it."
I can't. This option is not exposed.

See:
Godot_v3 2 2-stable_mono_win64_rEB4432HZM
data is: "[Export] public Resource data;"

I can indeed set it as a new Resource in inspector:
Godot_v3 2 2-stable_mono_win64_es7PNtRmDb

But after that i can't set any script or class to it:
Godot_v3 2 2-stable_mono_win64_CSDL3UKxq9

@vitorbalbio Oh, wat. That's weird. If you make a Resource individually in the Inspector, the script property shows up under a Reference category section. But that entire category section seems to be omitted in that dropdown. That feels like it should be considered a bug. All one would have to do is make sure that the script shows up in there and then you can assign a value there again. With that said, you CAN do it in 3.2.1 (where I tested it), but you have to, as you said, make individual resource files and then drag them over into the properties rather than being able to just inline it all in the Inspector/sub-Inspectors.

"If you make a Resource individually in the Inspector, the script property shows up under a Reference category section."
This is exactly how i create the data files. I add a new "Resource" in the project and set the script in inspector. But as you can see i can't do it without create a new file first. By the end i will have a lot of .tres data files.

I don't think its a bug either since you can't select the script for any "sub inspector" in godot. But it would be great if only it was exposed in inspector!

I don't think its a bug either since you can't select the script for any "sub inspector" in godot.

Doesn't mean we shouldn't interpret this as a bug, hehe. At least, I would see it as a bug. It's silly that you can change the type of a resource at the top level, but not in a nested level.

I would love to see this implemented anyway ;)

What is the status of C# custom resources?

@SimplyMiha As far as I know, nobody has started implementing this yet.

@SimplyMiha the missing pieces are my two existing PRs that are both WIP (one for custom resources export, one for adding script class support to other languages). However, despite that, I've been having trouble even getting a functioning mono build of the master branch on Windows. Every time I build it and run, the engine closes almost immediately. Can't even test whether my class attribute is working or not.

Anyway, once we have script class support on C# classes and the ability to export custom resources in general, then we will may need an additional PR to ensure that exporting a script class named C# resource works in the C# language.

@SimplyMiha the missing pieces are my two existing PRs that are both WIP (one for custom resources export, one for adding script class support to other languages). However, despite that, I've been having trouble even getting a functioning mono build of the master branch on Windows. Every time I build it and run, the engine closes almost immediately. Can't even test whether my class attribute is working or not.

Anyway, once we have script class support on C# classes and the ability to export custom resources in general, then we will may need an additional PR to ensure that exporting a script class named C# resource works in the C# language.

This looks a lot of work. There's one contributor working exclusively in C# support right? Maybe someone can poke him to help about it.

#15210
recreated the steps they did here and was able to create a c# script that extends resource class.

P.S if this comment violates any rules please let me know and kindly remove this.

EDIT:
was able to create a custom object from a C# script that extends resource class

Stumbled across this and found a "somewhat" ok workaround. The idea is to first define your custom resources using gdscript, and then use a generic helper method to read these in to their equivalent c# objects using reflection.

I've attached a example project that lets you read in a nested custom resource. It's currently just a prototype so you'd have to write the "other way" i.e. saving to a resource, but should be enough to go on for a current workaround. Ideally of course this wouldn't be needed!

Edit : I've now also included how to save in the project.
reflection test.zip

I spent some time digging around online and found a very clean C# plugin solution written by wgmor which solves this issue. I ended up modifying it a little bit to support registering custom C# resources anywhere under a specified list of directories to check, and it works perfectly for my needs.

I also integrated rob-mur's reflection-based process of saving as a workaround to this Godot C# resource saving issue and also added support for saving completely new instances of resources at new file paths during runtime.

EDIT: For saving you should use cgbeutler's solution since it's much cleaner and simpler.

EDIT EDIT: The plugin now uses attributes to achieve a more GDScript-like way of registering custom types. Here's a sample of how to add a custom type using this plugin.

// Inside a file named CustomNodeDemo.cs
using MonoCustomResourceRegistry;

// Registers a custom type with 
// 	a name of "CustomNodeDemo",
//	an icon located at "res://icon.png",
//	and a base type of "Node2D"
[RegisteredType(nameof(CustomNodeDemo), "res://icon.png", nameof(Node2D))]
public class CustomNodeDemo : Node2D
{
	...
}

Here's the sample node inside the context menu:

Untitled

If anyone is interested in using it, here is the repo:
Godot-Mono-CustomResourceRegistry

Here is another little workaround that's fairly simple. (Tested in 3.3.1)
Hit the little refresh next to a resource to get a new one. It's not pretty, but it may hold you over.

private CustomResource __res = null;
[Export]
public CustomResource res {
    get {
        if ( __res == null )  res = null; // lazy init (in-case godot loads from disk instead.)
        return __res;
    }
    set {
        if ( value == null ) {
            value = GD.Load<CSharpScript>("res://CustomResource.cs").New() as CustomResource;
        }
        __res = value;
    }
}

(Note: You can't make custom resources directly in C#, as their Script seems to lack the resource_path needed to save the Script as an ExtResource in a tscn or a tres file. Loading the long way will make sure that you get the cached version and thus that it gets saved as an ExtResource in the scene or resource file. Maybe I'll write a helper for newing up resources from c#...)

To create a C# custom resource saved to FileSystem simply:

In FileSystem dock > Right-click > New Resource...
Choose "Resource" (The base type. Should be first in the list.)
Hit "Create"
Name it as usual. Click "Save".
Double-click the new resource in the FileSystem dock to bring up its inspector.
Find the Script field and drag-drop or load your .cs script.
image

Use Ctrl-s to save those changes. You should now see all the exported fields. (If not, or data is messed up, script may need [Tool] mode.)
image

Note that if you change a script, godot will create a new object for the "new" script, then copy all exported variables to matching ones in the updated script. Any info that doesn't line up, will be lost.

Could a workaround be to add a child node with the properties of the c# resource and use setget to update the node when the resource's properties are changed, and add a GD script to the node to update the resource when it's properties are edited in the inspector? I am yet to try this out, but could this be a possible solution.

@DJ-Dav, yeah, but that would involve a lot of boilerplate and remove a lot of the benefits of resources. Godot basically has 2 garbage management methods: nodes and references. The tree will clean up stuff when that branch is deleted. When you need data in multiple places in the tree, like a sprite or something, resources (of type reference) allow that cross-tree data, caching it for you and cleaning it up when all references to it are gone. If you only need some data in only one place in the tree, you don't really need a custom resource, saving a node to a scene would do just fine in those cases.
In other words, nodes and resources are designed to solve different data dependency needs. You could work up a mix, but that kinda kills the benefits of the design of Godot.

Still not implemented in 3.4.3 stable Mono official

@ducabyte you just have to keep an eye on #48201 and its 3.x counterpart #44879 since those are the PRs that add support for this. Not sure if they specifically will be merged in, but either they or a separate PR that is derived from their contents will likely be merged in before the 4.0 release (core team has expressed an intent to add the feature for 4.0), and then it may end up being backported to a subsequent 3.x release after that.

@ducabyte you just have to keep an eye on #48201 and its 3.x counterpart #44879 since those are the PRs that add support for this. Not sure if they specifically will be merged in, but either they or a separate PR that is derived from their contents will likely be merged in before the 4.0 release (core team has expressed an intent to add the feature for 4.0), and then it may end up being backported to a subsequent 3.x release after that.

ty @willnationsdev

@cgbeutler I tried your solution, but I'm not seeing script variables show up in the inspector. Adding [Tool] didn't resolve things. Any idea what I could try to get it working? I'm on 3.4.4.stable.mono.

image

image

@awardell I think it has to be a property, not a field...?

using Godot;

[Tool]
public class HexCellData : Resource
{
  [Export]
  private string testStr { get; set; } = "test";
}

Also, if there are any compilation errors anywhere in your project, the new c# exports won't show up till it compiles.

I just discovered it was my own stupid operator error. I needed to build solution first. Now it works how I have it, as a field, even without [Tool]. My apologies for the distraction! Thanks for your reply.

This problem still there?

DJ-Dav commented

This problem still there?

I don't know, i am having issues getting .NET stuff to work these days. I will test this on a different machine later.

I'm struggling with custom resource in C# too, they should really add a note: C# support is not good enough to make a game now, just for fun only! And the performance on MacOS is totally a mess, please wait until we come up a new version in the future!

Totally a waste of time.

Addendum: If custom types still aren't showing up after adding [GlobalClass], click Build, then try again.

I'm struggling with custom resource in C# too, they should really add a note: C# support is not good enough to make a game now, just for fun only! And the performance on MacOS is totally a mess, please wait until we come up a new version in the future!

Totally a waste of time.

I am on MacOS M1 chip using dotnet 7.0 and not seeing any issues. Godot version 4.1.1