Open TruelyMostWanted opened 1 year ago
Scripts that aren't marked as [Tool]
are not instantiated in the editor, only a placeholder gets created so it can store the properties' values edited through the inspector.
This behavior is by design, and I think it's somewhat explained in the documentation (see the Running code in the editor documentation page), but it could be made more prominent because it seems a lot of users miss this.
Since the type is not instantiated, the constructor won't be invoked.
In 3.x, C# used to invoke the constructor in order to retrieve the default values for properties but this caused issues since constructing the type had undesired side-effects and users complained about it (see https://github.com/godotengine/godot/issues/40970). AFAIK GDScript never had this behavior.
Looking at it by saying that this logic is code that runs in the editor makes sense. But yeah true. It definitly needs to be cleared up somewhere.
people struggle with this as early as 2018 / Godot 3.X, see #22633 (still open til today) And the common misconception with the "undesired side-effects" is interesting. Therefore a certain understanding of the life cycle of Godot Objects and Logic should be prominent.
Let me explain how my misconception was created:
And then i went here and created that issue with that long title above.
Also a Use-Case that came to mind:
public enum Id
{
FirstMediaTitle = 0,
SecondMediaTitle = 1,
ThirdMediaTitle = 2,
}
public partial class VoiceoverCollection : Resource
{
[Export] public string[] Locales;
[Export] public VoiceoverFile[] Voiceovers;
}
Then you create that custom resource (here: VoiceoverFile) and it stores a enum value (Id) and the media (here: AudioStreamWav)
public partial class VoiceoverFile : Resource
{
public enum Id { FirstVoiceoverTitle, SecondVoiceoverTitle, ThirdVoiceoverTitle, .... }
[Export] public Id ID;
[Export] public AudioStreamWav Audio;
}
public VoiceoverCollection()
{
var ids = Enum.GetValues<VoiceoverFile.Id>();
Voiceovers = ids.Select(id => new VoiceoverFile() { ID = id }).ToArray();
}
This way you can save like 7 out of 9 clicks
So i thought: If in GDScript people are able to perform value initialization via _init(), then i need to do the same thing in C# using a default constructor on the object.
This is correct. The GDScript _init
method is the constructor. C# does not have a _Init
method, you should use the constructor.
When using Colliders or Areas for example , you can give them a CollisionShape3D/2D below and they have a Shape Property [...] and once you create that object, it seems to have some default values like the Cube Shape being automatically 1x1x1. [...] Why does mine need to be a editor class to perform that logic?
The built-in types behave as Tool scripts, their code is always executed in the editor.
In C#, you can also define the default value of your properties by using inline initialization which doesn't require the script to be [Tool]
. There are some limitations to this, so I recommend to create the default value in a method and call it in the inline initialization.
public partial class MyResource : Resource
{
public MyEnum { A, B, C }
[Export] public int MyNumber { get; set; } = 42;
[Export] public string MyString { get; set; } = "Hello, World!";
[Export] public MyEnum[] MyEnumArray { get; set; } = GetMyEnumArrayDefault();
private static MyEnum[] GetMyEnumArrayDefault()
{
var values = Enum.GetValues<MyEnum>();
return values.Select(value => value).ToArray();
}
}
Okay the fact that the Godot types and classes have the [Tool] attribute is the final puzzle piece i was missing! Because it really did not feel like editor code at all! Thanks for the clear up!
Regarding ways of how to initialize values, everyone has a different preference. I personally like the seperation for non-static members: declaring variables/properties at the top and then assigning their values where the object gets created (constructor). On the static (readonly) or const side of things, i'm forced to instantly declare and define variables. So i do it there.
So it really comes down to what one wants
public partial class MyResource : Resource
{
public int MyValue = 42;
}
public partial class MyResource : Resource
{
public int MyValue;
public MyResource()
{
MyValue = 42
}
}
Godot version
4.2-dev3
System information
Godot v4.2.dev3.mono - Windows 10.0.19045 - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 3080 (NVIDIA; 31.0.15.3623) - AMD Ryzen 7 3800X 8-Core Processor (16 Threads)
Issue description
I'm not sure if this "by design", a bug or still open for discussion, but is the following true about value initialization?
The Idea: Whenever you want a Custom Node or Resource to Initialize its values, you're supposed to... -> override the _init() function in GDScript -> create a default constructor and do it ithere in C#
The Problem: Checking this out in Godot 4.2-dev3 or even Godot 4.1.1-stable this does not work. It only works in both languages (C# / GDScript) when making the class a [Tool] class.
A Simple Example In GDScript -> testnode.gd
@export var vec : Vector3;
func _init(): vec = Vector3(1.0,2.0,3.0);
My Expectation: This should work without the Tool Attribute by simply calling new TestNode() in C# or _init() in GDScript when adding the script to a Node or creating a new resource object via the inspector
The Question: Is this value initialization behaviour "by design" or a valid bug report? Also am i wrong or is the constructor / _init() usage not really documented at all?
Links to the Documentation(s) where i'd expect such a explanation -> https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp/c_sharp_basics.html C# basics only tells about _ready() beeing used for initialization, but doesnt tell anything about constructors and default values
->https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html GDScript basics tells about the _init() beeing the constructor and also about _static_init() beeing a static constructor,
Steps to reproduce
Take any project, and copy these steps:
(1) Open up any Godot version from the 4.X releases or dev-snapshots
(2) Create a .cs or .gd file and copy the following code GDScript -> testnode.gd
In C# -> TestNode.cs
(3) Add a simple "Node" and attach the script to it. You'll see the values are there.
(4) Remove the [Tool] or (at)tool, Save the script.
(5) Remove the script from the "Node" object
(6) Re-add the script to the Node. The value wont update
Minimal reproduction project
(see steps above)