So I'm looking at modules/gdscript/gd_parser.cpp, lines 259-350, and I'm thinking to myself, why can't we just also make it possible to do an open/close parentheses with a single variant parameter as a way to do a function call for JQuery-esque selector syntax for accessing nodes in the node hierarchy? Wouldn't that make Godot a LOT more powerful for acquiring and comparing groups of nodes? In fact, it's probably the ONLY engine that could do this effectively.
Example syntax off the top of my head:
(note, these would all return a specialized Array type where attempts to get/set a property or execute a method NOT associated with the custom Array type would instead attempt to do that on the first item of the array)
get_node("name")I do get that this functionality may not be desired as part of the main GDScript module and possibly preferred as a plugin. If that is the case, then it would be nice if the engine made allowances for plugins to define global variables / functions, so that something like this could be done with G(...) or what-have-you.
Just a couple of observations:
self and node references you don't need any wrapping. For jQuery you need to wrap with $ to add the magic to a DOM element, but a Godot node reference is magical by itself, which also applies to self.rich_lookup() method to Node and documenting it should be used with care, leaving $ only for fast lookups and more general use.I like that idea. Definitely offers itself up for more flexible usage (fast-dynamic / slow-cache varieties).
It'd be nice if there were an easier way of getting the roots of various the local/global scene though. Using
get_tree().get_root() etc. is kinda troublesome...
Maybe changing the name to .query("...")?
I think it's a bad and dangerous idea for two important reasons:
Godot is object oriented and closely sticks to what has been established in object oriented languages over the years. Scenes are closely modeled after classes, I would even go as far as to say that they are the same. One key characteristic of object orientation is that there is no global state. Not having a global state is actually one of the core goals of object orientation, because that's what has been causing bugs (and still is due to popular environments like Unity reintroducing the concept of global state).
Using globals should be avoided at all times, so introducing a feature that makes access to global data easier and makes it harder to spot the difference between access to the state of the own object and the global state will lead to bugs and lower code quality.
Just a quick example what using this feature would lead to. I'll skip the first three, since $(self) is the same as self, $(node_reference) is the same as node_reference and $("name") is the intended use of the $-sign where it's just an abbreviation for get_node(). But any of the other named usages would break encapsulation, because they would be acquiring "all" nodes that fit certain criteria. The problem with that is that in object orientation you are always designing an object without knowing whether or not it's "the" object. Let's say you created a small puzzle game and you want to make it more interesting. One thing that you can easily do in Godot (if you have been using object orientation correctly) is put three instances of that puzzle on screen and have the player solve them simultaneously. Or you might place two of them on a screen and use it as a split-screen multiplayer.
As soon as your code accesses the global state this won't work anymore, because what used to be "all the buttons" is now just half of all the buttons. Your assumptions about the game have changed and since your code is built around these assumptions it has to change as well.
There is a reason why you find this kind of query operation in a procedural environment like jQuery, because there you have "the" site that can be traversed. Object-oriented languages don't have this feature because facilitating access to the global state is just inviting you to write bad code.
I mean don't take my word for it, just look at the fact that C# deliberately disallowed to create global variables and functions. It was created in a time where C++ was one of the most influencial languages. C++ is object oriented and still allows you to create global variables and functions, so when a later language goes out of its way to make it harder to create global variables that should be a sign.
My second point is not quite as important, but I still think it matters. Code is written once and read several times by several people. Especially in a game related scripting language that is built for high-level game code your first impression when reading code should be "huh, this is actually really easy to understand, I can work with this!", not "what the f is $("!") supposed to mean??!".
No offense, but if you think that "get_tree().get_root()" is more troublesome than $("!") then I think you have the wrong priorities. High-level code should read like everyday english, not like a guy swearing in a comic strip.
I once thought that a JQuery-like selector language for Godot/GDScript would be beneficial. I even started what I called "GDQuery", though it didn't even get to the prototype stage...
I don't think global state is the problem here, since with or without this proposal, we still have Godot's Autoloads.
JQuery's main benefits are being cross-browser and wrapping the DOM API with some more readable names. At Godot, we have one version to worry about, which means that we can change (from time to time) various method names and add utilities if we see a need. You can't do that to web standards.
Another nice feature of JQuery is the ability to work on many elements as if they were just one. But, frankly, I haven't needed this in Godot, and if I do, I would just use "Groups" (or some array of nodes).
In Godot we deal with nodes that have locally-unique names. JQuery and CSS selectors are made to work with many nodes of the same type, which generally do not have names.
The problem JQ/CSS selectors solve is similar to the one NodePaths do, but is quite different in the end.
That's why I believe Godot doesn't need JQuery-like selectors. That's unless I'm proven wrong 馃槂 .
Yes, we have autoloads and groups, and both have led to bad code design in several Godot projects I have seen. I would actually say that they are an example for how features like this are dangerous.
But I agree with the rest. In Godot you deal with locally unique names, because you are supposed to adhere to object orientation which means that you should not access anything that doesn't belong to the scene the script is attached to.
That's why unlike in JQ/CSS you wouldn't "attach" a class to an existing button, but instead you create the button as one of the desired class. That way you don't have some separate code that modifies the button from the outside, but the code that deals with the button is part of the button itself. This is invaluable in debugging, because if you stick to that principle any bug can only be caused by code inside the class of the object that is showing the faulty behavior.
I guess it should be obvious why any code that accesses all existing nodes that fulfill a certain criteria would immediately break this principle.
That's only my opinion, but I think that's a terrible idea. @Warlaan explained my gut feeling pretty well. I've already strongly opposed adding stuff like find_node() that would parse the scene tree (ouch efficiency!) to find the node of the given name, and the above proposal looks just worse. I'm not familiar with jQuery and it's likely useful for web development, but I really don't see a positive use case for it in game development.
In some web frameworks, namely Angular, they isolate each component's so they are self-contained. Even a trick is applied by which CSS becomes component-local. But inside a component (which is a building block homologous to Godot scenes) you still can use rich selectors for CSS and even for picking sub-elements. I think that design decision brings the benefits of selectors while preventing overuse and encouraging black-box thinking.
Something like that may (not sure) be useful in Godot. I mean, as long as you select from inside a scene file (like owned=true in find_node()), a richer syntax may prove handy. Probably not every kind of selectors proposed, but mainly for expressive/compact specification of ancestorship chains.
Regarding the global variables topic, C# and Java are OO. C++ allows objects, but it's multiparadigm. So having OO languages with no globals isn't a sign of they not being OK in any case, but maybe not good for OO. Now, if you insist in seeing Godot/GDScript as a pure OO language where the same good practices apply, you'd be right. But the docs suggests that Godot is not to be thought in such terms.
Of course encapsulation and the like are good to be embraced, but Godot doesn't enforce them nor one should avoid them at all cost. Game scripts don't need to be maintenable forever and don't need to be universally reusable. You just have to pick a way of working and stick with it. Consistency across the project is the key.
Well, just keeping the debate on fire. :)
To me that's not a good idea. This syntax is heavy and make the code much more difficult to read.
I get the point in javascript, where you spend a lot of lines selecting elements in your HTML document, but it don't see the point in game programming.
@RandomShaper I am always interested in counterarguments, but I need a little more substance.
I didn't say that globals weren't "OK in any case", I said that they break OO principles and that has negative consequences. I mean who cares what I find OK, it's the negative consequences that I described that people should care about.
And if you make claims like "the docs suggest that Godot is not to be thought in such terms" then the decent thing to do would be to add at least one reference to such a place.
But to be honest I am not really interested in what the docs say, I am much more interested in an explanation how you could not see Godot as object oriented.
About "Game scripts don't need to be maintainable forever and don't need to be reusable" - so how is it an advantage to have scripts that aren't maintainable and not reusable? By making it a habit to stick to simple object orientation rules you end up with code that is easier to maintain and reuse, so why do you think it's a good idea to disregard them?
Saying that game scripts didn't have to be maintainable forever is quite a meaningless statement, because nothing is maintainable forever. The question is why you would choose to code in a way that experience shows leads to less maintainable code other than you not knowing about the consequences?
Please don't take this as me trying to "get back at you", I am really interested in a good discussion. After all if there is a different perspective at programming in Godot I want to know about it, but so far I don't see anything in there that isn't very much the same as classic object orientation.
@Warlaan, no offense taken! I'm aware some of my points may be wrong, but healthy discussion is the best way of telling right and wrong ideas apart.
Now, please let me correct my indecency. :) In a certain section of the docs you can read:
When making games with Godot, the recommended approach is to leave aside other design patterns such as MVC or Entity-Relationship diagrams and start thinking about games in a more natural way. Start by imagining the visible elements in a game, the ones that can be named not just by a programmer but by anyone.
Might I have interpreted that foo far? It may be. But I still think that sticking to academic correctness is not the way to go sometimes. As I said, as long a you are consistent about how you use engine features, you aren't doing anything bad. Of course, I'm assumming a little of common sense when taking that decision.
I swear I don't prefer unmaintainable code! But when you are choosing which engineering principles to apply, you should think about what exact type of maintenance will that code actually need. And maybe you can be more pragmatic in some areas by leaving some of those principles in the toolbox for other projects.
Let's consider a couple of examples:
p_ or something like that and I know what that means and consider them part of its public API. No problems so far.In the end, especially in game development, which is a semi-artistic field, there are many possible styles. Some of them are good. Yours and mine fit in that category. That's not to say every approach is right. There are a lot of ways of doing things bad but when you have been coding for years and actively learning to write quality code, you know what you are doing and can deviate from academicism.
If that existed, I would not use it because:
1) I know my scenes, no need to search everytime for stuff
2) I do get_node() as less often as possible because it's a linear search behind. I won't use $(Button) all around because I cache results already in array form, so at best it makes one line of my script look like jQuery or Linq.
3) Getting nodes from root introduces dependencies that I prefer to hand to design (inspector property?) or signals, unless I have no choice, in which case I do a contextual bottom-up search, or finally, a singleton. However it's fine on children within a scene, just like class members.
4) Abusing public variable access always bite me in the back
5) GDScript helpers can accomplish all this and be more readable because you can write them in english
I agree with global state being something to avoid, but remember you could also do these searches within a context. Put in Godot's scene tree, a context is a parent node. "global" is relative (except for groups heheh).
But still... I wouldn't use such a feature due to my points above.
Definitely an oversight of mine to ignore the fact that globally accessing things in that way is dangerous. Thanks for the feedback @Warlaan @akien-mga. Still though I feel like as long as you didn't allow for that (so completely ignoring the idea of a "!" marker or anything like that), I still feel like it'd be convenient to have a way of accessing objects based on their class/property/method/group details all at once with a single method rather than just having to pick one option for a single evaluation (e.g. has_method or is_in_group), even if it were only for caching purposes.
Perhaps something like this would be more appropriate as a C# plugin (when 3.0 comes out) so people can opt-in to its functionality.
@RandomShaper It's interesting that you interpreted the recommendation to "think in a more natural way" as a recommendation to work in a less object oriented way, because what you quoted could have been a sentence from one of my lectures on object orientation.
The way I understand it this is about "classic" object orientation as opposed to patterns like Entity-component-systems or MVC that you find in other engines, where you tend to have something like a PlayerController instead of a Player or a GameManager instead of a Game (a programming style that I like to call "DuckManagerTyping" - "If it quacks like a duck, swims like a duck and flies like a duck I call it a DuckManager").
I agree that groups can be the more pragmatic solution at times, it's just important to know the costs involved. I don't know whether this applies to you, but my impression is that many developers still think of game programming as kind of a waterfall model workflow where the game designer decides on every detail up front and the programmer then writes the code, but in most successful projects you can see that knowledge about the technical possibilities played a major role in the creative process, and that's why topics like agile development and rapid prototyping are so popular and why I teach that the task of a programmer is not to just make things work but to drive the creative process from a technical side. And in order to do so modular, reusable code is paramount, because it gives you the flexibility needed to quickly showcase and prototype different ideas.
I agree that game code is generally not required to be reusable beyond the scope of one project, but during the development of that one project code reuse and modularity are important to allow you to work efficiently.
Concerning public variables: imho that's a common misconception about OOP, and that impression is backed up by several books on the topic that I read. The core concept in object orientation is to bundle procedures alongside the data they manipulate (thereby turning them into methods).
Whether you read the player's health directly or via a getter doesn't change anything about that. In fact using a default getter to access a variable makes it no more or less encapsulated than if it's accessed directly.
The point about oop and encapsulation is that you shouldn't change the player's health from the outside, neither by accessing the health member nor using a setter, but you should instead give the player a method (e.g. "receiveDamage()") that enables the player to react to an event by modifying its health points from within the class.
Using getters and setters as a pattern instead of directly accessing members does make some sense, but it's not really a matter of oop and encapsulation, it's a matter of API design and library development (which isn't really something that applies to game script code).
I can't be sure about the spirit of the recommendation from the docs. The thing is that I interpret it as I've explained and that works for me, once more, without sacrifying quality.
But what do you think about me setting other object's data members from outside? Something to condemn?
What do you think about data-driven development where they say "object orientation is a lie"? And about dependency injection?
Conceptually, are those built on top of OOP or when using them you can no longer say you are using pure OOP? I know this is just semantics/naming, but it is to illustrate that by sticking too heavily to principles you can be limiting yourself more than helping the dev process.
Anyway, I don't feel you have answered to all my points. Namely, I'd like to hear what you think about my "styles" point of view. Do you agree with it at least partially?
I think we have been off-topic for too long. We should start thinking about coming to a conclusion if possible.
While I don't think there's need for all that functionality, @willnationsdev made few interesting points.
$ is syntax sugar for get_node() (less powerful but less typing).
$("!") makes no sense to me since you can do $"/root/node"
I like the idea $ operator could be used to get array of nodes. I can see two useful cases:
$"Button*" ?Just an idea :) As @willnationsdev said this might be interesting plugin, no need to have this in core Godot.
Updating this:
What might be useful is creating a means of agnostically addressing a variable as if it were a single object or an array / dictionary of objects, but that would belong in its own issue.
Most helpful comment
My second point is not quite as important, but I still think it matters. Code is written once and read several times by several people. Especially in a game related scripting language that is built for high-level game code your first impression when reading code should be "huh, this is actually really easy to understand, I can work with this!", not "what the f is $("!") supposed to mean??!".
No offense, but if you think that "get_tree().get_root()" is more troublesome than $("!") then I think you have the wrong priorities. High-level code should read like everyday english, not like a guy swearing in a comic strip.