I HATE searching for nodes by names. However this particular piece of code grew out of, “I don’t know a better way to do this.” It goes directly to what @normalized was saying. You have to use all the tools in your toolbelt. I used this precisely because it gives me a nice null when it doesn’t find something so I can error out gracefully. I could also iterate over the children of the character and match on type. I just don’t know which one is more performant TBH.
first of all: Thank you for this elaborate response (-:
Have you read our comments, rebuttals, and discussion about that very video in this thread here? Godot Architecture: 7 steps for more flexible, extensible, testable code (video)
I have not. Thank you for linking to it (-:
This is an interesting complaint. Is that because you’re used to multiple inheritance in scripts instead of composition?
That was my first attempt. Basically, in Unity you can attach an unlimited amount of scripts to a game object (or whatever it was called; it’s been some time since I used Unity)
But in Godot you can’t do that. Thats why I had to learn that now everything is a Node in the scene tree. (Ok, almost everything. I have read the docs about when not to use Nodes (-: )
I havent heart of scene inheritance. Thank you for showing this to me I will look into it. It sounds very useful.
You are right, it definitely is oversimplified. If I had actually tried to put everything I learned and encountered in the last 2 years in this post, it would have been much longer. (And it already is pretty long.)
Spoken like a true natural scientist. I am not sure if I agree. I think subjective soft measurements and skills are very important in development. If you want to measure objectively, you oftentimes get either extremely complicated or uselessly simple results.
How do the other objects subscribe to the signals the health object defines?
Signals are great for decoupling, and I use them a lot. But unfortunately, they cannot solve the issue of needing to get a reference to the object that defines the signal.
That totally makes sense. Although I do notice that the getters for the maximum and current are not declared as private. Do you access them from other scripts?
I don’t quite get how making them private or extending them in inheriting scripts helps you with archiving those things. Do you have another script that extends PlayHealth and is also responsible for emitting signals that cause sounds or playing sounds directly?
From how I am understanding your code, Game is an autoload, which defines signals that can be emitted and subscribed to using its name. This is actually exactly the pattern I meant to describe with Pattern 3. (Although perhaps I haven’t been clear enough. I think I confused the terms Singleton and Autoload quite a lot.)
interessting.
that is what I meant to say (-: sry for the confusion
I disagree. While the autoload itself is loaded first, the same cannot be said about the scripts that use them / subscribe and emit signals. Of course if you manage your functions correctly you can avoid this, but it is very easy to mess up. This is why I have been looking for an architectural solution to this problem. Perhaps there is no good one as of right now. Which would be fine and in of itself good to know.
I do agree that with experience this happens a lot less. I still have a lot to learn. I just wish I could spare beginners from some of the pain I had to go trough.
Good to know.
I will. (-: The code you shared across various posts does look pretty cool.
I have. In fact some of my questions have very much been about earlier versions of this exact problem. (Earlier meaning I knew less about them)
However often I did not know what to ask for. This post is a great example for this: https://forum.godotengine.org/t/how-to-do-inheretance-with-different-kinds-of-object/83499
This is one case of me accidentally asking a XY question. (The kind you recently complained about (-: ) What I really wanted to know was how to structure complex systems in Godot and what I asked was how to to complex inheritance, because that was the only thing I knew.
Im sorry for insulting your baby. It would probably be fairer of me to say:
“It is not clear to me what java is good for” rather than:
“Java is good for nothing”
I can access a reference to an object that has not yet been created? Curious. Altough I think that would be a question for a different post.
interesting. I have so far always manually checked for types. But perhaps that is the better approach
I have, and I will continue doing so. This is really good advice, and I learned a lot from gamejam. However, I learned it by doing everything fast and messy. I still think that there are some things I need to learn with projects that go on over longer periods of time.
Finally:
I know. I’m sorry for bothering you so much. But I don’t think any of my words were unimportant, and I have already learned a lot from the replies (-: So thanks once again.
What I tend to believe is that all rules are made to be broken. It is just that you should know why you are breaking them. If you don’t, then you will create your own custom solutions to problems that have already been solved by the tools you are using. I don’t want to learn about architectural patterns in order to always strictly follow them without exceptions. I want to learn about them so I can intentionally decide if I do or do not follow.
Hm. That seems to be a rather radical position to me. Obviously this is theoretically correct. If I want to do something, like, for example, spawn an enemy at a random location on a path, I could do this without using path2D implementing everything from scratch. But it is a good architectural pattern to use Godot internal nodes if they are present and solve my issue. If I only think about the problem domain, path2D is still the better solution because it is simpler (although even that is an architectural idea if you think about it)
But if I know about the pattern of looking for built-in solutions, I am more likely to know about them.
Of course there could be problems where not using the Path2D and doing stuff myself is actually the best solution. But once again, if I know about the architectural pattern, I will look at the Path2D Node first and then actively decide that in my problem space it is best to ignore this pattern instead of just blindly ignoring what’s there and going for the first problem solution I find. Although I am sure that is not what you mean. I think we are getting a bit caught up about terminology.
What you’re describing are not “architecture patterns”. It’s just knowing how the engine works. Architecture is how you combine that to solve problems and build domain specific systems. Architecture is mainly the responsibility of your creativity and problem solving skills.
Just “know how the engine works” is not a very helpful thing to say to a beginner. (Or anyone really) But for example: If you have a parent initializing a child, think carefully if you really want to do this. (Because decoupling is usually desirable)
Is a useful tip. Of course you can still say that you are sure that you want to do that. But the pattern made you think about that. And that (in my experience) drastically improves code quality.
Anyways, if you define an architecture pattern as a set of rules that you never break, that is indeed not what I am describing or talking about.
I am talking about a set of general standards that help you see potential issues with your code and avoid common mistakes.
Your probably right xD.
But that is never what I wanted to do. I think some general rules like “Try to make you skripts simple and short if reasonable”
And suchlike things are maybe not the first thing you should hear while learning, but certaintly also not the last.
I don’t think a list of such “rules” can really be made. You’d likely end up with some vague generalizations of questionable pragmatic value, and we already have plenty of those.
Your example being one of them. What is considered “short” or “reasonable”, who decides that, and why scripts need to be “short”? It may just be your personal opinion or preference. The rule also doesn’t mention that there are negative consequences of keeping scripts short, namely you’ll need to manage and jump around a large number of them. Etc.
We do, and I do believe that we have them for a reason. If you want patterns that are always correct and address every single possibility, you are obviously not going to find anything. But there are many coding patterns and bad smells, and they are there for a good reason (if you ask me). These rules and standards do not speak in absolutes and should not be absolute.
This is therefore indeed up to interpretation. As it should be. A design pattern should forbid you from doing things. It should show you good and bad practices and make it easier for you to identify issues early.
They also have many other advantages, especially if you are working in teams. If you are working with architecture or design patterns, your code will all tend to walk similarly. That’s why we have naming conventions. You usually know where to look for stuff. Of course you don’t have to abide by these rules. But I, personally, believe it would be foolish to just assume that game dev is so chaotic that no useful rules about it can be formulated and no tendencies can be identified.
I am actually not that big of a fan of strict patterns and in uni I was generally pretty annoyed by how strictly we had to follow them. But I dont think they are completely useless or should just be completely ignored.
Yeah but all those things you’re mentioning are largely project-specific. And rightfully so because they stem from problem domains. There’s no much sense in generalizing them above that.
In any case, if you want to put out some Godot rules and standards that you think should be widely announced and universally accepted, you can always do so and see how they fare.
However I’d first battle-test them by making a good game that strictly conforms to them.
I just hope they won’t be some N-th rehash of “clean code”.
sounds reasonable (-:
I also want to say that I don’t disregard your opinion. I am actually really glad to have found someone who disagrees.
Although I don’t want to just define some rules I set on how to correctly do things. I think that would be fundamentally against the open-source spirit. But I would like to find out what the community believes to be good ideas to think about while coding.
That is actually why I made this post and asked.
Except that there are objective measurements for these things. Cognitive complexity can be measured based on the number of loops and branches (ifs) you have in a function. Likewise, by the number of functions you have in a class. SonarQube is an excellent example of a tool that does this. (They just don’t have anything for GDScript.) A tool like this, or a linter like in the GDScript toolkit. (GDLinter uses it when you save your project.)
Those are objective measurements, and following them will help you get to easily readable code objectively.
Depends on the signal. For the Health node tied to the States they need to know about the full object, and we go back to the discussion of classes with protected access - which is an access modifier Godot doesn’t have. No matter where you implement a State Pattern, you’re going to have to know about things outside yourself.
For the UI, it subscribes to a Signal Bus. In this case, Game.
@warning_ignore_start("unused_signal")
signal player_health_changed(new_health: float)
signal player_max_health_changed(new_max_health: float)
signal player_equipped_thrown_weapon(weapon: ThrownWeapon)
signal player_new_item_gained(number_of_items: int)
signal number_of_healing_potions_changed(number_of_potions: int)
@warning_ignore_restore("unused_signal")
I can move the UI around as much as I want and it doesn’t matter. This also helps because I have multiple player objects throughout the game. So whichever one is active reports to the Game and doesn’t worry about what happens after that.
No. But going back to your “easily readable code” argument, the only way to do that is to override the built-in setter with a privately declared one. So I would…
Add more code to do something that is already being done. I have yet to have any reason in GDScript to ever define a getter.
Not actually make it private, as declaring something private is convention over configuration. It’s still publicly accessible in reality.
Be confusing, because it’s not a private variable. All @export variables are public, as they can be set in the Inspector.
I don’t understand the question. What do you mean by archiving?
No. It does all that. Why would I need to extend it again?
You disagree because you got an abstract answer to an abstract problem. TBH, I just got tired of explaining things. This is the problem with you brain-dumping 2 years of thoughts (even if you edited it). I got fatigue in answering.
This is a solvable problem. But I don’t want to play “guess what you implemented”. If you want a concrete solution, create a Help post about the problem you’re having with lots of details.
Pain is how we learn.
Thanks.
LOL I’m not that attached to it. I also love C#, but you’ll see me often tell people not to use it with Godot. It’s all about the right tool for the job. I once coded an entire tool in Java just because it was easier for me to redo it in Java than teach people C#, Ruby, or Python - the other languages I’d made the tool in before.
This is definitely the topic of a help post. Because what makes you think it hasn’t been created? And that’s a completely different question. It goes back to the X-Y problem. Saying “I can’t access Y because it’s not available” is the wrong problem. The right problem is “I’m trying to do X, I tried Y and it’s not working”.
It is. Because the physics system is checking using a bitmask to see if the object is on the player layer. Any coded check you do is on top of that very fast check that is already happening. So that whole type check is literally there only to detect a bug, and with a bug-free game it’s extra processing that never matters.
What I do is create plugins, and refactor and refine those after game jams. You would probably enjoy reading my DevLogs at the top of my Itch page. In which I talk about this topic, and many other things I’ve learned making games. I’m about to start a new game jam tomorrow, and plan to make periodic posts (though maybe not daily). So follow me if you want to follow along.
I have no idea what this was about. Make a new post in Help.
It’s important to note that the setter is private - not the variable itself.
The Inspector whenever you change an exported value.
The load_node() function which is a public function - an abstraction layer - that has access to the private variables.
Any inherited classes, of which PlayerHealth itself is.
friend and protected access modifiers didn’t exist when I started programming, so while they’re nice, I don’t necessarily rely on them unless I’m feeling fancy.
I think you want a cookbook. That’s fine, and afaik there’s plenty of cookbook-ish material sprinkled around tutorials, even some in official documentation. But I don’t think framing recipes as “rules” is a good way to go. Godot is very flexible and can be used in many different ways. Promoting “rules” kinda goes against this. For example, did you know that in Godot, you can legitimately do everything without creating a single node.
You said you made your setters private and separate so you can extend them using inheritance because you want to play a sound and emit signals and save the game under specifc conditions. I do not understand how making your setters inheritable helps with doing any of these things.