Ressources

Nœuds et ressources

Jusqu’à présent, les Nœuds ont été le type de donnée le plus important dans Godot car la plupart des comportements et des fonctionnalités du moteur sont implémentés par leur biais. Il y a un autre type de donnée qui est tout aussi important : Ressource.

Les nœuds vous offrent des fonctionnalités : ils dessinent des sprites, des modèles 3D, simulent des propriétés physiques, organisent une interface utilisateur, etc. Les ressources sont des conteneurs de données. Elles ne font rien par elles-mêmes et les nœuds utilisent les données qu’elles contiennent.

Tout ce que Godot sauvegarde ou charge depuis le disque est une ressource. Que ce soit une scène(un .tscn ou un fichier .scn), une image, un script… Quelques exemples de ressources : Texture, Script, Mesh, Animation, AudioStream, Font, Translation, etc.

Quand une ressource est chargée depuis le disque dur, elle est chargée une seule fois. Cela signifie, si il y a une copie de cette ressource déjà chargée en mémoire, essayer de charger à nouveau cette ressource retournera la même copie encore et encore. Cela correspond au fait que les ressources sont justes des conteneurs de données, donc il n’y a pas besoin de les dupliquer.

Typiquement, chaque objet, un Nœud ou une Ressource, peuvent exporter des propriétés. Il y a de nombreux de types différents comme une chaîne de caractère, un entier, Vector2, etc… et n’importe lequel de ces types peut devenir une ressource. Cela veut dire que les nœuds et les ressources peuvent contenir des ressources comme propriétés :

../../_images/nodes_resources.png

Externe vs intégré

Il y a 2 possibilités pour sauvegarder les ressources. Vous pouvez :

  1. Externe à une scène, enregistrés sur le disque en tant que fichiers individuels.
  2. Intégrés, enregistrés dans le fichier *.tscn ou le fichier *.scn auquel ils sont attachés.

Pour être plus spécifique, voici une Texture dans un nœud Sprite :

../../_images/spriteprop.png

Cliquer sur l’aperçu de la ressources vous permet de voir et de configurer ses propriétés.

../../_images/resourcerobi.png

La propriété chemin nous indique d’où provient la ressource. Dans ce cas, elle provient d’une image PNG appelée robi.png. Lorsque la ressource provient d’un fichier comme celui-ci, il s’agit d’une ressource externe. Si vous effacez le chemin ou que ce chemin est vide, il devient une ressource intégrée.

Le basculement entre les ressources intégrées et externes se produit lorsque vous enregistrez la scène. Dans l’exemple ci-dessus, si vous effacez le chemin ` »res://robi.png »`et que vous sauvegardez, Godot sauvegardera l’image dans le fichier de scène .tscn.

Note

Même si vous enregistrez une ressource intégrée, lorsque vous créez une scène plusieurs fois, le moteur n’en charge qu’une copie.

Chargement de ressources à partir du code

Il existe deux manières de charger des ressources depuis un code. Tout d’abord, vous pouvez utiliser la fonction load() à tout moment :

func _ready():
        var res = load("res://robi.png") # Godot loads the Resource when it reads the line.
        get_node("sprite").texture = res
public override void _Ready()
{
    var texture = (Texture)GD.Load("res://robi.png"); // Godot loads the Resource when it reads the line.
    var sprite = (Sprite)GetNode("sprite");
    sprite.Texture = texture;
}

Vous pouvez également preload (précharger) des ressources. Contrairement à load (charger), cette fonction lira le fichier à partir du disque et le chargera au moment de la compilation. Par conséquent, vous ne pouvez pas appeler le préchargement avec un chemin variable: vous devez utiliser une chaîne constante.

func _ready():
        var res = preload("res://robi.png") # Godot loads the resource at compile-time
        get_node("sprite").texture = res
// 'preload()' is unavailable in C Sharp.

Chargement de scènes

