godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.17k stars 98 forks source link

We need a way to unify scripts, scenes and nodes (Discussion) #1906

Closed Shadowblitz16 closed 3 years ago

Shadowblitz16 commented 4 years ago

Describe the project you are working on

Space ship networking game

Describe the problem or limitation you are having in your project

Scene and Scripts have two different hierarchy's

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Make Node and Scripts derive from script so we don't need to manage two different hierarchy trees

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Nodes and Scenes would no longer attach scripts to them but instead would be directly editable as scripts Scene information would be stored in the script under the @SceneStart and @SceneEnd annotations

This information would not be visible to the user in the script editor in godot but would still be editable in external script editors The class name would be the scene name

#no need for class name but it can be used to export your type
extends Control #must extend the type you are changing this changes the type

export var myInt := 0

#start defining scene
@SceneStart
   var btn = Button.new()
   add_child(btn)
@SceneEnd 

If this enhancement will not be used often, can it be worked around with a few lines of script?

Yes it would be and No it can't

Is there a reason why this should be core and not an add-on in the asset library?

It gets combines the script and node hierarchy's and gets rid of any confusion between node and script

dalexeev commented 4 years ago

You can do like this:

extends Control

export var my_int = 0

func _init():
    var btn = Button.new()
    add_child(btn)

or like this:

extends Node

func _init():
    add_child(preload("scene.tscn").instance())

I sometimes miss the ability to register a scene as a type (as is possible for scripts with class_name) to use that in code. The closest working analogy is:

const MyScene = preload("my_scene.tscn")
const MySceneType = preload("my_scene.gd")

var my_scene := MyScene.instance() as MySceneType

While for scripts it looks much shorter:

const MyScript = preload("my_script.gd")

var my_script := MyScript.new()

Yes, there are certain inconveniences with scenes, but this is not a reason to change the inheritance hierarchy so strangely as you suggest.

willnationsdev commented 4 years ago

@dalexeev The problem that OP has is the fact that there are divergent inheritance hierarchies between scripts and scenes. The fact that you can have a script which becomes dependent on state supplied by the scene, but the script can still be instantiated without the scene accompanying it, or the script in the scene could be replaced with a different script that makes the scene function differently. OP doesn't like the looseness and additional required logic checking of such an architecture, so he is searching for a way to solve that problem. Something that allows one to synchronize the disparate systems.

Adding the "build a scene" logic as runtime script logic in the _init() of a script doesn't help OP because he wants to be able to still use the GUI editor to build his scenes.

Adding an instantiated, preloaded scene as a child in the _init() of a script also doesn't help because then the scene root and the script are not the same object, nor do they necessarily need to have any type equivalence in the first place. OP wants a stricter system that allows one to tightly bind one script to one scene as the root node type. Therefore, if you edit the scene file from the GUI, you're automatically also editing the script simultaneously and the file serializations are merged into one.

Adding two preloaded constants for the scene and the scene's root script, separately and manually like your third example, also isn't sufficient for OP since 1) you have two separate files which are strictly related or dependent on one another, but yet no system helps to facilitate the maintenance of that relationship, and 2) by extension of point 1, if you suddenly decided to change the scene file later on to use a different script, then suddenly your GDScript example would stop working. You'd have to manually go to the code and change it to reference a different type hint.

Now, with all of that said, I also have many issues with this proposal.

@Shadowblitz16

Nodes and Scenes would no longer attach scripts to them but instead would be directly editable as scripts

As I've mentioned before, I believe any suggestion that involves fundamentally changing the engine's architecture is going to be a hard sell. You'd have to prove actually prove, with usable examples, that your suggested changes will have a measurable impact on productivity/usability and fulfill a need in the community.

Yes, that is in part what the purpose of these proposals is. If enough people want a proposal to be implemented, then it indicates that there is a desire for a similar, if not identical, solution to the stated problem. However, just because a proposal is highly voted on doesn't necessarily mean the devs will choose to merge a pull request that implements the solution. If the pull request in question has a detrimental effect on the design or long-term maintainability of the codebase/application as a whole, then it will be rejected outright until an alternative design that better resolves the problem in a healthier way can be designed, written, and approved.

With that stated, let's look at what your changes would bring. For one, as I've mentioned in the previous post, making these changes would lead to invasive changes that fundamentally change the way Godot works (huge amount of effort to implement) and which add huge restrictions on what people are capable of even doing with scripts and scenes.

