Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a small tutorial on using Resource scripts. #1768

Merged
merged 1 commit into from
Oct 4, 2018

Conversation

willnationsdev
Copy link
Contributor

I can't vouch (at all) for the C# snippets in here, so someone more familiar with Godot's C# syntax will have to proofread those. I'm also not sure if my links are executed properly and whether I can safely nest code snippets within a note or warning (as I've done here).

This should finally help people get a better idea of how useful Resource scripts can be, how to make them, and how to use them.

@mhilbrunner
Copy link
Member

Thanks for this! I'll proofread - FYI, building the docs yourself locally is a great way to verify how it looks in the output, and isn't too hard: Look at the Building with Sphinx section of the README in this repository.

@willnationsdev willnationsdev force-pushed the res-script branch 2 times, most recently from 1d7cfd5 to babdcc7 Compare October 1, 2018 20:41
@willnationsdev
Copy link
Contributor Author

@mhilbrunner I got it rendering locally (yay!), so I've fixed up a bunch of stuff with the syntax, however I can't seem to figure out how I'm supposed to make the "script classes" link work. It's supposed to link to the "Scripting Continued" page at the "Register scripts as classes" header, but all the variations I've tried (like :ref:`script classes <scripting_continued#register-scripts-as-classes>`) don't seem to work.


- Resources can even serialize sub-Resources recursively, meaning users can design even more sophisticated data structures.

- Users can save Resources as version-control-friendly text files (\*.tres). Upon exporting a game, Godot serializes resource files as binary files (\*.res) for increased compression.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't *.res files also faster to load?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, because the same amount of data is packed into a smaller space, so it doesn't take as long to parse the data as input.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose I could do "for increased speed and compression" instead.

With GDScript, it only supports Resource classes at the top level. Defining
a Resource subclass, i.e. an inner class, within a GDScript file will have no
effect. If one attempts to save an instance of a Resource subclass, none of
the scripted properties will serialize to the saved file.
Copy link

@LikeLakers2 LikeLakers2 Oct 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like a bug to me. I've saved ShaderMaterials with Shader resources inside them before, so I don't see why this should be any different.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can have sub resources within scripts, but if you try to define an entirely new type of Resource as a subclass within another script, then Godot isn't going to identify the path for the script on that resource as "path to the file that has the Resource subclass AND somehow mark that you need to assign the constant value of the subclass Script resource as the script value for the created *.tres. You know, something like "res://file.gd#MyResource" or what-have-you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like, if I make a Resource object and set it's script to be not another script file, but a Resource class that's a subclass of a script file (where the script file could derive ANYTHING), it won't start serializing those script properties properly or anything.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, sounds good.

Copy link

@LikeLakers2 LikeLakers2 Oct 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah... I think that's my bad for the misunderstanding, then.

That being said, it might be worth trying to reword this paragraph, as I get the feeling it can still come off as ambiguous, not making a clear enough distinction between inner classes and subresources.

Sure, sounds good.

By the way, I deleted my first response to your reply because I realized I completely misunderstood what you were going for, and decided it'd be better to remake my comment.

.. note::

Resource scripts are similar to Unity's ScriptableObjects. However, unlike in
Unity, there is no need to implement IMGUI logic to enable users to create
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, there is no need to implement immediate gui or custom editor to use a "common" ScriptableObject.

The current workflow (I tested it in Unity 2018.3):

  1. Create your ScriptableObject class:
public class CustomData : ScriptableObject {
    public string data;
}
  1. Add it to Assets->Create menu:
// This is equivalent to `class_name` in Godot 3.1+
[CreateAssetMenu(menuName="MyCustomStuff/CustomData")]
public class CustomData : ScriptableObject {
    public string data;
}
  1. Create a component that will use your ScriptableObject:
public class MyComponent : MonoBehaviour {
    public CustomData data;
}
  1. Create your asset using the Assets->Create menu;
  2. Drag and drop it to the data field in the inspector.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, interesting. That's news to me. Great that they added that feature! Appreciate the heads-up. I'll go ahead and take out that tidbit then.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unity docs and changelogs are pretty confusing so I don't know in which version this started to work this way 😅

@willnationsdev
Copy link
Contributor Author

@williamd1k0 @LikeLakers2 Updated the document to reflect all of the changes.

@willnationsdev willnationsdev force-pushed the res-script branch 2 times, most recently from 64ff075 to c4ae77b Compare October 1, 2018 21:48
@willnationsdev
Copy link
Contributor Author

Okay, finally figured out how to make the script classes link work (just doing an absolute link via URL rather than relative one with :ref:).

public int health;

[Export]
public Resource subResource;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

http://docs.godotengine.org/en/latest/getting_started/scripting/c_sharp/c_sharp_style_guide.html#naming-conventions

