Skip to content

Commit

Permalink
Merge pull request #1768 from willnationsdev/res-script
Browse files Browse the repository at this point in the history
Add a small tutorial on using Resource scripts.
  • Loading branch information
NathanLovato authored Oct 4, 2018
2 parents 0668b36 + 0b488f4 commit 43aa1c0
Showing 1 changed file with 252 additions and 4 deletions.
256 changes: 252 additions & 4 deletions getting_started/step_by_step/resources.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,260 @@ Freeing resources
Resource extends from :ref:`Reference <class_Reference>`.
As such, when a resource is no longer in use, it will automatically free
itself. Since, in most cases, Resources are contained in Nodes, scripts
or other resources, when a node is removed or freed, all the children
or other resources, when a node is removed or freed, all the owned
resources are freed too.

Scripting
---------

Like any object in Godot, not just nodes, resources can be scripted,
too. However, there isn't generally much of an advantage, as resources
are just data containers.
Like any Object in Godot, users can also script Resources. 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.
This comes with many distinct advantages over alternative data
structures such as JSON, CSV, or custom TXT files. Users can only import these
assets as a :ref:`Dictionary <class_Dictionary>` (JSON) or as a
:ref:`File <class_File>` to parse. What sets Resources apart is their
inheritance of :ref:`Object <class_Object>`, :ref:`Reference <class_Reference>`,
and :ref:`Resource <class_Resource>` features:

- They can define constants, so constants from other data fields or objects are not needed.

- They can define methods, including setter/getter methods for properties. This allows for abstraction and encapsulation of the underlying data. If the Resource script's structure needs to change, the game using the Resource need not also change.

- They can define signals, so Resources can trigger responses to changes in the data they manage.

- They have defined properties, so users know 100% that their data will exist.

- Resource auto-serialization and deserialization is a built-in Godot Engine feature. Users do not need to implement custom logic to import/export a resource file's data.

- 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 speed and compression.

- Godot Engine's Inspector renders and edits Resource files out-of-the-box. As such, users often do not need to implement custom logic to visualize or edit their data. To do so, double-click the resource file in the FileSystem dock or click the folder icon in the Inspector and open the file in the dialog.

- They can extend **other** resource types besides just the base Resource.

.. warning::

Resources and Dictionaries are also different in that both are passed by reference,
but only Resources are reference-counted. This means that if a Dictionary is passed
between objects, and the first object is deleted, it will invalidate the second
object's variable. This is not so for Resources which won't free their memory until
*all* instances are gone.

.. tabs::
.. code-tab:: gdscript GDScript

extends Node

class MyObject:
extends Object
var dict = {}

func _ready():
var obj1 = MyObject.new()
var obj2 = MyObject.new()
obj1.dict.greeting = "hello"
obj2.dict = obj1.dict # obj2.dict now references obj1's Dictionary
obj1.free() # obj1 is freed and the Dictionary too!
print(obj2.dict.greeting) # Error! 'greeting' index accessed on null instance!

# to avoid this, we must manually duplicate the Dictionary
obj1 = MyObject.new()
obj1.dict.greeting = "hello"
obj2.dict = obj1.dict.duplicate() # now we are passing a copy, not a reference
obj1.free() # obj2's Dictionary still exists
print(obj2.dict.greeting) # prints 'hello'

Godot makes it easy to create custom Resources in the Inspector.

1. Create a plain Resource object in the Inspector. This can even be a type that derives Resource, so long as your script is extending that type.
2. Set the ``script`` property in the Inspector to be your script.

The Inspector will now display your Resource script's custom properties. If one edits
those values and saves the resource, the Inspector serializes the custom properties
too! To save a resource from the Inspector, click the Inspector's tools menu (top right),
and select "Save" or "Save As...".

If the script's language supports `script classes <https://godot.readthedocs.io/en/latest/getting_started/step_by_step/scripting_continued.html#register-scripts-as-classes>`__,
then it streamlines the process. Defining a name for your script alone will add it to
the Inspector's creation dialog. This will auto-add your script to the Resource
object you create.