Les scènes sont également des ressources, mais il y a une différence. Les scènes sauvegardées sur le disque dur sont des ressources de type PackedScene. Cela signifie que la scène est emballée à l’intérieur d’une ressource.

Pour obtenir une instance de la scène, la méthode PackedScene.instance() doit être utilisée.

func _on_shoot():
        var bullet = preload("res://bullet.tscn").instance()
        add_child(bullet)
private PackedScene _bulletScene = (PackedScene)GD.Load("res://bullet.tscn");

public void OnShoot()
{
    Node bullet = _bulletScene.Instance();
    AddChild(bullet);
}

Cette méthode crée les nœuds dans la hiérarchie de la scène, les configure (définit toutes les propriétés) et retourne le nœud racine de la scène qui peut être ajouté comme enfant à n’importe quel autre nœud.

Cette approche présente plusieurs avantages. Comme la fonction PackedScene.instance() est rapide, vous pouvez créez de nouveaux ennemis, balles, effets, etc… sans avoir à les recharger à chaque fois à partir du disque dur. Il est important de se rappeler que, comme toujours, les images, les maillages, etc sont tous partagés entre les instances de la scène.

Libérer des ressources

Lorsqu’une ressource n’est plus utilisée, elle se libère automatiquement. Puisque, dans la plupart des cas, les ressources sont contenues dans des Nœuds, quand un nœud est libéré, le moteur libère également toutes les ressources de ce nœud si aucun autres nœuds ne les utilisent.

Créer vos propres ressources

Comme tout objet de Godot, les utilisateurs peuvent également scripter les ressources. Les scripts de ressources héritent de la possibilité de traduire librement entre les propriétés d’objet et le texte sérialisé ou les données binaires (/.tres, /.res). Ils héritent également de la gestion de la mémoire de comptage de références du type Référence.

Cela présente de nombreux avantages distincts par rapport aux structures de données alternatives telles que les fichiers JSON, CSV ou TXT personnalisés. Les utilisateurs peuvent uniquement importer ces ressources en tant que Dictionnaire (JSON) ou en tant que Fichier à analyser. Ce qui distingue les ressources, c’est leur héritage de Objet, Référence, et Ressource :

  • Ils peuvent définir des constantes. Ainsi, les constantes d’autres champs de données ou d’objets ne sont pas nécessaires.
  • Ils peuvent définir des méthodes, y compris des méthodes setter/getter pour les propriétés. Cela permet l’abstraction et l’encapsulation des données sous-jacentes. Si la structure du script de ressource doit être modifiée, le jeu utilisant la ressource ne nécessite pas de changement aussi.
  • Ils peuvent définir des signaux afin que les ressources puissent déclencher des réactions aux modifications des données qu’elles gèrent.
  • Ils ont des propriétés définies, de sorte que les utilisateurs savent à 100% que leurs données existeront.
  • L’auto-sérialisation et la dé-sérialisation des ressources est une fonctionnalité intégrée du moteur Godot. Les utilisateurs n’ont pas besoin d’implémenter une logique personnalisée pour importer/exporter les données d’un fichier de ressources.
  • Les ressources peuvent même sérialiser les sous-ressources de manière récursive, ce qui signifie que les utilisateurs peuvent concevoir des structures de données encore plus sophistiquées.
  • Les utilisateurs peuvent enregistrer des ressources sous forme de fichiers textes de contrôle de version conviviaux (*.tres). Lors de l’exportation d’un jeu, Godot sérialise les fichiers de ressources sous forme de fichiers binaires (*.res) pour une vitesse et une compression accrues.
  • L’inspecteur de Godot Engine rend et édite les fichiers de ressources immédiatement. En tant que tels, les utilisateurs n’ont souvent pas besoin de mettre en œuvre une logique personnalisée pour visualiser ou éditer leurs données. Pour ce faire, double-cliquez sur le fichier de ressources dans le dock Système de Fichiers ou cliquez sur l’icône de dossier dans l’inspecteur et ouvrez le fichier dans la boîte de dialogue.
  • Ils peuvent étendre d’autres types de ressources, outre la ressource de base.