As of this point in time, the script and scene system is incredibly flexible. Suddenly taking away that flexibility in the interest of making Godot more opinionated isn't really in the spirit of Godot Engine which generally tries to create low-level, simple, flexible systems that can be adapted in multiple ways by end-users. This keeps the code easy to maintain and enables it to appeal to a wide audience. You would be taking away much of those benefits by introducing such restrictions, and would further add more logic to scripting and how it integrates with Godot, especially in regards to third-party text editor support which likely won't know what the heck your scene info is doing in the scripts they're looking at.

You'd be forcing people to create engine-specific plugins for existing language extensions just to make the language usable in the third-party editor. "Oh, to use C# in VS Code, I just need the C# extension. Makes sense. Wait, no, I'm getting errors. What is this crap in the C# file? Oh...huh? I can't use the regular C# extension cause it doesn't know how to handle this metadata. I need to use the Godot C# extension that isn't maintained as well as the one used by the whole rest of the world? Eff that!" Very bad idea. Users and extension maintainers alike would hate us.

Scene information would be stored in the script under the @SceneStart and @SceneEnd annotations

Firstly, not every language supports metadata like annotations. Even GDScript is just recently getting that. If you force every script to contain the scene information of the scene it is a root of, then you simultaneously prevent many different scripting languages from being used by Godot entirely (so GDNative is less flexible, like no C++ support).

Secondly, even the ones that do support annotations likely won't support annotations with that kind of syntax. I know I've never seen C# with something like that, and I'm fairly certain that even the GDScript ones don't intend to create annotations with start and end points like that.

#start defining scene
@SceneStart
   var btn = Button.new()
   add_child(btn)
@SceneEnd 

If you look at the content of a scene file, you'll notice that the scene file doesn't store information as script code. It stores it in a machine-readable format that is faster and more efficient for the Godot Engine backend to process. If you were to start using script metadata to store script code for scene information, you'd effectively be removing all Godot Engine backend processing to handle the creation of scenes, and you'd be delegating those operations to script code (essentially making it identical to using the script's constructor to build the content).

This may have been what you meant to communicate in your proposal, but doing so would slow down the runtime execution of building scenes significantly since the engine C++ code is no longer responsible for it. The larger your game world is, the more costly it becomes to use GDScript for developing your game in the first place. With the current design of Godot, GDScript and VisualScript simply don't have to worry about most performance considerations with how slow they are just because the costly stuff is being handled by the engine. You would be ending that.

This information would not be visible to the user in the script editor in godot but would still be editable in external script editors

It's generally a terrible idea to ever have users be tinkering with the same file that is intended to be edited or updated in real-time by a machine. That's why computer-generated files often have comments at the top that say something like, "THIS IS A GENERATED FILE. IF YOU TOUCH IT, DON'T BLAME ME FOR LOSING ALL OF YOUR CHANGES OR CORRUPTING MACHINE DATA." No matter what, I guarantee you, if you start mixing scene code inside of script code and then inviting people using third-party editors to modify those "script" files, Godot will start getting flooded with complaints about it and asked to switch things back.

Yes, I know that the computer should only touch the stuff between the annotations. But again, not all languages support that. And what if users think they can just start modifying the generated logic and have the scene "detect" and update with those changes? And then what if it parses something wrong and the scene suddenly says that it is corrupted cause humans were monkeying around with its data? Machines have their files they deal with, and humans have their own.

If humans have the ability to modify a file that has metadata from a machine inside of it, then they should only be modifying the file through a custom editor that only shows them the human portion of that file. This is how built-in scripts work today. You'll notice that built-in scripts are not intended to be edited by third-party text editors. They are only ever modified in the ScriptEditor, and when saved they are automatically bundled into the metadata of the scene file.

The class name would be the scene name

Having the class name be generated from the scene name also wouldn't be good unless you had a global namespacing system for synchronizing all types of scripts (which doesn't exist and which, according to reduz, shouldn't ever exist). Otherwise, you wouldn't be able to name two different scenes the same name in different parts of the application (goodbye locally scoped naming).


I've mentioned before in #1224 how I believe you could fulfill all of the stuff you want from this proposal, AND without making any heavy changes to the current Godot Engine, AND without compromising the other benefits of the existing script and scene systems: the whole ScriptScene concept I described to you. It is very similar to this proposal, but goes the other direction of storing script information inside scenes, similar to built-in scripts, but creates a new type of resource that is more stringent on the binding.

If you would like, I can take the time to write up a formal proposal for that concept since, out of all the suggestions we've discussed going back and forth, I think that is the only truly viable one that could be implemented. And in fact, most of its features could probably be implemented purely as plugins (though not all). Still, it'd be good enough to build a plugin that demonstrates the potential usefulness of the feature and might be able to convince devs to add it to the core.

I'm not promising I would implement any such plugin though. I would just write up the instructions for how to get started making it. I simply don't have the time to devote to something of that scale.

Shadowblitz16 commented 4 years ago

@willnationsdev @dalexeev your right. The point of these proposals is to get peoples opinion on how how to combine the scene/node and script hierarchy's in a way that better support godot's design philosophy of being oop.

The reason why I am creating so many proposals is because I don't know where to post were I can get peoples ideas on how to do so. Github issues are encouraged to be about one thing and its up to the poster to supply the information on how to do it.

That I don't know. I only know that godot's pseudo inheritance doesn't fit well with true oop and seems to be a anti pattern when it comes to ease of use.

dalexeev commented 4 years ago

Just thinking. Is it possible to solve it somehow like this:

# my_scene.gd
extends Node
class_name MyScene
@scene("res://my_scene.tscn")
# At compile time, Godot verifies that the script is attached to the root of `res://my_scene.tscn`.
# another.gd
var my_scene := MyScene.new()
# At runtime, Godot loads and instantiates `res://my_scene.tscn`.
# var my_scene := preload("res://my_scene.tscn").instance() as MyScene

?

Also, if the problem with cyclic dependencies will be solved, I think the following code will work:

# my_scene.gd
extends Node
class_name MyScene

static func instance() -> MyScene:
    return preload("res://my_scene.tscn").instance() as MyScene
# another.gd
var my_scene := MyScene.instance()
Shadowblitz16 commented 4 years ago

If you mean linking the scene to the script I think deriving from it would be fine..

# my_scene.gd
extends res:://my/scene/path.tscn"  # or MyScriptBase or BaseNode
class_name MyScene
# true type
var my_scene := MyScene.instance()
willnationsdev commented 4 years ago

@Shadowblitz16 I don't like the idea of deriving it like that cause it would give the impression that you are extending a class that exists as the root node of that scene, not that the script itself is the root node of that scene (at least, that's my thought).