Let's see some examples.

.. tabs::
.. code-tab:: gdscript GDScript

# bot_stats.gd
extends Resource
export(int) var health
export(Resource) var sub_resource
export(Array, String) var strings

func _init(p_health = 0, p_sub_resource = null, p_strings = []):
health = p_health
sub_resource = p_sub_resource
strings = p_strings

# bot.gd
extends KinematicBody

export(Resource) var stats

func _ready():
# uses an implicit, duck-typed interface for any 'health'-compatible resources
if stats:
print(stats.health) # prints '10'
.. code-tab:: csharp

// BotStats.cs
using System;
using Godot;

namespace ExampleProject {
public class BotStats : Resource
{
[Export]
public int Health { get; set; }

[Export]
public Resource SubResource { get; set; }

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

public BotStats(int health = 0, Resource subResource = null, String[] strings = null)
{
Health = health;
SubResource = subResource;
Strings = strings ?? new String[0];
}
}
}

// Bot.cs
using System;
using Godot;

namespace ExampleProject {
public class Bot : KinematicBody
{
[Export]
public Resource Stats;

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

.. note::

Resource scripts are similar to Unity's ScriptableObjects. The Inspector
provides built-in support for custom resources. If desired though, users
can even design their own Control-based tool scripts and combine them
with an :ref:`EditorPlugin <class_EditorPlugin>` to create custom
visualizations and editors for their data.

Unreal Engine 4's DataTables and CurveTables are also easy to recreate with
Resource scripts. DataTables are a String mapped to a custom struct, similar
to a Dictionary mapping a String to a secondary custom Resource script.

.. tabs::
.. code-tab:: gdscript GDScript

# bot_stats_table.gd
extends Resource

const BotStats = preload("bot_stats.gd")

var data = {
"GodotBot": BotStats.new(10), # creates instance with 10 health
"DifferentBot": BotStats.new(20) # a different one with 20 health
}

func _init():
print(data)
.. code-tab:: csharp

using System;
using Godot;

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

public BotStatsTable()
{
_stats["GodotBot"] = new BotStats(10);
_stats["DifferentBot"] = new BotStats(20);
Godot.print(_stats);
}
}

Instead of just inlining the Dictionary values, one could also, alternatively...

1. Import a table of values from a spreadsheet and generate these key-value pairs, or...

2. Design a visualization within the editor and create a simple plugin that adds it
to the Inspector when you open these types of Resources.

CurveTables are the same thing except mapped to an Array of float values
or a :ref:`Curve <class_Curve>`/:ref:`Curve2D <class_Curve2D>` resource object.

.. warning::

Beware that resource files (\*.tres/\*.res) will store the path of the script
they use in the file. When loaded, they will fetch and load this script as an
extension of their type. This means that trying to assign a subclass, i.e. an
inner class of a script (such as using the ``class`` keyword in GDScript) won't
work. Godot will not serialize the custom properties on the script subclass properly.

In the example below, Godot would load the ``Node`` script, see that it doesn't
extend ``Resource``, and then determine that the script failed to load for the
Resource object since the types are incompatible.

.. tabs::
.. code-tab:: gdscript GDScript

extends Node

class MyResource:
extends Resource
export var value = 5

func _ready():
var my_res = MyResource.new()

# this will NOT serialize the `value` property.
ResourceSaver.save("res://my_res.tres", my_res)
.. code-tab:: csharp
using System;
using Godot;

public class MyNode : Node
{

public class MyResource : Resource {
[Export]
public int Value { get; set; } = 5;
}

public override void _Ready()
{
MyResource res = new MyResource();

// this will NOT serialize the `Value` property.
ResourceSaver.save("res://MyRes.tres", res);
}
}

After seeing all of this, we hope you can see how Resource scripts can truly
revolutionize the way you construct your projects!

0 comments on commit 43aa1c0

Please sign in to comment.