public methods/properties need to be PascalCase. Update this for every property.

scripts grant all the same benefits as the base Resource object. They
inherit both reference-counting memory management and automatic serialization
between binary or text data (\*.res, \*.tres) and in-memory object
properties.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resource scripts grant all the same benefits as the base Resource object.
They inherit both reference-counting memory management and automatic serialization
between binary or text data (*.res, *.tres) and in-memory object properties.

This is IMO very complex. We need to avoid overwhelming users with cognitive overload :)

How about:

Resource scripts inherit all the same benefits as the base Resource object:
Automatic memory management using reference counting, automatic serialization
between binary or text data (*.res, *.tres) and in-memory object properties.

Copy link
Contributor Author

@willnationsdev willnationsdev Oct 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about...

As Resources, Resource scripts inherit the ability to freely translate between object properties and serialized text or binary data (*.tres, *.res). They also inherit the reference-counting memory management from the Reference type.

Sound better?


public class BotStatsTable : Resource
{
private Godot.Dictionary<String, BotStats> stats = new Godot.Dictionary<String, BotStats>();
Copy link
Contributor

@exts exts Oct 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use underscore prefix for private members & update other occurrences across the board http://docs.godotengine.org/en/latest/getting_started/scripting/c_sharp/c_sharp_style_guide.html#naming-conventions

public class Bot : KinematicBody
{
[Export]
public BotStats stats;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public Resource subResource;

[Export]
public String[] strArray;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public class BotStats : Resource
{
[Export]
public int health;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@willnationsdev
Copy link
Contributor Author

@mhilbrunner @exts updated to reflect the syntax changes and the rewording of the introductory paragraph. I have also added an additional warning and code segment for GDScript (not even sure if a C# example would apply for the warning since it has to do with Dictionary vs. Resource memory management and I don't know if C# GC's Dictionaries). I also fixed a few rendering errors that were preventing the examples from showing up.

@willnationsdev
Copy link
Contributor Author

willnationsdev commented Oct 2, 2018

Oh, also, I realized that I've been mixing and matching my C# code examples' syntax for allocating objects, but I don't see anything in the docs to indicate what it looks like in the style guide or API differences. Which of these would be used?

MyResource res = new MyResource(10);
MyResource res = MyResource.new(10);

And am I supposed to implement an _Init() override, or do I define a constructor?

[Export]
public String[] Strings { get; set; }

public override void _Init(int health = 0, Resource subResource = null, String[] strings = [])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C Sharp's _Init() implementation is parameterless.
This might be best as a constructor:

public BotStats(...)

Default parameters must be compile time constants.
I suggest using String[] pStrArray = null instead

{
Health = health;
SubResource = subResource;
Strings = strings;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you can use strArray = pStrArray ?? new String[0]; to tidy up the default parameter.

public class Bot : KinematicBody
{
[Export]
public BotStats Stats;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BotStats' type is not currently displayed in the inspector.
This means that exporting this variable only presents the opportunity for users to select the wrong type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there isn't a way to export the type (scripted types may not be supported this way?), perhaps something where we export Resource, but then do a runtime check that the type extends BotStats.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BotStats is not displayed in the Create New Resource dialog either.
Its unclear to me how to create one using the editor GUI.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this seems like a deeper issue than I realized. godotengine/godot#22641

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for right now, you basically HAVE to export it as an engine type (Resource or some derivative that is still in the engine) and then check if the runtime instance is a script at runtime.


public override void _Init()
{
if (!Stats)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To perform a null check we should use:

if (stats == null)

{
if (!Stats)
{
Stats = (ResourceLoader::load("BotStats.cs") as BotStats).new(10);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are using the constructor as suggested above we can use:

stats = new BotStats(10);

[Export]
public BotStats Stats;

public override void _Init()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While testing this code I found that _Init() isn't being called (godotengine/godot#22633).

@willnationsdev
Copy link
Contributor Author

Updated to reflect all changes and relevant Issues.

public class MyNode : Node
{

public class MyResource : Resource {
Copy link
Contributor

@KellyThomas KellyThomas Oct 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good now, just a couple of smaller points.
This brace would be best on the next line.

public override void _Ready()
{
if (Stats != null && Stats is BotStats) {
Godot.print((Stats as BotStats).Health);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this should be GD.Print().

@NathanLovato
Copy link
Contributor

Thanks much for your work!

@NathanLovato NathanLovato merged commit 43aa1c0 into godotengine:master Oct 4, 2018
@willnationsdev
Copy link
Contributor Author

Oh, I hadn't made those last two changes yet (moving the brace and changing Godot.print() to GD.Print()). @NathanLovato

@NathanLovato
Copy link
Contributor

Sorry, just added the two small changes!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants