31 / GodotOnReady

A C# Source Generator that adds convenient onready-like features to your C# scripts in Godot Mono (3.x) without any reflection.
https://www.nuget.org/packages/GodotOnReady
MIT License
123 stars 13 forks source link

Add [OnReadyLoad("...")] for resources (PackedScene, Texture, etc.) #1

Closed 31 closed 3 years ago

31 commented 3 years ago
[OnReadyLoad("res://somewhere/i/have/a/scene.tscn")] private PackedScene Scene { get; set; }

GDScript lets you do this:

export var scene : PackedScene = preload("res://ExportPackedScene/ExternalScene.tscn")

which shows up like this:

image

The default shows up nicely when you freshly attach it to a new node, and you can set it to something else.

This C# code does the same thing as far as I can tell:

[Export] public PackedScene Scene
{
    get => _scene;
    set
    {
        _scene = value;
        _sceneSet = true;
    }
}

private const string _sceneDefault = "res://ExportPackedScene/ExternalScene.tscn";
private PackedScene _scene;
private bool _sceneSet;

public ExportPackedScene()
{
    // Set Scene in the constructor when in the editor so when Godot instantiates the node, it can
    // tell what the default should be.
    if (Engine.EditorHint)
    {
        Scene = GD.Load<PackedScene>(_sceneDefault);
    }
    GD.Print($"End of constructor: {Scene}");
}

public override void _Ready()
{
    // At runtime, only load the scene once we're in _Ready. Godot will construct this object and
    // populate the property with the custom value if non-default. Note that we can't just check
    // if Scene is null, because it may have intentionally be set to null in the editor.
    if (!Engine.EditorHint && !_sceneSet)
    {
        Scene = GD.Load<PackedScene>(_sceneDefault);
    }
    GD.Print($"End of ready: {Scene}");
}
31 commented 3 years ago

Asked for people to look at this on Discord, and it came up that you can use field initializers for the default:

[Export] public PackedScene Scene2 { get; set; }
    = Engine.EditorHint ? GD.Load<PackedScene>("res://ExportPackedScene/ExternalScene.tscn") : null;

public override void _Ready() { ... }

However, field initializers only work for auto properties, so we can't track bool _sceneSet, so intentionally setting the property to null doesn't work (the default will load anyway) with this method.

31 commented 3 years ago

Actually, it is better to use a field initializer on the backing property. This way the generator doesn't have to generate a constructor, and the node script can implement its own constructor without interference.

[Export] public PackedScene Scene
{
    get => _scene;
    set
    {
        _scene = value;
        _sceneSet = true;
    }
}

private const string _sceneDefault = "res://ExportPackedScene/ExternalScene.tscn";
private PackedScene _scene = Engine.EditorHint ? GD.Load<PackedScene>(_sceneDefault) : null;
private bool _sceneSet;

public override void _Ready()
{
    if (!Engine.EditorHint && !_sceneSet)
    {
        Scene = GD.Load<PackedScene>(_sceneDefault);
    }
    GD.Print($"End of ready: {Scene}");
}