Avertissement

Les Dictionary et les Resource sont passés en référence, mais seules les Resource ont un compteur de références. Cela signifie que la durée de vie d’un Dictonary dépend de l’objet qui l’a créé. Même si le Dictionary est partagé entre plusieurs objets, si l’objet qui a créé le Dictionary est supprimé alors le Dictionary est aussi supprimé. Puis toutes les références vers le Dictionary vont être invalidées. A contrario, l’objet Resource lui ne sera pas effacé tant qu’il y aura des objets qui s’en serviront.

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 facilite la création de ressources personnalisées dans l’inspecteur.

  1. Créez un objet Ressource dans l’Inspecteur. Cela peut même être un type qui dérive de Ressource, du moment que votre script hérite de ce type.
  2. Définissez la propriété script dans l’inspecteur comme étant votre script.

L’inspecteur affiche désormais les propriétés personnalisées de votre script de ressources. Si on modifie ces valeurs et enregistre la ressource, l’inspecteur sérialise également les propriétés personnalisées ! Pour enregistrer une ressource de l’inspecteur, cliquez sur le menu Outils de l’inspecteur (en haut à droite), puis sélectionnez « Enregistrer » ou « Enregistrer sous … ».

Si le langage du script prend en charge ` les classes de script, cela simplifie le processus. Définir un nom pour votre script seul l’ajoutera à la boîte de dialogue de création de l’inspecteur. Cela ajoutera automatiquement votre script à l’objet Ressource que vous créez.

Quelques exemples.

# 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'.
// 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 botStats) {
                GD.Print(botStats.Health); // Prints '10'.
            }
        }
    }
}

Note

Les scripts de ressources sont similaires à ScriptableObjects de Unity. L’Inspecteur fournit un support intégré pour les ressources personnalisées. Si nécessaire, les utilisateurs peuvent même concevoir leurs propres scripts d’outils basés sur Contrôle, et les combiner avec un EditorPlugin pour créer des visualisations et des éditeurs personnalisés pour leurs données.

Les DataTables et CurveTables de Unreal Engine 4 sont également faciles à recréer à l’aide de scripts de Resources. Les DataTables sont une chaîne mappée à une structure personnalisée, similaire à un dictionnaire mappant une chaîne à un script de ressource personnalisé secondaire.

# 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)
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); // Creates instance with 10 health.
        _stats["DifferentBot"] = new BotStats(20); // A different one with 20 health.
        GD.Print(_stats);
    }
}

Au lieu de simplement aligner les valeurs du dictionnaire, on pourrait aussi, alternativement …

  1. Importer une table de valeurs à partir d’une feuille de calcul et générer ces paires clé-valeur, ou …
  2. Concevez une visualisation dans l’éditeur et créez un plug-in simple qui l’ajoutera à l’inspecteur lorsque vous ouvrirez ces types de ressources.

Les CurveTables sont la même chose, sauf qu’elles sont mappées à un tableau de valeurs flottantes ou à un objet ressource Curve/Curve2D.

Avertissement

Attention, les fichiers de ressources (*.tres/*.res) vont stocker le chemin du script qu’ils utilisent dans le fichier. Une fois chargés, ils vont chercher et charger ce script comme une extension de leur type. Cela signifie que tenter d’affecter une sous-classe, c’est-à-dire une classe interne à un script (comme l’utilisation du mot clé class dans GDScript) ne fonctionnera pas. Godot ne sérialisera pas correctement les propriétés personnalisées sur la sous-classe de script.

Dans l’exemple ci-dessous, Godot chargerait le script Nœud, verrait qu’il n’étend pas Ressource, puis déterminerait que le script n’a pas pu être chargé pour l’objet Ressource car les types sont incompatibles.

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)
using Godot;

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

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

        // This will NOT serialize the 'Value' property.
        ResourceSaver.Save("res://MyRes.tres", res);
    }
}