Shadowblitz16 commented 4 years ago

but you would be extending the scene though as your own type

if your extending something that isn't root it would just be a node type

#extend scene as true custom type
extends "my_scene.tscn"

#extend named script as true custom type
extends MyCustomType

#extend built in node as true custom type
extends Node2D

willnationsdev can you explain in more depth what you mean?

willnationsdev commented 4 years ago

@dalexeev

Actually, that would get decently close while going in a completely different direction than I was thinking. But, there are some downsides to the approach.

  1. it's a scripting-language specific implementation solution, so you'd have to duplicate the same logic in every language for which you want to support the feature.

  2. it requires boilerplate code for every class to ensure that it gets the scene instantiation consistency you are looking for.

  3. it doesn't technically solve the split inheritance hierarchies issue since I could, at any point, create a subsequent scene that changes what script is assigned to the root node of the scene, and there is nothing in the logic of the PackedScene class that should be responsible for checking that kind of thing either (wouldn't make sense to add it). So you still have the ability to break the assumed/intended relationship, i.e. anything extending TheBaseScene should also have methods and signals of TheBaseSceneScript - oh, but wait! TheDerivedScene suddenly has a different script! etc.

@Shadowblitz16

No language should be responsible for supporting the idea that it "extends" a PackedScene since that isn't an actual type in Godot's type system. If possible, we should avoid creating that sort of hack-ish relationship. My proposal does things in a slightly more legit way, as far as the type system is concerned.


The only reason I think @dalexeev's suggestion may get approved over my own is that the overall level of changes brought to Godot would be lower compared to my proposal #1909 if you only did it for GDScript. But my approach is applicable to more use cases out-of-the-box and with less customization to different parts of the codebase. (Edit: after having actually made the proposal and looking at it, it seems that there are more limitations than I realized initially since the necessity of built-in scripts for that proposal is non-trivial in terms of how much it limits usage).

DriNeo commented 4 years ago

I have an extreme idea (maybe for Godot 5.0 ?). A single script by scene is allowed and the code is stored inside the packedscene. We will loose some flexibility but there is some usability gains: no need to create a script, an empty one is ready to edit, less files to manage in the project, The editor UI is a bit simplified (no script list). Some topics are also clarified: the script scope is a scene and nothing else, no inheritance for the script just fetch the nodes you need in variables, when you inherit the scene no need to inherit the script, beginners don't have to ask for the sane number of scripts, for embedded script or in separate file etc... Sorry for the english.

willnationsdev commented 4 years ago

I finally created a formal proposal for dalexeev's idea, so now I don't have to @ them every time I want to refer to it. lol