Closed MysteryGM closed 6 years ago
If the question is simply : "Are there several way to achieve a same result in Godot ?", the answer is yes. And trying to force a single workflow is absurd, as each system in a game might have different requirements. You don't code the same way a system that requires a lot of performances and one that requires to be updated often by a game designer. All solutions have tradeoffs so there is no point is making an "official" workflow.
can make a class as complex as MeshInstance with multi components
With what components exactly?
EDIT: And just so you know, one of the issues you cited was closed with the author stating "This idea truly and utterly sucks and is completely nonsensical."
To demonstrate this I will use a mock class of what a MeshInstance, if made in Godot would look like. There is pseudo Unity, aka child nodes: Godot users use child nodes to create a equivalent to MultiScripts. Pseudo Unreal, aka controllers and intermediate: Pseudo Panda3D, aka Preload(Script): WTF script: Yes, I know Godot has a MeshInstance class. The point this demonstrates is that most of the workflows used by developers right now can make a class as complex as MeshInstance with multi components; except each workflow is seriously flawed.
I don't quite understand what you are trying to say here. Why would someone create a MeshInstance class if there is already one? And why is it a problem that Godot has features similar to other engines? Also, if you are trying to attach multiple scripts to one node you either do not use OOP or you are putting two separate functionalities into one node, which is easily fixed by using Godot's node system.
If the question is simply : "Are there several way to achieve a same result in Godot ?", the answer is yes...
If I make a hammer I know what it is meant for, I know some of what it shouldn't be used for even if I do not know everything it could be used for. If you give a hammer to a person who has no idea what it is, and you never explained how to use it; you can't act surprised when they start swinging wildly.
This is where Godot is at, the developers are swinging in the dark, hoping that somehow this will allow them to create something.
With what components exactly?
Things like materials and transforms, are these considered part of the original instance or not? If we create similar components how do we attach them to the node?
What about other task, like "follow target" or "shoot at target" should these be added into the class or attached somehow? Since Godot never made a proper Include or Import functions for Godot does that mean it was never meant to be used that way?
We know we can't have everything, so the question is what do we have? How was it design to work?
@MysteryGM Loose and disconnected analogies are not helpful.
Things like materials and transforms
Transform is a Spatial property and Materials are a Mesh property. MeshInstance is built on top of Spatial so it inherits all of Spatial's properties. It's a simple hierarchy that makes sense to me.
If we create similar components how do we attach them to the node?
Why would you create your own Mesh system when Godot already has them?
What about other task, like "follow target" or "shoot at target"
Code goes in scripts. You can put it in the node's main script or give it a child with a script if you believe it would be more useful to do it that way.
Why would you create your own Mesh system when Godot already has them?
Because game objects are just as complex as a mesh if note more so. Remember that engines where part of games original, it was just the part re-used.
If Godot can't create a Mesh class inside the engine effectively, then it can't be used to make complex games. All of the games will have to be less complex than the engine. This in turn means that it is of no use to a professional team of developers. Unless such a team only uses C++ with the source code.
Code goes in scripts.
But in what way? I already know I can't make a child for every script because I will need more than a million functions and even a million empty nodes is too much for Godot. What does that mean, how many functions should be grouped together or should I spend 2-4 months planing my object inheritance on paper?
What I want to know is how does Godot deal with large games with complex systems like weather effects, volumetrical fog and large terrains?
The following relates to me, my opinions and experience, YMMV
Every engine has it's quirks, and Godot is no different. If I am being honest, given how long it has been since its first stable release, I am definitely gobsmacked on how good it is and how fast it evolved. That may be a reason for some of the problems we have. The scene/node system is my favorite part, the simplicity of scripted nodes is really what shines for me. Even though it has its downs for me, I dislike python-like syntaxes... and the preload system... couldn't figure out how to get C# working.
Taking Unity 4/5 as an example (to which I was chained for years), it has components so bloated that it can't be compared to any other ECS I've ever used. And oh, you want to make an http request? Sure, add a new component to a new object to your scene structure because it makes a lot of sense to couple http requests with in-scene objects and its components. The "save a scene with no changes breaks all UUIDs" feature seemed to be coined just to make it harder for our non-techy designers to use version control. I don't even want to get started on "Don't Destroy On Load" and prefabs.
What was the rationale between most of that stuff anyway? I'd say it is probably organic, iterative development, and some hacky workarounds here and there (also found in Godot and probably all other game engines). I mean, it crashed so much that my Swift Keyboard started predicting the words "Unity crashing" every time I typed "With". That was a major part of my workflow, crash -> reimport all assets for a minute, work for 30', repeat.
I have seen so much blasphemous monobehavior usage, but who can I blame? The engine does allow people to put a GameState "component" inside a pointlight object. Should we forbid it? How do I argue that the game state data shouldn't even be inside a scene object's component when http communication (or virtually any async work) needs one?
That takes me to my point, comparing engines in their different development stages is hard, and indeed Godot is evolving fast and adding/removing features. That does mean our workflow will have to change and adapt accordingly. I have a decent 2D workflow with Godot, it is definitely better than the one I had with Unity. Yes, best practices are cloudy. Yes, if you ask three developers you'll have three different workflows. But I can't say that is a new problem for me, or at the very least it is one present on both the compared engines.
I haven't really used Unreal (or any other relevant game engine) for more than prototypes and test projects, can't compare with reasonable depth.
But in what way? I already know I can't make a child for every script because I will need more than a million functions and even a million empty nodes is too much for Godot. What does that mean, how many functions should be grouped together or should I spend 2-4 months planing my object inheritance on paper?
Put all the functions in one script perhaps? Godot can be used object oriented pretty well, so if you ever used OOP you can use script or scene inheritance, create classes and so on.
What I want to know is how does Godot deal with large games with complex systems like weather effects, volumetrical fog and large terrains?
Weather effects? Create a scene containing a particle node, a sun and a world environment and attach a script to the root. This script can control the weather. You could also create an empty node and use it as a state machine, attaching empty nodes for each state you have (like rain, sunshine etc). Now just put the code for each state in their nodes. This state machine can be controlled by the root script. This is a very easy to read way of doing things.
Volumentrical fog? I think I have seen this in the new FPS Demo.
Large terrains? I had a project with procedural terrain meshes once and it was running pretty smooth on my a couple of years old laptop.
These are just some functionalities rather than a 'big issue' you where talking about in the OP though.
Weather effects? Create a scene containing a particle node...
Well we plan on being a bit more ambitious than that :) with a region map to tell both the AI where to walk and then using a other channel for rain exclusion zones while keeping the last channel for depth so that snow can build over time.
Large terrains?
Space large. We need some way to reset scenes to the Vector3(0,0,0) without it freezing because too many nodes need to be removed. Physics and everything shakes with large floats.
it has components so bloated that it can't be compared to any other ECS I've ever used.
Agreed, components can be awful that is why it wouldn't surprise us if Godot has a different strategy in mind. However we have no idea what strategy Godot planed or if it even did.
2 months of learning Godot has only thought us how to make small games. With 14 days left to decide on using Godot, I am hoping the developers of the engine can explain how it is suppose to work on large projects.
The last thing we want to do is fight uphill against the engines design while we try to develop the game.
@MysteryGM I really don't understand why you think Godot isn't usable for big projects. Its design really lends itself for big projects because you can split everything into separate scenes and use OOP to reuse work. If your problem with using Godot for big project is that you don't know how to achieve a goal, just ask on the Godot Discords help channels, you will get a very immediate response.
Physics and everything shakes with large floats.
See #288
However we have no idea what strategy Godot planed or if it even did.
I do understand it is kind of alien at first, but to me node+script usage it is pretty straightforward. I would definitely recommend taking a look at (at least) the hobby version of this course here: https://gumroad.com/gdquest It uses a node script structure very similar to mine, and can definitely give you a better insight on how those really involved with the engine use it; the author is a major contributor to godot docs.
edit: Actually, you can take a look at the source in the repository, if you don't want the course https://github.com/GDquest/make-pro-2d-games-with-godot
With 14 days left to decide on using Godot, I am hoping the developers of the engine can explain how it is suppose to work on large projects.
I guess I'm just a little confused on what exactly you mean by this. You used the hammer analogy earlier as an example, but it's a flawed analogy since a hammer is generally a single-use basic utilitarian tool, and there is a intrinsic understanding of what the tool is used for due to its affordance.
Furthermore, while you have listed all of the problems with all of these above systems, I think that the reality of any big software project is that each architecture 'choice' you make will lead to some disadvantages down the road, which you just have to work more with along the way. So are you looking for a silver bullet suggestion? Because frankly, I don't even think most Unity or Unreal projects follow the exact 'recommended' structure and often fork the engine to suit whatever workflow works best for them. I'd even go as far to argue that every game needs to have its own unique idea on node architecture. (A simulation game would have a very different structure than a third person shooter, for instance.)
I guess an argument could be made for more documentation on examples of project structure, but I think that's just a matter of more contributions to the documentation.
separate scenes and use OOP to reuse work.
Like I mentioned before, moving objects around after signals have been attached is a pain. Especially when they stop working without ever giving a exception or warning. We decided to go with a 3rd party refactoring tool, after a other developer mentioned it; but even so it can't solve Godot's unique problems. Not moving things results in this problem (images from our "problem board"): So to fix this we made child nodes. Except at around 50 000 it starts causing problems even they are just Nodes.
What we want to know is how do we work with these problems, how did Godot plan to deal with these; we tested every workflow I mentioned above.
See #288
Thanks for that, we are so use to resting world coordinate, we never checked this.
I do understand it is kind of alien at first, but to me node+script usage it is pretty straightforward.
The problem is not that it is alien, the problem is that it causes a different problem. Mainly that adding more children eventually lags the game or freezes it when we try to unload them. Even when they are just Nodes.
Why do you need 50,000 objects in your game? Surely this would lag ANY engine.
Why do you need 50,000 objects in your game?
Because we use nodes to add extra scripts to the objects. Because Godot's UI tools use containers in containers in containers... to scale things at real time. Our smallest UI is over 800 objects. Most of these containers.
We have 50 000 objects because a single enemy AI is +/-8 nodes with a purpose and +/- 64 nodes that are only there so we can attach scripts to them. Because by splitting each task into it's own code we can see where our problems are. Because there is no other way to quickly find a specific part of the game.
In that case, it seems like you have breached into the zone where Custom Modules are a good option. http://docs.godotengine.org/en/3.0/development/cpp/custom_modules_in_cpp.html You can create custom nodes there, and avoid a lot of nesting.
edit: Though I'd argue that 800 objects for one (smallest?) ui points towards bad design/architecture.
I seriously fail to understand how you can have anywhere close to 64 scripts for a single object. This sounds more of a design problem of your project than a Godot problem (again, likely to lag any engine). Consolidate your code into fewer scripts. Don't make a script for each piece of functionality, but instead for each type of object (If an "Enemy" node can do XYZ then add that part into the Enemy script instead of adding child nodes for X and Y and Z functionality). If you need more advanced features to do this, perhaps you could make use of custom inheritance and/or C# interfaces.
In that case, it seems like you have breached into the zone where Custom Modules are a good option.
See this was one of the things we are wondering about, because all of us know C++ if Custom Modulse is needed we would instead just edit the Godot engine. We know that we can safely use #include where the Godot preload() is questionable; no one can tell us how stable it is or if there is any downsides.
I seriously fail to understand how you can have anywhere close to 64 scripts for a single object.
Simple, every AI can do more or less 64 tasks, not minor tasks either. A Script for example is SquadFlightPatters and in here is all the flight formations all 20 of them, then there is SquadFlightPattersCosmetic than only resolves animations, sound and particles related to flight patterns.
Our rule at the moment is to divide user feedback from the mechanic.
If an "Enemy" node can do XYZ then add that part into the Enemy script instead of adding child nodes for X and Y and Z functionality
If you look at the images and Pseudo Unreal, aka controllers and intermediate.
because all of us know C++ if Custom Modulse is needed we would instead just edit the Godot engine.
I find creating modules much simpler than changing the engine itself, as it builds on top of it, instead of into it.
Still can't imagine this scaling to such high numbers though, especially UI with hundreds or thousands of nodes per widget.
In response to the referenced Issues in OP:
- add proper dependency manangement with system wide installs of assets. Move addons out of the project itself with a way to bring them back for modification
- make it easier to extend custom nodes, allow cross-language script inheritance
- allow scripts to be referenced by class name and namespace to avoid super long paths in case of assets.
Script classes solve the last problem. Namespaces can be created by creating a toplevel script class with constants that serve as aliases for other scripts. The changes I've made to the editor's scene dock in 3.1 also make it MUCH easier to extend custom scripts now. As for cross-language inheritance, that will never formally be implemented, but if MultiScripts are ever re-introduced, then those could fake it reasonably well.
Now, since MultiScript essentially supports ANY script that is extending the base engine type (so you could have multiple Node-extending scripts from different languages that all do different things, but aren't related to each other), it actually functions almost more like a cross-language trait system...sort of. I do think re-adding MultiScript would be pretty cool though
Many of the issues raised in the OP revolve around there being multiple potential ways to address a problem, each of which may have various inefficiencies. Usually, when there is a particular way of doing something that would be most optimal, the engine provides its own implementation in the main C++ engine (no need for scripts or plugins) and that's the suggested usage.
If such a class exists and people aren't using it, then that's a sign that 1) they aren't properly checking the documentation to see if their needs are already met by the engine / how the engine suggests it be used or 2) the engine / organization isn't doing a good enough job of making the documentation highly visible or accessible to users (since they aren't finding the resources that exist).
The only thing to be done to fix problem 1 is for those people to vocalize their problem and for someone else to redirect them to the docs or Q&A site to help them find their solution. I've created an Issue (#23043) that suggests letting people browse and use the Q&A site directly from the editor which should help a lot with problem 2. Reasons are described in the link.
(Edit: if there are many ways to solve a problem, and one of those ways is far better than any other, and Godot doesn't yet implement its own native solution for it, then that would be something to suggest as a new feature for the engine. This is kinda how we got the SimplexNoise stuff added since people were writing their own SimplexNoise algorithms in GDScript / shader code, etc.).
Based on the "problem board" you depicted, the issue you were having is that multiple inheritance isn't supported (nor a good idea), so you went with component nodes that grant functionality to their parent (which sounds reasonable to me). If you are running into issues with your game lagging because it is simply creating too many nodes, then it seems to me like there'd more than likely be a problem with your general approach (which is what you seem to be recognizing and seeking help for in identifying here).
As for your examples of workflows in OP...
Unity approach, child nodes:
It does make sense to use child nodes, assuming that one writes pairing logic. This is ideally where a trait system would be useful (so that one can write the pairing logic once and just replicate it everywhere as needed). I often write components in ways where they automatically set themselves as "targets" for their parents when they are parented/unparented and auto-connect/disconnect relevant signals. You can use the NOTIFICATION_(UN)PARENTED notifications in _notification(what)
for this.
UE4 approach with controllers:
Godot 3.1 allows you to assign script classes for scripts, so there is no need to preload them anymore. In this case, Godot DOES allow you to assign typenames to nodes, but it is specific to the script, so you have to be careful. Scenes don't get the same treatment, and probably won't since it would involve adding a bunch of stuff to the engine core (which core devs can get pretty touchy about).
I'm also not sure how you are getting so much duplicate code? If multiple classes require the same logic, then you bundle that logic into a single Node and then reproduce that Node under the classes that need it, the same way you would with Unreal (components, child Actors), Unity (MonoBehaviours or child GameObjects), or pretty much any other engine that shift dependencies onto an owned object to reduce code re-use.
Panda3D approach with preloads:
I could understand there being a lot of problems with this approach since NodeRefs aren't yet a thing, and there are problems with exporting Resource scripts at the moment which I'm hoping to fix for 3.2 (although you could probably make the fix yourself easily enough, it's pretty small).
WTF script approach:
I don't really understand all the problems you are having here, probably because we don't have a lot of context.
Conflicts with instance system. Because instances flow down while signal connect best up ward.
When would you want to have a parent's signal trigger behaviour in a descendant? Why not just have the parent directly get and call the descendant and its method? There's no danger of the parent seeing its child. It's like saying a C++ object shouldn't be able to access its struct or class member variables(?).
Small pointless scripts just loitering around. All they do is send a signal and track a single value.
This is already a sign that the project's design isn't good. It's the problem of having every singular interaction in the game be tracked by its own personal object. This isn't really ideal, from any perspective.
Moving a object with so many signals attached is frustrating in the least.
In what way? I've generally found that I'm either hardcoding the connections in the scene file or algorithmically connecting and disconnecting signals for nodes if they are intended to be moved around. That has served my purposes enough, but if you have specific problems, sharing them can help others to help you.
Signals and Groups allow for whole parts of the game to just fail, without as much as a warning.
Thus is the problem with any boolean via existence design. The alternative is to loop through ALL object, check if they match the criteria, and then do the logic if they match and, if they match, but not properly, then throw an error. In the case of signals and groups, behaviors are triggered on everything within the set automatically, so if things fall out of the set, there's no way to detect that a problem has occurred during the time of execution. The only way to find problems of this sort is during the setup processes where the groups/signals are being arranged in the first place.
Anyway, that was all my experience with the stuff. It would help a lot more for us to know more about your project and what all it is actually doing so that we can give suggestions for your particular use-case.
Either way, the problems that you've raised here don't appear to be "new" problems with Godot Engine's design or implementation per se. Godot gives people an API and how people use that API is up to them. If you have an idea of how a particular feature should be made more integrated / available in Godot (for example, re-vitalizing MultiScript), then that would be a fine proposal.
As it stands though, the question here seems to be more of a "what's the best way to design a system like X" which...isn't really a GitHub issue. Seems more appropriate in the Q&A site for a "best practices" sort of question. That, or it's a suggestion that we should provide an avenue of making this sort of use-case-specific information more clear / publicly available, which is exactly what my Q&A Issue is about.
Script classes solve the last problem.
First thanks for the long reply, that is a lot and clearly took some time to write. I am pleased to hear about script classes, we didn't use 3.1; only to run the FPS demo.
but if MultiScripts are ever re-introduced, then those could fake it reasonably well. It is completely understandable if Godot never implements MultiScripts. In truth I was just pointing out that a lot of the users on GitHub looked for similar workflow solutions as we tried.
When would you want to have a parent's signal trigger behaviour in a descendant? Why not just have the parent directly get and call the descendant and its method?
I feel like I should explain this. At the time we had very large scripts, each having roughly +/- 30 functions. For that reason we wanted to avoid adding the extra signal functions to the already large script. The result was that we created loose scripts to offload the work from the large script.
This is already a sign that the project's design isn't good.
That is indeed the root of the problem. Mainly that we tried lots of designs recommended by the Godot community and some from tutorials. 1.) At first we had insane inheritances, quickly abandoned when it became clear that Saving Branch Scene or moving a object into a other scene disconnected signals. 2.) We followed this with large scripts. Except the abstract names of these scripts to try and explain what each does, made it difficult to find problems. We knew what the names of the functions causing problems where, but not exactly where they where located. 3.) Then by reading the manual we found the child node approach. It was maybe too tempting because it allowed for direct nodes and we could see the structure in the editor; also filter it to find the exact script we wanted.
Ending up with me here, and I think the last reply addressed a lot of what I was wondering about; I will have to double check tomorrow as it is 2AM over here.
"what's the best way to design a system like X" which...isn't really a GitHub issue.
I want to point out that we exhausted most of our other options. Each of us asked around on the Q&A forums, we made friends in the community and collected examples from them. Scanned the documents looking for explanations. All of the workflows I described is things we tried explained to us by Godot users, who use these workflows. Each would work in the engines mentioned, they just didn't work that well in Godot.
Only the last workflow sort of worked, except it had our main programmer banging his head against the table and miming strangling someone.
Sorry to read you're having issues finding solutions to your problems. Will already gave you a long answer so I'll try not to clutter the conversation too much but at this point, it sounds like you'd need more of a consultant to help you solve the specific challenges that arose from your needs.
Mainly that we tried lots of designs recommended by the Godot community and some from tutorials
Most of us in the community work in projects of a relatively small scale. I use a lot of nodes to help get the point and structure of a given system across in tutorials. This will scale fine for most people. But what works for a small or a mid-size game doesn't apply to larger ones.
Following on what @willnationsdev said earlier, it might be worth considering back-end code to help the game scale, both performance-wise and just to have less code to manage in the editor itself. I can't help a lot besides that as, like many here, I don't have experience with larger projects. Good luck!
Hi, we just have one more question.
If we have two unrelated objects, like a building and water, but we want the scripts of these objects to interact, how do we do this without nodes or preload? The water is a texture so it can't collide with the building either.
@MysteryGM Are you saying you don't want nodes representing the building and water, or you just don't want scripts attached to them? There's nothing wrong with having data for processing that doesn't exist in some way within the scene tree aside from a single data containing node.
If you want to take the ECS approach, instead of attaching scripts to every instance of a type and having them process individually, you can have a single "system" node that'll iterate through and perform processing on the nodes according to a type/group you assigned them. If you want to avoid attaching scripts to nodes for their individual detailed information, you could also store that info in the system depending on how temporal. This keeps your nodes lighter weight, and can be more efficient for you when operating on thousands of entities of the same type.
With having 50.000 of anything you have long exceeded the point where Godot's SceneTree-and-nodes object-based metaphor is useful.
That isn't much different from other engines, with Unity you'd reach similar limits with their game objects, GC, or with UE4 you'd find problems scaling blueprints that far - but above all, having that amount of anything means the default visual editors are simply no longer useful, and the provided tools start to break or at least perform badly.
In all engines, the solution is largely the same: build your own solutions. What those are depends more on your game, architecture and the details than the engine.
Manager classes, pub-sub communication buses, message brokers, instancing managers similar to what is done for particles, object pools, custom shaders to offload work to the GPU, compute shaders, moving processing out to threads or other processes/servers - there are tons of building blocks for possible solutions, and which ones you'll need to combine depend on your specific problem set.
Unity makes some of this easier with their new ECS infrastructure. Not sure about UE4. At the end, at this advanced level, you'll have to do most of the heavy lifting yourself, however (or hire someone experienced to do it for you).
UE4 and Unity only are easier in so far as that they are older, more established, and thus there is a larger number of people having run into such scaling problems, which means more knowledge (and hireable talent) available for solving it.
But really, at the end, no engine will carry you that far. Every ambitious game will run into limitations in any engine. I know of scores of examples for mid to large games in UE or Unity that needed to solve similar problems as well. Either solve them or scale back your scope.
What Godot game developers needs from the Engine developers is a clear deceleration of what the official workflow is for making complex objects and systems.
That depends largely on your game. There isn't a straight answer for this in any large engine. Should you use the gameobjects-scene-based approach in Unity? Or the hybrid ECS? The full new ECS? Which render pipeline do you use? Will you go with Blueprint or C++ in your UE4 project?
All engines offer tons of choices, tons of worklows and a lot of freedom, because there isn't a one size fits all solution.
So if you need concrete advice on how to approach your specific game in Godot, how to structure it, what architecture to use, you'd need to provide way more context and information.
Tell us what you want to accomplish, and maybe someone can chime in with advice. If not, the only way may be to hire an experienced Godot dev as a consultant.
I can only agree with Groud:
All solutions have tradeoffs so there is no point is making an "official" workflow.
@mhilbrunner Just playing devils advocate here but a better engine design (like proper ECS in the vats majority of cases where performance matters) does make many of those limits significantly higher than almost any game would actually ever experience (this is how the new Rust Amethyst game engine is building up to focus on for example).
But yep, I've done an ECS'ish thing in Godot (2) and it worked a lot better than the scenetree instancing style in terms of efficiency and distibution of functionality. It can be done, just a bit painful since Godot lacks some useful primitives for it.
@OvermindDL1
I can only speak for myself, not other Godot devs, and I only have experience with UE, Unity and Godot, so I don't know about Rust Amethyst.
It can be done, just a bit painful since Godot lacks some useful primitives for it.
Having a discussion about where it would make sense to add such primitives / how Godot can accommodate such use cases better would be very valuable I think. Maybe not in this issue :)
But we should gather this feedback some way.
And I agree that there are different limits depending on the engine, but again, I've seen people hit similar barriers in Unity and UE4 as well. Maybe later, maybe those were higher - but at the end, you'll need to be able to solve problems you run into in any engine. :)
@MysteryGM Are you saying you don't want nodes representing the building and water, or you just don't want scripts attached to them?
I already have a script attached to the water object that feeds the water shader. So I can no longer attach a script to the water node. I have a script attached to the building that controls it's resource production and connects things like doors to it. So it also has a script that I don't want to add more code into.
With both objects already having code, what do I do now? I can't add more nodes, and it is still unclear if preload is a possible solution.
My question if I phrase it as a OOP design is: How does two scripts access each others interfaces?
The problem is that GDscript doesn't look like it has any way to access other scripts directly. If I want Fire to burn Wood, I either have to have Wood->extend->Fire or Fire->extend->Wood.
@MysteryGM: One tip: signals. Fire can have a signal burn(target) where target is your Wood.
How does two scripts access each others interfaces?
$NodeWithScript.do_function()
?
/me is still playing devils advocate, do not take this as any measure of what 'should' be done in Godot, at best it's bikeshedding
I can only speak for myself, not other Godot devs, and I only have experience with UE, Unity and Godot, so I don't know about Rust Amethyst.
UE uses an Actor SceneTree model. Unity used an EC model (though recently they've been building an ECS and deprecating the old EC model, it's not quite complete yet but it looks promising). And Godot uses a Component SceneTree model. ECS is a subset of the dataflow pattern (specifically it is a generalization of it).
Quick summary on ECS:
An 'Entity' is just an integer ID, though most engines pack a generation and entity index into a single int32 (8 bits generation and 24 bits index) or int64 (32 bits each, int64 is significantly more common as ECS actually allows you to scale to such significant quantities of entities).
A 'Component' is a POD structure in C/C++ terminology, it absolutely should not ever contain functionality. In generally it should be trivially memcpy'able.
A Component 'Table' is the storage for components, at it's most basic it can just be a resizeable array, but in general a few types tend to be used:
A 'System' is at it's most basic a function that takes a joined set of components, though most libraries have it be a non-virtual class or module or so, it operates over that joined set of components to 'do' something, which could involved changing values in components, adding or removing components, etc... Some examples:
Internally you can think of an ECS as a database where you can subscribe to set mappings of data, though if well made they are extremely well optimized. With knowledge of which systems should run before which other systems and which systems operate over non-overlapping (dependent on read or read/write) then you can trivially multi-thread the calls as well. (I keep using Amethyst and it's ECS specific library part called specs
as an example because I had a hand in its creation from porting my old C++ ECS library) The spec Book details a lot about 'Why' ECS, 'How', efficiency, uses, etc... Amethyst goes in to even further detail especially with how ECS's should be used with the general game systems, rendering, etc...
If you want to see a well written C++ ECS library then look at https://github.com/skypjack/entt as from all the C++ ECS libaries I've seen that I didn't make, it seems to be the best made overall (though specs still outperforms it in my simple tests, though not by much, which is impressive considering specs outperforms about everything I've ever compared it to by significant margins).
The general 'usage' of ECS is you have an 'Entity' (just an integer, no data), and you have the set of Tables that map components to entities, and the systems to actually perform work. In GDScript pseudo-code I'd imagine it to be a simple API, though with a bit of an odd convention to 'integrating' into the Godot scenetree since the scenetree wasn't designed with it in mind.
# All of this would generally be wrapped up in one or more function calls for easy
# building.
# Create a new entity
var e = createEntity()
# Map it to some Components, starting with a godot scenetree component
# I'm using long names to be descriptive, but eh...
var eNode = scenetreeComponentTable.set(e, SomeNodeType())
# Generally things like the transformation matrix and so forth would be their own
# components, but since the godot scenetree already bakes all that in then just
# deal with it all via the node type, you definitely lose a little efficiency doing
# it this way though since nodes involve multiple virtual calls to access and
# the usual ECS patterns entirely get rid of virtual calls with potentially only
# the occasional indirect calls inside a table (maps, etc...)
# 8x4 inventory slots
inventoryComponentTable.set(e, 8, 4)
# 1 fuel slot, accepts items that have the "burn" tag
fuelStorageComponentTable.set(e, 1, "burn")
# Give this entity the furnace component that uses the "basicFurnaceRecipes" recipes
furnaceComponentTable(e, "basicFurnaceRecipes")
# Add other components
# All of the above component mappings could also be in, say, a JSON file, and
# loaded something as such:
componentTables.map(e, "res://entity_types/basicFurnace.json")
# Or maybe via a preloaded JSON object or so
# Once at startup:
# And you could make a system something like, I'll use a GDScript class for this:
val furnaceCookSystemHandler = systems.register(FurnaceCookSystem.new())
furnaceCookSystemHandler.staticTickRate(0.05) # 20 times a second exactly
# Where the `FurnaceCookSystem` could be something like:
class FurnaceCookSystem:
def _entity_matchers():
furnaceBurnMatcher =
componentTables.matcher().
write(inventoryComponentTable).
write(fuelStorageComponentTable).
writeEither(inventoryChangedComponentTable, furnaceCookingComponentTable)
read(furnaceComponentTable)
return [furnaceBurnMatcher]
# Assume we have matching semantics here to destructure a list, I'm lazy...
def _entities_process([furnaceBurnEntities], delta):
# This function takes a list of entity sets in the same order as the matchers
# returned above.
while entity in furnaceBurnEntities:
val furnace = furnaceComponentTable[entity] # or .get(entity) or whatever API
val cooking = furnaceCookingComponentTable[entity]
if cooking == null or cooking.cookTimeRemaining <= 0:
if cooking != null:
if inventoryComponentTable.put(item) == False: # No space, try later
continue
# Get next item that has a "burnable" tag, if any
val item = inventoryComponentTable[entity].getAndRemoveFirstOf("burnable")
if item == null:
furnaceCookingComponentTable.remove(entity)
else:
cooking = furnaceCookingComponentTable.set(entity, item, furnace.cookTime * getCookTimeModifierOfItem(item))
else:
cooking.cookTimeRemaining -= delta
# ... whatever other functions are useful to the system
# Then of course just with the right components on any entity it will just work
# 'as' a furnace. You can have a whole variety of 'generic' systems that can be
# mixed and match with impunity. If you want a random rock that can be a furnace
# then just add the proper components, or if you want an Ent that can both be
# chopped down like a tree for wood but will also attack the player then just add
# the proper components, etc.... etc....
@MysteryGM: One tip: signals. Fire can have a signal burn(target) where target is your Wood.
Sorry I meant scripts not instances. I want scripts to interact without needing a node in the Godot tree.
Sorry I meant scripts not instances. I want scripts to interact without needing a node in the Godot tree.
Maybe I'm misunderstanding your issue, but I'm pretty sure you can already do this by creating classes in GDScript. You'd still need an auto load or at least one script on a node to serve as an entry point.
Also, have you tried C#? If you're trying to make a complex system that has scripts both interacting and controlling nodes via something other than attaching scripts to the nodes you'll probably have a much better time with C# than with GDScript for such a system.
@MysteryGM Getting scripts to interact with each other without the use of a node in the scene means you have to get a loaded version of the script resource, at a bare minimum. The only way to do this in 3.0 is to preload
/load
the script by file path. 3.1 adds the "script class" feature for GDScript and NativeScript where the names given to the scripts are registered globally. GDScript than manually converts those global registrations in the engine to global variables in the language.
Then, if you have the Script
resource, you can call static methods on the script or call .new()
to get a ScriptInstance
and then call member functions / access member variables. If the Script
is deriving Object, Reference, Resource, or another non-Node type, then simply creating an instance of the Script
/ScriptInstance
will be all you need to get the static/non-static content. If it's a Node, then the same applies except for things that require SceneTree access (like get_node
) in which case you'll have to use add_child
to add it to the SceneTree first.
All of the above applies to any scripting language used with Godot (although, preload is an operation specific to certain languages I believe...).
the script by file path. 3.1 adds the "script class" feature for GDScript
When will 3.1 be official released?
@MysteryGM When the milestone percentage completion is high enough / when the core devs have judged the progress to be far enough along that they consider the build to be "stable".
Milestone: https://github.com/godotengine/godot/milestone/7
For comparison, 2.1 and 3.0 are currently sitting at 99% completion. Not quite sure if that's the status they were at when they went live though.
Basically, the answer is "when it's ready".
Thanks for the responses and we apologize for taking up your time. It looks like Godot doesn't fit our needs at the time. Maybe in the future we will get a chance to use the engine again.
If I want Fire to burn Wood, I either have to have Wood->extend->Fire or Fire->extend->Wood.
@MysteryGM See here. The solution (no matter engine or language) might be to have a "system" script which controls interactions between fires and woods. That presentation focuses on components, but one can make a system working with objects too.
In C# I could say that the Wood
implements IBurnable
which Fire
uses.
Hi, thanks for the continued concern and we are really grateful for @willnationsdev support. We believe that the scripts as export resources solution has a lot of potential. As mentioned in #22660.
Because we still have 10 days to decide what engine to use, we have been trying some more with Godot. We have been looking at the workflow problems and I believe our programmer said it best:
"The Godot developers keep every Issue in Github on it's own post, always open one issue for one bug, but do not allow the same workflow for their engine." The fact that we have to make a script AND we are forced to add it to the hierarchy.
"system" script which controls interactions between fires and woods. That presentation focuses on components
Yes that is what we want, how do we do it in GDscript?
In C# I could say that the Wood implements IBurnable which Fire uses.
Godot has Duck typing to do the same thing as Interfaces. What we want is for Burning to be a separate class so we can re-use it, without forcing us to add a node to the hierarchy.
What we want is Fire, Burning, Wood. We do not want Fire (Wood+Burning). The advantage of this workflow is that we can debug Burning on it's own, without the need to fix Every object that can burn.
In C# we will just declare Burning as it's own class. Public class Burning and our problem is solved. In C++ we would Include Burning and problem solved.
Yes that is what we want, how do we do it in GDscript?
@MysteryGM
Without diving into performance optimizations, all I'm suggesting is to move interaction resolving to a separate system script (not in fire, not in wood). The system in simplest case can hold arrays of all objects in the world and handle their interactions. In this case, objects don't have to care about types, only the system cares.
To make things faster, one might only perform checks for objects that actually collided (for example, by sending a signal
from object to the system when the collision occurs).
Some GDScript-specific examples for script cross-referencing (for version 3.0):
Here Enemy
acts as the type/interface, one can check for it with is
operator:
# Cache the enemy class.
const Enemy = preload("enemy.gd")
# Use 'is' to check inheritance.
if (entity is Enemy):
entity.apply_damage()
Here runnerGame
points to an autoload scene (aka singleton) with path /root/runner:
onready var runnerGame = get_node("/root/runner")
onready var runnerScript = preload("res://runner/runner.gd")
func _ready():
runnerGame.setMode(runnerScript.SPAWN_TITLE)
Here we connect the signal
"pressed" of the button to the handler (which could be in a different object and script, we are using self
to refer to the same one):
func _button_pressed(which):
print("Button was pressed: ", which.get_name())
func _ready():
for b in get_node("buttons").get_children():
b.connect("pressed", self, "_button_pressed",[b])
See also the docs here, there are some more features available: http://docs.godotengine.org/en/3.0/getting_started/scripting/gdscript/gdscript_basics.html#inheritance
See also the docs here, there are some more features available:
Thanks for your input in the topic. The problem with inheritance is that it only works if we know all the relationships of classes when we implement them.
Here is an example:
Step A. MeshInstance+ HomingScript = HomingMissile. But now if I want a Homing Landmine I have a problem. (MeshInstance+ ProximityScript) = Landmine. (MeshInstance+ ProximityScript) + HomingScript = ERROR more than one script (MeshInstance+ ProximityScript) + (childNode + HomingScript ) = FAILED as only the invisible node moves. (MeshInstance+ ProximityScript) + (childNode + ExtendedHomingScript ) = SUCCESS as we now extend the Homing class so it can posses nodes from the child node. We get HomingLandmine.
Except now it can be argued that both Homing Missile and Landmine should extend from a HomingClass.
StepB. HomingMissile[from Homing] HomingLandmine[from Homing] [+ ProximityScript] The script is now copy and pasted into the landmine.
Later in production the same thing happens with other scripts we have. StepC. Landmine[from Proximity] ProximityAlarm[from Proximity] [+ AlarmScript] // HomingLandmine[from Proximity] [+ HommingScript] // Now our landmine can also fit here.
So we have to keep going through StepA to find the proper inheritance for StepB. It keeps repeating this pattern going beyond StepF etc.
We found a way to prolong StepA, We just avoid adding a script to the top node, and always make that a Spatial or some primary node. (Spatial + MeshInstance+ ProximityScript) + HomingScript = HomingLandmine Except this is just the same problem but with a more expensive ParentSpatial; instead of childNode.
I apologize for how long this is.
@MysteryGM
"The Godot developers keep every Issue in Github on it's own post, always open one issue for one bug, but do not allow the same workflow for their engine." The fact that we have to make a script AND we are forced to add it to the hierarchy. ... What we want is Fire, Burning, Wood. We do not want Fire (Wood+Burning). The advantage of this workflow is that we can debug Burning on it's own, without the need to fix Every object that can burn. ... In C# we will just declare Burning as it's own class. Public class Burning and our problem is solved. In C++ we would Include Burning and problem solved.
What you have to understand is that Godot functions no differently in this case. While the go-to solution is to make a sub-Node with its own script (for "Burning" in this case), you can just define Burning as its own class and include/use it via static functions (or instance it and then use properties/methods)
For a Unity/ScriptableObject approach, one can use Godot's Resource scripts for the same purpose.
"system" script which controls interactions between fires and woods. That presentation focuses on components
Yes that is what we want, how do we do it in GDscript?
The easiest way to do this is with an autoload node/scene of some kind. The reason being that even if you only use one node with tons of resources for all of the different systems (to minimize the number of nodes you are using), you still will probably want to have access to a Node that is in the tree so that you get access to delta time, input data, yield operations (esp. combined with signal usage), animation/tween/timer nodes, the list goes on. Furthermore, as an autoload, GDScript again will generate a global variable for the node so that the "systems" can be accessed by name anywhere in your codebase (another suitable replacement for using a script class in this case).
I actually have a large refactoring of an old WIP plugin, godot-skills, that I'd like to do that I believe encompasses this and a lot more. Please read the (sorry, long) reddit thread on the topic. It involves examining the viability of using coordinated multithreading in a server architecture (similar to Godot's underlying architecture) to implement a similarly generic and re-usable interactivity system.
btw, if you ever wanna chat in real time on the topic, feel free to pm me on Reddit/Discord/Twitter where I use the same username.
@MysteryGM
MeshInstance+ HomingScript = HomingMissile. But now if I want a Homing Landmine I have a problem. (MeshInstance+ ProximityScript) = Landmine. (MeshInstance+ ProximityScript) + HomingScript = ERROR more than one script (MeshInstance+ ProximityScript) + (childNode + HomingScript ) = FAILED as only the invisible node moves. (MeshInstance+ ProximityScript) + (childNode + ExtendedHomingScript ) = SUCCESS as we now extend the Homing class so it can posses nodes from the child node. We get HomingLandmine.
Except now it can be argued that both Homing Missile and Landmine should extend from a HomingClass.
I believe I'm missing something here. Afaik, the proper way to make a "component behavior" node is one that does not change itself, but rather directly modifies a "target" node where the default target is the parent. This circumvents the issue you are encountering entirely, if I'm not mistaken.
extends Node
var src_target
export(NodePath) onready var dst_target = get_node(dst_target) if dst_target else null
func _notification(p_what):
match p_what:
NOTIFICATION_PARENTED:
src_target = get_parent()
NOTIFICATION_UNPARENTED:
src_target = null
func _physics_process():
# if necessary, can manually assign src_target elsewhere to be NOT a parent
if src_target and src_target is KinematicBody2D:
src_target.move_and_slide(...) # do logic to get parameters that move towards dst_target
Something like this would mean you don't have to change anything by simply making a node with the script be a child of the thing you want to have the "homing" feature. You just have to assign the exported property value (either in the scene or at runtime) to tell it what to home TO. Other exported properties can be used to modify the type of movement and that stuff could also be outsourced to a Resource if you want to have different "builds". Again, Godot allows you to be very flexible with your game framework's design.
Some corrections:
- C#
- - public class Burning {};
+ - public class Burning {}
- using Game.Burning;
- Burning.apply(wood, fire); // is it :: here? I forget...
- - Burning b = Burning::GetSingleton(); b.apply(wood, fire);
+ - Burning b = Burning.GetSingleton(); b.apply(wood, fire);
Semicolon after the class's closing braces is not needed. You use a dot for static members instead of ::
.
As a side note, the convention for accessing a singleton is to use a property (generally named Instance
) instead of a method.
@neikeq Thanks. I'm way too out of practice with C#, lol.
As for the "Global Scripts" approach, I am very against this. It would encourage devs to code their game in bad ways. A common question of previous bare C++ and Java game devs, when they try Unity/Unreal/Godot/etc, is "where is the main game loop that I can write my code in?". Adding such a system would allow those devs to do this, so they might end up writing code in global scripts that reference the objects they want to modify instead of just using a script on an object.
Perhaps instead we could improve the performance of barebones Node
node so that it doesn't introduce overhead for object "components" that purely hold scripts. Or maybe even a separate simple "only-holds-scripts" node.
@aaronfranke
As for the "Global Scripts" approach, I am very against this. It would encourage devs to code their game in bad ways. A common question of previous bare C++ and Java game devs, when they try Unity/Unreal/Godot/etc, is "where is the main game loop that I can write my code in?".
As I mentioned, if someone were averse to doing this (reasonably so in a large project), then there's no problem with just creating a "namespace" global script class that maintains constants of other scripts to travel further down the API hierarchy. We're talking scripts only here, not actual node references.
Adding such a system would allow those devs to do this, so they might end up writing code in global scripts that reference the objects they want to modify instead of just using a script on an object.
This system is already implementable by anyone in Godot via autoloads (there's no "adding" of a feature being suggested). Furthermore, the problem they are encountering in the first place is that they don't want to add the systems themselves as sub-nodes in the world (though I give a suggestion for that as well in the follow-up comment) since they have to have both the node there AND the script on that node, and potentially refactoring the relationships between all of the nodes and scripts when they later realize things must move around.
Perhaps instead we could improve the performance of barebones Node node so that it doesn't introduce overhead for object "components" that purely hold scripts.
In what way? The only way to do this transparently would be to implement a MultiScript system and there are a whole host of problems that come along with that paradigm. The only other alternative I could come up with when trying to solve that problem was the GDScript trait system I suggested in #23101 which confines the MultiScript issues to one language, but comes with its own host of even more severe problems.
Or maybe even a separate simple "only-holds-scripts" node.
Once we fix the array type hints system in 3.2, we'll be able to do this with just...
extends Node
export(Array, Script) behaviors = []
One could even implement the feature now using a Dictionary (to access the scripts by name) and an EditorInspectorPlugin from 3.1 to define a custom UI when those nodes are opened in the Inspector (create a exported-array-of-scripts UI, but have it add the script with a key that matches the filename under-the-hood).
extends Resource # burning.gd
Ah, I think I found our problem. None of our preload attempts was extending from resource. I don't think any of us even knew we could make custom resources.
I also want to point out that no one who shared their games with us or who described their workflow was extending from resource. Almost everyone was extending Node. No idea how much of a difference this makes, will need to run tests.
FileSystemItemList (warning, it's an early WIP)
Will definitely have to give this a look over.
This is more of a informal topic but it is a drastic issue if not the main Godot issue. No one knows how the developers of the Godot engine, planed on the users using it.
Some related issues: #19486 #7402 #15996 #8502 These are the top 4, but there are lots of similar ones that all aim to solve the same problem: Everyone is looking for a usable workflow.
Amazingly Godot users have managed to create inefficient workflows that copy other familiar engines. To demonstrate this I will use a mock class of what a MeshInstance, if made in Godot would look like.
There is pseudo Unity, aka child nodes:
Godot users use child nodes to create a equivalent to MultiScripts. MeshInstance(Spatail +script) --- Mesh(Mesh +script) --- Material (node +script) - Shader (node +script) - Texture (node +script) --- Transform (Position3D +script) Basically they will use the most appropriate node and code what they need to create what class they want using a script. **Problems:** 1.) It is the digital equivalent of adding cold water to a boiling pot; it helps short term but causes more confusion in larger projects. 2.) Most users end up using signals like events, connecting them only to the node sending them and using **Export** to find the target. 3.) Events can be intercepted before reaching the lower nodes. 4.) Performance is slow and at around 50 000 object it can stall when the game tries to load scenes.
Pseudo Unreal, aka controllers and intermediate:
Godot users make controllers and possessor nodes that manipulate all the children. The trademark of this workflow is the classes derived with extends. MeshWithTexturedMaterial.instanced() *only node in tree -> extends from -> MaterialTextured -> extends from -> Material -> extends from -> Shader This system requires heavy planing, or you end up with similar code in many classes. Like a camera, character and vehicle classes all containing their own copy of "follow target" **Problems:** 1.) Messy large scripts that is a pain to debug. 2.) Duplicate code in different classes. 3.) Godot doesn't treat nodes as classes; forcing these developers to heavily relay on self coded classes. 4.) Extends don't make sense, like a _combat system_ extending a _weapon system_ and finally extending the player.
Pseudo Panda3D, aka Preload(Script):
This one replicates the workflow of how most code based engines work. The developers will often have more scripts than nodes. MeshInstance (Node) Preload (Mesh Script) Preload (Material Script) Preload (Transform Script) Strangely it is these developers who refuse to abandon GDscript, even when C# is clearly better for this approach. That is why I assume they are familiar with Python. **Problems:** 1.) Confusion on how imports should be called, as constants maybe variables with onready; who knows. 2.) Rarely uses Godot nodes, I have even seen developers ignore the UI tools completely. 3.) Pain to debug as there is no indication of where a script is linked.
WTF script:
Left the best for last. Some Godot game developers have managed to make good games using Signals and Groups. MeshInstance (Node) having a empty node on top allows for easy moving and re-design of the children. --- Mesh (Mesh +Script || Signal) --- Material (Sprite +Script || Signal) --- Transform (Position3D +Script || Signal) When the tree has to connect objects 2 scenes or more deep, a group will be used instead. Notable about this workflow is how it has shallow trees. **Problems:** 1.) Signals and Groups allow for whole parts of the game to just fail, without as much as a warning. 2.) Small pointless scripts just loitering around. All they do is send a signal and track a single value. 3.) Moving a object with so many signals attached is frustrating in the least. 4.) Conflicts with instance system. Because instances flow down while signal connect best up ward.
Yes, I know Godot has a MeshInstance class. The point this demonstrates is that most of the workflows used by developers right now can make a class as complex as MeshInstance with multi components; except each workflow is seriously flawed.
All these work flows share the same core problem. They are short term solutions to developing a game. None of them scales well and debug is flawed in all of them.
What Godot game developers needs from the Engine developers is a clear deceleration of what the official workflow is for making complex objects and systems. That way they deviate but always know how it was intended to be used.