No video

Godot 4: how to implement interfaces in GDScript!

  Рет қаралды 26,747

Tutemic

Tutemic

Күн бұрын

Пікірлер: 147
@bocdagla
@bocdagla 6 ай бұрын
Gdscript has its own way of creating contracts wich is using has_method, not having an autocomplete its a bummer but I believe doing some hacks will make your project more cumbersome than it needs to be. Also before working on interfaces you should be static typing everything you can, using interfaces just for the sole purpose of autocomplete doesn't really add much value, this issue can also be solved by adding small changes for every commit and thesting them as we add them (even manually, as long as you test them). It still surprises me that we're trying to move everything to writing our own systems rather than to use the ones we have and just get really experimented with them, using gdscript should be for writing scripts, simple mechanics that can be isolated and work independently and the godot team is working towards that. If we try to add more dependencies we're quickly loosing the advantage of loosely coupled scripts, where they only connect by either nodepaths or signals. The moment we add a framework on top of that just to work with gdscript as if we were working with another language i adds unneeded complexity. Why not use C# right away that its intended to use interfaces? godot allows multilanguage projects
@tutemic
@tutemic 5 ай бұрын
Good points!
@DenerWitt
@DenerWitt Күн бұрын
this^
@spy_brightscale
@spy_brightscale 11 ай бұрын
I implemented an easier solution: using class_name and _init(): Interface: class_name MyInterface (some members) within each script: implements = [MyInterface, MyOtherInterface] func _init(): for interface in implements: if interface != null: for interface_method in interface.get_script_method_list(): assert(interface_method in self.get_script().get_script_method_list(), "your error message") For each interface you need to define an extra script, which is more user friendly to me!
@NatiiixLP
@NatiiixLP 9 күн бұрын
If this gets too complex, and you have too many instances of the class (e.g. projectiles in a bullet-hell game), you could add a static boolean variable, and only check the members once for each class. It's not the cleanest implementation of interfaces, but it's cleaner than hardcoded strings all over your code. 👌
@Rin-qj7zt
@Rin-qj7zt Жыл бұрын
holy shit.. not only are you back.. BUT YOU ARE DOING GODOT TUTORIALS?! this is truly one of the best times. you were such a huge influence on fueling me forward in my effort to learn programming concepts. you are not in the slightest obligated to make content, but i am very thankful that you decided to.
@ShiloBuff
@ShiloBuff 11 ай бұрын
Minor note for anyone watching and struggling with the take damage amount: take_damage function has a hardcoded/magical value of 5. It should be "amount" instead (without quotes).
@darius_defiant
@darius_defiant 10 ай бұрын
Man I know I just commented on your more recent video, but I saw you had more on Godot and I cannot express enough how thankful I am for your content. I don't know how to put my finger on it but you talk about code the way I feel about it, and it reinvigorates my passion and enthusiasm. I love the 'theory' of it - the logic and planning and thinking outside of the box a bit. I really hope you keep making more of these - I've subscribed so I don't miss any!
@stedunn563
@stedunn563 Жыл бұрын
From a c# background wanting to use gdscript, not having interfaces is annoying. Thanks for the video man
@tutemic
@tutemic Жыл бұрын
I went ahead and cleaned up the code from this video and posted a ready-to-use initial version of the interfaces.gd file on GitHub for you to use in your own project! github.com/tutemic/gdscript-interfaces/ The version in the GitHub link solves the undesirable string reference in checking if a node implements an interface (shown in the video) and adds support for implementing multiple interfaces in one script. Try it out!
@yoloyojick
@yoloyojick 7 ай бұрын
When I come to Godot, I also tried to figure out how to do interfaces. But instead of variables and has_method calls, I implemented interfaces through the nodes system. If unit has node “Attackable”, than I attack, for example. In the end we all came into similar solution, I was happy to see that here, it warmed my heart ❤️ .
@charlesabju907
@charlesabju907 7 ай бұрын
Your idea is much easier to implement, does it solve all the problems he pointed in the video? I think yes, you just have to put the logic to check if the parent node has the correct methods, right? Very good, thanks a lot for the idea
@samiyokim
@samiyokim 6 ай бұрын
Sounds you designed composition, over interfaces, which some would argue is better.
@w花b
@w花b 6 ай бұрын
​@@samiyokimYeah that's how Godot works so it makes sense
@HuangShengHong
@HuangShengHong 6 ай бұрын
I also prefer this method, create a abstract class "Interface" and inherit "Interactable", "Damageable"... Put it under any nodes. Create a singleton with method Utility.get_interfaces(Node), which loops through all children of the node and returns all "Interface" class objects. Since any scene prefabs naturally just contains few dozens of children, and you'll never design your code to implement interfaces at your level.tscn (which might have 1000+ nodes), performance won't be an issue.
@tutemic
@tutemic 5 ай бұрын
Thanks for your contribution! I like you're keeping it more within the Godot paradigm. Godot does indeed give us C# support for those who prefer the other oop-ey ways of doing things.
@Nevarek_
@Nevarek_ 11 ай бұрын
Wow dude this is clever. Never thought to use those get method functions to compare abstract classes like that to enforce interfaces. Great advice! Way better than groups or enums. Plus you get all the design pattern benefits from interfaces. Definitely something I will be taking to my future projects. I like that the implementation is actually pretty easy to remember and the recursion is a dead simple dfs tree walk. Plus it's easy for anyone to use.
@kevinscales
@kevinscales 11 ай бұрын
In Interface._ready() put that code within an if statement "if OS.has_feature("debug"):" if you don't want it to run in your release build.
@atlas4247
@atlas4247 Жыл бұрын
Chabane, I'm so glad to see videos from you again. Even though I feel incredibly comfortable using Godot and GDScript, I always learn something new with you.
@soganox
@soganox 7 ай бұрын
Fantastic idea. Very quick to set up, and can definitely save headaches in the long run. Thank you!
@Alicorn_
@Alicorn_ 8 ай бұрын
Good video. I'd love to see more intermediate-level tutorials about architecture and patterns in Godot. Another way to avoid using method string references is to use composition and have a "HealthComponent" Resource that handles the damage attached to the entities , and that way the engine should Intellisense the methods available, and yell at you if something is wrong with your call.
@KyleLuce
@KyleLuce 10 ай бұрын
Geat to see this concept explored :) . A few small things: when you instantiate the interface with new, of the interference doesn't inherit from Reference or its descendants, you'll leak memory. The idea of enforcing contracts is a good one, but I'm thinking this may be a bit over complicated. Why not just create a DamageableInterface with String constant members such as: const TAKE_DAMAGE = "take_damage" # etc - The interface implementor can take care of enforcing the contract - you can enforce on _ready in any class claiming to implement the interface. - A static or Singleton utility can be created, that does the reflection checks given the interface and the implementor object. This checker can be called on _ready from the implementor. This way you know early when any errant implementor object is created, and not only in a specific scenario.
@irascib1e
@irascib1e 7 ай бұрын
I like that you're presenting a pareto principle alternative to the video. I'm sure lots of people will find that valuable. For me personally, "the interface implementor can take care of enforcing the contract" that's the reason I prefer the video's method. I want something decentralized and right at the beginning of the project. The video's method is pretty much identical to a static type system for interfaces in gdscript which is pretty rad
@wreckingballgames
@wreckingballgames Жыл бұрын
My Godot skills are just a bit advanced for a tutorial for absolute beginners, but I was planning to watch this series for your programming insights anyway. I'm going to have to bump that up soon because I recently became obsessed with interfaces in Godot! I'm using C#, but prototyping in GDScript is so nice I just have to see your take on interfaces in GDScript. I'm really glad you're back!
@42ultra
@42ultra 8 ай бұрын
I have been smashing my head into a wall for weeks with every pitfall you discussed in this video, even down to lasers vs. missiles space shooter stuff. Thanks!!
@tarsyth3433
@tarsyth3433 Жыл бұрын
Awesome stuff! Glad to have you back, when you explain best practise and how to write better code, it feels like there's actually a benefit to all of this. It's sometimes hard to understand the underlying reasons why some things in coding are preferrable over others.
@desmondbrown5508
@desmondbrown5508 6 ай бұрын
Well, I'd say in addition to that if performance is/were a concern, you can always get around that by making a check for OS.is_debug_mode() in the _ready() method of Interface singleton class. That way, when you ship to release mode, there's absolutely no checking for interfaces, since you'd already know it worked if the debug code was able to run without crashing. For bigger projects this may not be as easy to test in debug mode overall since it requires the scenes to instantiate in order for script checks to work, but I suppose a gdnative/gdscript plugin tool could also be implemented to ensure it by parsing all scripts to get the real native interface feel.
@Nitbandier
@Nitbandier 11 ай бұрын
Best tutorial i have seen about godot.
@BlazertronGames
@BlazertronGames 6 ай бұрын
Something like this should absolutely be built in. Really sucks when using static typing but not having any nice built-in way of doing interfaces.
@TrashHeap64
@TrashHeap64 5 ай бұрын
Wild how involved the process is to build "interfaces" in GDScript, but good to know you can do it. Here's hoping they actually build it in at some point. Its rough out there without them :(
@ShiloBuff
@ShiloBuff 11 ай бұрын
Loving your videos, hope to see more advanced videos. Also find it interesting that you call things "magical" that I refer to as "hardcoded".
@ThomasBCharlie
@ThomasBCharlie 4 ай бұрын
it's another way to call hardcoded constants, it's a "magical" number cause it comes out of nowhere 😉
@ShiloBuff
@ShiloBuff 11 ай бұрын
22:00 That bug/error is a nightmare. Those types of errors can easily takes hours to fix. That's rediculious that the error didn't even point to the proper issue and also rediculious that @export has to be above a non-export. Glad you ran into that issue because I might have done the same in the future and been lost.
@jRsqILVOY
@jRsqILVOY 10 ай бұрын
It should really run a linter / formatter for stuff like that :/
@bill_makes_games
@bill_makes_games 3 ай бұрын
I am at version 4.1.3 and this error doesn't occur ( no problem with "@export var [...]" being after "var implements = [...]")
@stevencoghill4323
@stevencoghill4323 10 ай бұрын
Great tutorials. I went ahead and added multiple player lives, and multiple enemies. Took some doing. My extending the Enemy class to Fighter and Destroyer classes flopped at first, but finally figured it out. Excellent intro for me since I know how to work with Unity. Looking forward to any other vids you may have coming out.
@igor-grachev
@igor-grachev 11 ай бұрын
We've waited a long time, thanks for coming back!
@bissash05
@bissash05 10 ай бұрын
I've watched the first 4:20h and I also watched the extended version, and seeing this solution is very interesting, but , i think is not a good solution, not for the Godot approach, I recently uploaded a video called "Combat System" in my KZbin channel, you can watch it (editing a typo here), its in Spanish tho, but i know just watching the code you will understand how it works. Now my way of thinking in Godot is more like the childs of the Node is the interface of the Scene, what i do is to code in a way that i can reuse custom nodes, and i use them as if they are "Components" and basically what i do is composition. This means custom nodes should do only one thing. HealthComponent is an Area2D that holds methods like take_damage(), and take_heal() and some signals that helps to extends but not modify the code so it doesn't mess up with the scene that have this node, lets say an enemy, the player, a trap, etc. Now a HitboxComponent could be inside of an enemy, or a bullet and works perfectly fine, a HitboxComponent (Area2D). I have defined a Class_Name with the respective HitboxComponent | HealthComponent and I treat them as Types, so what i do in the HitboxComponent is to check if the area is a HealthComponent, and as we know, with interfaces, if this is true then calling the method is also ok, no errors at all. extends Area2D class_name HitboxComponent var damage: int = 2 void _ready(): area_entered.connect(hit) func hit(area): if area is HealthComponent: area.take_damage(self.damage) Simple as this, im not checking for a method name, im checking for the type that was defined with the class_name. I really recommend you to check my video. The Scene itself in godot is a class. Think of child nodes as if they where interfaces for the scene | Class, I know it differs from your idea of adding interfaces to a specific script, but i think this is not the way to think in Godot. Could be useful? Yes, maybe, but i wouldn't base my architecture in this approach. Now with your permission, im going to watch the 10h godot video from this channel.
@tutemic
@tutemic 10 ай бұрын
Thanks for the comment. To be clear, I'm not saying that I personally recommend using hacky interfaces with GDScript as a basis for your code architecture. They could be a nice little tool though especially if you're already acquainted with using interfaces as a means to alert you to a break in class structure. Thanks for saying you'll watch the 10 hour code architecture tutorial (spoiler, no interfaces)!
@bissash05
@bissash05 10 ай бұрын
@@tutemic Thanks for the videos, I have to say I got a lot of value in those hours, really appreciate your work 😁
@u9vata
@u9vata 10 ай бұрын
I also like this approach much better! I honestly learned myself NOT to do OOP anymore even in C++. There my issue was performance related and readability also improved by composition like techniques. It seems though that a lot of people have very mind-ingrained wish to use interfaces, but this what you do it much much better design in my opinion and if someone does not know about OOP they would likely much more easily accept a node-based composition approach like this.
@bissash05
@bissash05 10 ай бұрын
@@u9vata Is a more better way to reutilize Nodes in Godot, infact this is the way that Nodes are intended to work, in a composition based approach.
@maxmustermann3938
@maxmustermann3938 11 ай бұрын
I think you can also check that a method has the required amount of arguments and potentially even if the types are right (in case you want to use strict types in your methods). The reflection stuff in GDScript most likely lets you access that information about methods as well.
@thegamedevclub
@thegamedevclub 8 ай бұрын
Excellent work! For beginners Godot seems perfect but once you try to create a bigger project things become very very complicated soon and thats when you begin to appreciate object orientated principles^^ Keep up the good work!
@Sylfa
@Sylfa 9 ай бұрын
One thing that can be done to improve the string lookup in has_method or "implements" in … is to use unit tests. It's how I would deal with the lack of static typing and not have a typo suddenly break things. Or rather, have the typo break things early so that you know *what* broke things.
@Roncaroto
@Roncaroto 11 ай бұрын
Wow!! You're alive! lmao, glad to have you back man.
@Caio_Raphael
@Caio_Raphael Жыл бұрын
That's a really cool problem. I'm really enjoying the tutorials! I'm glad you are back :)
@mleii1169
@mleii1169 11 ай бұрын
Wow, as someone who really wants to get into Godot, and I am, but really dislikes how much string use is done in GDScript this video helped me see how I can kill off some of this terrible string use. Thank you! One area that I'll have to see if I can do something similar with now, guessing I can, is with the use of strings in Input. My guess is that I might be able to do something similar here as well. I'm a LONG TIME software development engineer in test, so things like using strings make for fragile and hard to debug code. And as someone else mentioned this would be good to make only occur in debug mode so that it didn't impact performance of new objects created if spawning a lot of them, I'll have to try out the method they mentioned. Also might be good to figure out how to make only run if the Node is actually one that has scripts rather than all Nodes. Though I suppose wouldn't matter as much if I made it such that it only occurred in debug mode or maybe with a global value that turns it on/off, and defaults to off when not in debug mode. Also, still learning Godot, but now wondering if this could be done at either script entry time or maybe build time. Like maybe some sort of Editor Tool. Been thinking a lot about other tools I'd like to make for Godot after I learn more.
@ShiloBuff
@ShiloBuff 11 ай бұрын
This is very clever and useful. I love it. I believe it can also be more error-proof by also type casting for intellisense and hopefully other benefits. Example Line #22: (other_area as Interface.Damageable).take_damage(self.damage_amount) But it's a shame that it wont give editor-time errors or compile time errors for this type of situation. Also would only prefer this system to only run in debug mode, so i'd probably try to find a solution to only have this type of check while testing before releasing the app. Which would keep the runtime optimized. I also feel another approach would be inheritence and subclasses, as that was my first idea to solve this. But I guess that won't always solve the same problems that interfaces do.
@TheLayeredKing
@TheLayeredKing 9 ай бұрын
The lack of interfaces has very much pushed me towards C# for game logic, at the very least.
@pedrodelgado4125
@pedrodelgado4125 3 ай бұрын
This workaround has saved more lives than penicillin
@jRsqILVOY
@jRsqILVOY 10 ай бұрын
The equivalent in languages that use composition rather than OOP is Traits - i.e. if you wanted to implement multiple interfaces. It feels a bit hacky to do it like this, might be better to look at using Rust or C# with Godot directly. Although it's handy to have a way of doing it at runtime at least. It doesn't check the arguments that the methods take though for example.
@PubliusMontis
@PubliusMontis 3 ай бұрын
Maybe checking all the scripts files at the start of the execution when the singleton is ready, get all the files .gd, check if they have the interface variable, then assert if the methods exist and so on. This is totally independent of the node tree and would crash faster
@LichiMan
@LichiMan Жыл бұрын
Thank you so much for all your videos. I discovered your channel just a few weeks ago and I was sad when I checked you didn't upload anyhing since two years ago. There's nothing like your videos anywhere. So, I'm so happy you were back! Thanks! I've just one question and it's the same you did in the minute 9:06 : "The enemy can take care of itself but now the laser is taking care of the enemy". So my question is about how Interface help on this? Because if I've understood everything well, Interface helps in a way you don't forget to add all the functions, variables or signals a Node needs to work properly and raising an error in case you missed something instead of just getting crazy trying to see where the bug is. But we still have the laser taking care of the enemy. Is this a good pattern? Or we should still avoid to objects taking care of other objects? I'm still in the middle of watching your 7h and a half video about Godot code architecture course and you used a lot of signals just to avoid this problem I'm talking about. Hope my comment makes sense! Thank you.
@tutemic
@tutemic Жыл бұрын
That's a great question, and there's a lot of debate about it. These assorted mantras, such as "big kids can handle themselves," within the bucket of "best practices" are really just general rules of thumb. We generally want to decouple our systems from each other as much as we can, but at a certain point our efforts to fully decouple our systems have made the code so complex and hard to follow that we have created an even larger problem. There is no science on exactly what that threshold is. For the specific common example you raise about communicating the amount of damage an attack does between the attacker and the attacked, there are so many ways to handle this, and don't let anyone shame you for your approach. Having the laser call the object's take_damage function still generally avoids a lot of the problems of coupled code. It doesn't know anything about what it is damaging (other than that it implements Damageable), and there's no rigid reference to the thing it is damaging. If you wanted to have the enemy ship handle itself slightly more closely, you could create a base class called PlayerProjectile which possesses a damage property, which is set differently by all classes that derive from it. Then you can check in the Enemy script if other_area is PlayerProjectile and get the damage property. You could do something similar but with interfaces instead. You could create some kind of global damage table that designers can modify in one place, and both projectiles and enemies can reference it. There are again so many ways to handle this conundrum. Your approach choice can also depend on how you implemented other systems within the same game, in an attempt to remain consistent with your other decisions.
@LichiMan
@LichiMan Жыл бұрын
Thanks for this very detail reply and for all the ways/ideas you explain. I'm always afraid of "is this the best way to face this problem or I will finish doing some spaghetti code impossible to manage?" But from your reply I get that there's no only one right path and that even decoupling all the systems it can even get worse in the future. Thanks again to take the time to answer!
@domi6369
@domi6369 8 ай бұрын
Hello, is it possible to foreach() the project files and check scripts that way instead of checking nodes?
@Betegfos
@Betegfos 8 ай бұрын
Awesome tutorial as always! Do you have any content on how to implement multiple resolutions for a game and perhaps and options menu as well?
@badunius_code
@badunius_code 7 ай бұрын
6:30 it is nice to have some sort of `damage_taken` signal. For stats collecting, or achievement, or combo As for actual damage delivery I would extract it to a singleton. So that a projectile on `body_entered` would call `CombatSystem.process_hit(self, body)` and let the combat system decide the outcome.
@infernopyromaniac
@infernopyromaniac 10 ай бұрын
Very useful info, I didn't end up using the system you made, instead opting for a much simpler Interface class which just holds some functions and some dictionaries so I don't have to use ducktyping when using has_method and also allowing for Interfaces to be implemented with just a function declaration rather than the slightly more sophisticated variable you used. Overall this has been very helpful!
@luismaschietto
@luismaschietto 11 ай бұрын
Please keep going, I love your videos.
@PangolinMontanari
@PangolinMontanari Жыл бұрын
Alternately, you can avoid string names in has_method() by putting those strings as variables in an autoload, then assert amethod is in a node by something like @onready var damage_interface = Interfake.interface.new(self,Interfake.take_damage) ## in our autoload, var take_damage = "take_damage"## Where the interface inner class's _init() asserts that the calling node has that method
@tutemic
@tutemic Жыл бұрын
Hey that's one simple workaround for string literals! Check out the GitHub link in the description where the version solves the string literal problem shown in the video. There should hopefully be no string literals using the script now.
@lalalala99661
@lalalala99661 5 ай бұрын
thank you for that tutorial, i had a issue i ran into using this approach, i think its a bug in godot itselve: the check if "implements" in node: was true on one particluar node of type (class) BoneAttachment3D for some reason it allways passed the check ( if "implements" in node:) even it doesnt had the "implements" in the node. i made a little workarround and created a var excluded_types_list:Array= ["BoneAttachment3D",] and added after if "implements" in node: a additional check: (if node.get_class() not in excluded_types_list:) so if the node type is not in excluded types list do the interface checks. if it is in the excluded_types_list i print a message: else: print("there was a ignored edge case node in the interface check called "+node.name+"with the ignored node type:"+node.get_class()) hope that might help somebody running into a simmilar issue. i heard in other context that the BoneAttachment3D is not working like expected in godot 4x maybe this is some special edge case.
@dudehobo
@dudehobo Жыл бұрын
this is really good can help me implement things that components cant/hassle
@KENISEG
@KENISEG 7 ай бұрын
20:40 - 22:40 KEK :D i like it
@malcolmcolindixon
@malcolmcolindixon 11 ай бұрын
Interesting problem! I had to have an attempt at this but I am a Godot newbie but not a newbie programmer. @ilonachan hinted at a proposed solution earlier. Declare interfaces in _static_init() of class, e.g. Interfaces.implement(Enemy, IDamageable) Interfaces is an autoload (Singleton), which stores the interface as the key and the types as values in a dictionary,: func implement(type: Object, interface_type: Variant): if is_implemented(type, interface_type): return var types = implemented.get(interface_type, []) types.append(type) implemented[interface_type] = types Check that the "registered" type has implemented the methods in the interface as part of the Interfaces.implement method: var new_interface_type = interface_type.new() var interface_methods = get_methods(new_interface_type) var new_type = type.new() var type_methods = get_methods(new_type) for method in interface_methods: assert(method in type_methods, "%s has not implemented method '%s' for interface %s" % [type.resource_path, method, interface_type.resource_path]) Here's the get_method's method: func get_methods(type: Object): var methods = [] for method in type.get_script().get_script_method_list(): methods.append(method.name) return methods Within scripts to check if an object has implemented an interface use Interfaces.is_implemented(Enemy, IDamageable) using this: func is_implemented(type: Object, interface_type: Variant): return type in implemented.get(interface_type, []) This seems to work fine, although I've seen the editor reporting a type isn't in scope, which seems to be a bug as reloading the project it disappears. This method will only report the first class that hasn't implemented a method but that's easy enough to work through until you've implemented all methods. I'd really appreciate any feedback as I may have missed an easier method for parts of the solution. BTW I learned quite a bit from this so thanks.
@m0-m0597
@m0-m0597 11 ай бұрын
pretty interesting, worth a discussion in the godot forums
@malcolmcolindixon
@malcolmcolindixon 11 ай бұрын
@@m0-m0597 Thanks, I just recently joined the Godot Engine Discord, is that where you mean? If so, which channel to discuss in?
@m0-m0597
@m0-m0597 11 ай бұрын
i was mainly refering to the ask godot website but u know what, I want to be honest here, I was thinking a little bit about the interface approach on gdscript, and kind of changed my mind If I rethink my whole architecture and take GDScript for what it is - which is a dynamically typed language, how icky that may feel - I might not run into problems where I have to check what type an object is. That includes the whole event/signal architecture. You want to keep dynamic type checks rare. Also, GDScript is super intertwined with the editor. So, have we really exhausted the editor's complete potential here? This approach on an interface is nothing but a giant dynamic type check anyway. I also think about using C++ modules for the performance critical parts. Don't really want to use C#, since marshalling is heavily expensive
@badunius_code
@badunius_code 7 ай бұрын
56:00 whatever is done up to this point is in no way better than just checking if `other_area *is* Enemy` The latter requires no additional code, no additional variables, uses no string refs, and provides autocompletion
@Garretlike
@Garretlike 5 ай бұрын
Just watched the whole video because i'm currently at that point using projectiles of different types with player and enemies and even though i'm not new to interfaces and programming i still need to understand go as a lang a bit. So thanks for putting this out here. i have 2 questions though. 1) Wouldn't it be performance cost ineffective to do all these iterations/recursions on multiple instances of an enemy? the _ready function would call every time these loops when an instance of an enemy and player would enter the scene. 2) Wouldn't it be better to have some sort of unit test that instantiates different enemy types and then does the assertion instead of doing it ingame constantly? (edit: i forgot to mention under the condition i'm using a ci)...else would you pre-relese remove those assertions and checks?
@hiiambarney4489
@hiiambarney4489 28 күн бұрын
I am fairly inexperienced in programming and so on, let this be prefaced. I was searching for "interfaces" in regards to separating input checking from a player script and came across this video. Isn't the entire setup fighting the entire design "philosophy" of Godot's Node system? And please correct me if I'm wrong, so I may learn, but: Wouldn't it be easier, quicker, and more modular to just create separate nodes for expected behavior from entities? Instead of creating this autoload interface, we create a node with a script, giving it the class name "Damageable" and specifically handle how damage is processed in there. For Example: It would probably be beneficial to handle the entire health system of an entity inside one of these components, offloading the responsibility from calculating damage, when to queue_free() itself and so on, from each enemy type, player, or whatever can take damage in your game, be it an asteroid or other obstacle that isn't a player nor enemy, onto it's own component. Have an indestructible obstacle? Don't attach the health component, simple. This, at least it seems to me, is, on a fundamental level, how Godot seems to want to handle it's relations. A Player doesn't need to have a sprite, it could instead have a 3d Model as a child, representing it's visual component. You define little amalgamations of complex objects by adding functionality through attaching more nodes. An Area2D could therefore just be defined as a "Hitbox Component" and can have a Class attached to it like that. With this setup, you don't even need to check if something "has_method", if the area you are checking (using the built in keyword "is") IS (of the class) HitboxComponent, you can directly call the function from there, giving you intellisense and even let you control + click it and see the functions declaration. All in all this system requires at most as much setup as this interface has but has a range of benefits for structuring and visually adding functionality to any given entity on the fly, right? Sort of like using little "mini interfaces" in the form of nodes instead of an autoload and essentially having to rewrite behavior for entities that cannot inherit every behavior, such as an obstacle.
@dibaterman
@dibaterman 10 ай бұрын
Um, I just made a set of static functions, pass in the damage to that as well as the object hit, it handles passing that to the object that was hit which changes its stats accordingly.
@rhyspuddephatt
@rhyspuddephatt 8 ай бұрын
Looks cool, does mean you can only have one interface since you bind it to the implements string. Make it a list and it will be able to handle c# style interfaces
@nftsasha
@nftsasha 7 ай бұрын
haha this was entertaining to watch, but... no. "if 'implements' in..." is the same devil as "has_method", so this improves nothing. Can't wait for interface implementation in gdscript at some point!
@thomsencummings8471
@thomsencummings8471 Ай бұрын
couldnt you defined Implements as a const in Interface autoload, so you dont have to do the string compare?
@dueddel
@dueddel 6 ай бұрын
That custom interface solution is a bit of "von hinten durch die Brust ins Auge" as I'd say in German (which is some sort of expression for being cumbersome in English). Too bad, however, that GDScript doesn't support actual interfaces. Using inner classes as you did is a neat "hack", though. But I somehow doubt I will ever utilize that for my own projects. Nonetheless awesome video as always. 😘👍
@soran2290
@soran2290 11 ай бұрын
How create two data binding un godot?
@alexsandrzhemanov5710
@alexsandrzhemanov5710 6 ай бұрын
That's weird. How do you have get_tree().node_added.connect(...) in "interface" singleton working. It does not work at startup. It works for me (4.2.1 | 4.3 ) only at get_tree().reload_current_scene().
@johnmerriam7844
@johnmerriam7844 14 күн бұрын
When you are using the if "implements" in other_area does the area in question need to be an Area2d node ? In my setup, the script for the enemy is attached to a characterbody2d with an area2d node child, and this if "implements" code isn't working.
@johnmerriam7844
@johnmerriam7844 14 күн бұрын
I checked this by creating a script for the Area2D node specifically, and using var implements there, and it now works. Can anyone explain about this?
@Sylfa
@Sylfa 9 ай бұрын
I think adding a function in interface . gd that performs the appropriate checks would be better: if Interface.implemented_in(other_area): instead of if "implements" in other_area: Or at least: if Interface.implements in other_area: Though I think encapsulating it in a function would allow for better checking, such as in debug mode you can keep a dictionary over scanned types, and it can then look for typos in "var implements" by scanning all members and looking for the interface types. Obviously only for debug, but it'd allow it to catch "var impements = Interface.Damageable" without needing to bring in unit testing frameworks. Honestly, I feel like there should be a good way of using pattern matching, allowing something like: match Interface.implements_in(other_area): Interface.Damageable: It would work with a single interface, but sadly I can't think of a good way to deal with multiple interfaces. Array matching would be clunky, and as far as I know you can't check for an element at an arbitrary position. [.., Interface.Damageable, ..] isn't supposed to be legal for instance, and again it'd be rather clunky. if Interface.implements(other_area, Interface.Damageable): Would allow array of interfaces, checking for typos, and be easy to use. Maybe a fluent interface, something like: if Interface.in(other_area).is(Interface.Damageable):
@tutemic
@tutemic 9 ай бұрын
I've thought of a couple better ways since I livestreamed this video. I'm glad that the video is at least stirring the pot and getting all you wonderful people to talk about better ways for GDScript to handle this problem. The beauty of FOSS is that maybe one of you will even get your system officially implemented in the engine!
@qwitwa
@qwitwa 11 ай бұрын
get_all_descendants could be find_children("*"), which is recursive by default, no?
@tutemic
@tutemic 11 ай бұрын
Hey yeah that would probably work! Haha, doesn't hurt to get some recursion practice in though, eh?
@josefGebbels
@josefGebbels 5 ай бұрын
Sorry for interfering, but could we use something like ``` var implements = Interface.add(Interface.Damageble) ``` to have check for methods in "add" function and not traversing through the whole tree?
@josefGebbels
@josefGebbels 5 ай бұрын
Also we can use array in the add function
@lincolnpepper816
@lincolnpepper816 7 ай бұрын
using a hardcoded keyword for the variable that stores the interface ("implements") seems kinda gross. Is there a better way?
@Lmfaocj
@Lmfaocj 3 ай бұрын
Wouldn't a dictionary of callables or something like that have a similar effect? I'm a beginner at coding. I just am trying to figure out why that couldn't replace this.
@IraKane
@IraKane 11 ай бұрын
Great tutorial. As a newbie to Godot (I've been coding for a while and I've been using Unity for 10 years....yes ...I know...) I don't know if what I'm going to say is even possible but, what if, you use a global script in C# with the Interface? and then make another node that can implement the Interface in C#, and then "somehow" access that implementation from any gdscript...maybe. Or maybe I'm a mad man saying a bunch of crazy stuff. I don't know perhaps is a terrible idea (my idea not your idea XD ) You can always use C# for it all and that's it. But GDScript has its own charm I guess. In any case your solution looks cool in case of using GDScript 😃
@tutemic
@tutemic 11 ай бұрын
It's not a "crazy" thought. I have used C# to pipe in features to GDScript that GDScript otherwise doesn't have access to. I think I might make a KZbin video on an example of how this can be very useful. Piping in code features of C#, such as interfaces though? Hmm... I'm not sure how possible that would be. I'd have to think about it, but it's an interesting idea!
@IraKane
@IraKane 11 ай бұрын
😁@@tutemic
@jamesrivettcarnac
@jamesrivettcarnac 6 ай бұрын
I would love golang style interfaces in gdscript
@KENISEG
@KENISEG 7 ай бұрын
so hard for me ; _ ; I will continue to use the has_method until my rectum starts to hurt
@Theraot
@Theraot Жыл бұрын
Summited to your consideration: Iterate the filesystem and check all the scripts instead of checking all the nodes. Potential drawback: won't check any script created in runtime. With that said, anybody creating scripts in runtime probably knows what they are doing.
@tutemic
@tutemic Жыл бұрын
Thanks for the feedback and idea! Ideally, it would be real-time linted like Godot does with syntax that is built-into GDScript so that you would know there was a problem before even hitting run! I think you could maybe do that if you made it a @tool script, but those don't work on autoload script (I don't think), and so the developer would have to put in an instance of the tool node for each scene they want that to check their scripts in real-time (after hitting save). However, I feel this would be terribly buggy and probably create performance problems in-editor, on top of the confusing extra work to set it up.
@Theraot
@Theraot Жыл бұрын
@@tutemic EditorPlugin
@truenincillo805
@truenincillo805 6 ай бұрын
Hello, I have merged (jump) + (swim), swimming has limits of pressing button 1 at a time, how can I do it without limits??? But without touching jump.
@truenincillo805
@truenincillo805 6 ай бұрын
extends CharacterBody2D # --------- VARIABLES ---------- # @export_category("Player Properties") # You can tweak these changes according to your likings @export var move_speed : float = 400 @export var jump_force : float = 600 @export var gravity : float = 30 var GRAVITY = 30 var JUMP = -600 @export_category("Toggle Functions") # Double jump feature is disable by default (Can be toggled from inspector) var is_grounded : bool = false var is_in_swim = false @export var gravedad_nadar : float = 0.25 @export var velocidad_nadar : float = 10 @export var fuerza_nadar : float = -200 @onready var player_sprite = $AnimatedSprite2D @onready var spawn_point = %SpawnPoint @onready var particle_trails = $ParticleTrails @onready var death_particles = $DeathParticles # --------- BUILT-IN FUNCTIONS ---------- # func _process(_delta): # Calling functions movement() player_animations() flip_player() # --------- CUSTOM FUNCTIONS ---------- # # func movement(): # Gravity if !is_on_floor(): velocity.y += gravity # Move Player var inputAxis = Input.get_axis("Left", "Right") velocity = Vector2(inputAxis * move_speed, velocity.y) move_and_slide() # Player jump func _physics_process(delta): if not is_on_floor(): if(!is_in_swim): velocity.y += GRAVITY * delta else: velocity.y = clampf(velocity.y + (GRAVITY * delta * gravedad_nadar), -1000, velocidad_nadar) if Input.is_action_just_pressed("Jump"): if is_on_floor(): velocity.y = JUMP if is_in_swim == true: velocity.y += fuerza_nadar # Handle Player Animations func player_animations(): particle_trails.emitting = false if is_on_floor(): if abs(velocity.x) > 0: particle_trails.emitting = true player_sprite.play("Walk", 1.5) else: player_sprite.play("Idle") elif (is_in_swim): player_sprite.play("SWIM") else: player_sprite.play("Jump") # Flip player sprite based on X velocity func flip_player(): if velocity.x < 0: player_sprite.flip_h = true elif velocity.x > 0: player_sprite.flip_h = false # Tween Animations func _on_nadar_swim_state_changed(in_swim): is_in_swim = in_swim
@Caracuan12
@Caracuan12 Жыл бұрын
What is the disadvantage of using hurtbox/hitbox instead of the interface?
@pirateKaiser
@pirateKaiser Жыл бұрын
he's still using a hitbox to detect collision, what he's trying to solve here is making sure that the object with which you collide has functionality to take damage. Hypothetically the laster/bullet could hit an indestructible asteroid, it wouldn't need a take_damage() method and your code would break if you try to execute an inexistent function. GDscript's solution is to check if a method exists by passing in the method's name as a string, which as demonstrated is error prone.
@ilonachan
@ilonachan 11 ай бұрын
the concept of having to check every node now seems a bit overkill. we have to explicitly declare when an interface will be implemented, so why not make this system opt-in? Rather than a global variable "implements" being exposed and statically assigned, have the paradigm be that to implement an interface you do sth like "Interface.implement(self, Damageable, OtherInterfacesAsListOfArgs, IdkIfGDScriptAllowsThis)" in _ready. I'd say it's pretty readable, and eliminates the overhead for nodes that don't care about this system. (It could automatically create the "implements" variable in the background too for easy checking) Also could it be possible to only do this check once when the script code is first loaded, and not repeat it for each instance created? Since the method should probably either exist from the start or not at all.
@tutemic
@tutemic 11 ай бұрын
Generally this check is pretty inexpensive, but yes if are going to be spawning hundreds of lasers per second, then you could easily optimize the interface script, such as keeping an array of references to scripts that have already been checked. Or simply getting rid of the autoload when exporting your project. Or have it set to only run on debug. But if you're spawning hundreds of lasers per second, you should probably optimize that first. Object pool for example.
@TheEngomusic
@TheEngomusic 9 ай бұрын
nice one!
@festerdam4548
@festerdam4548 11 ай бұрын
I haven't watched the whole video (only up to 15:00), but in your problem couldn't one simply use node groups? In the _on_area_entered method you could then simply check `if other_area.is_in_group("damageable")`.
@tutemic
@tutemic 11 ай бұрын
I hope the rest of the video kind of elucidated the answer to your question. But the simple version is yes, you can use node groups, but you cannot define strict method, property, and signal ownership through a node group. Part of the reason to use interfaces is that the code will fail before the game even starts if any "damageable" node script does not follow Damageable properly. Using node groups, something could fail during runtime and in a non-obvious way.
@wizardscrollstudio
@wizardscrollstudio 9 ай бұрын
I would not follow this suggestion because inner classes in GDScript are incredibly inefficient data structures.
@charlesabju907
@charlesabju907 7 ай бұрын
Thanks for this input. We could do a simpler version without classes though, right? Like using a Dictionary or something
@charlesabju907
@charlesabju907 7 ай бұрын
Also, is "get_method_list()" efficient? I don't know Godot's code but this sounds like reflection and isn't reflection generally expensive/badly optimized?
@revimfadli4666
@revimfadli4666 6 ай бұрын
What would be the most efficient way to do this then?
@wizardscrollstudio
@wizardscrollstudio 6 ай бұрын
@@revimfadli4666 There is an implementation of this as an addon by nsrosenqvist called "gdscript-interfaces" is on Godot 3 on asset store and has forked and updated for Godot 4 by Mastermori. The implmentation uses no inner classes instead it uses regular scripts as interfaces with class_name , an array called implements = [] that keeps track of what implements what and an addon/plugin that facilitates calling of the implemented code. Thats it.
@SheepUndefined
@SheepUndefined 10 ай бұрын
Oh huh, interesting. I've been more or less hacking my way around this by having functions in a base class that do nothing other than print a "you forgot to impliment this function in x object" error, but that requires unneeded inheritance sometimes, which...has been screwing me over a fair bit, haha I'm not sure how I feel about the reflection tho? Part of my brain is going "wait isn't that kind of slow" but the other part of my brain is too lazy to actually look that up and see if I'm right.
@jeffvenancius
@jeffvenancius 10 ай бұрын
Maybe it would be better to just have string arrays for interface instead of full functions(?) Something like class damageable var functions := ["take_damage"]
@eano22827
@eano22827 10 ай бұрын
Thanks!
@onreadyvar
@onreadyvar 10 ай бұрын
I am a beginner on any language, but I guess GDScript does not have interfaces by design. In other words, GDScript is for duck-typing. I do not love the design, I need interfaces, and the workaround Chabane introduced is really cool. But doesn't it mean I should use C#? Hmm.
@kamex3102
@kamex3102 10 күн бұрын
Hey! Strongly believe you are overcomplecating the problem. You are puting too many responsibilities into each class while also having string referencies. Also puting lots of code into single file. Kinda opposite of Engine ideology i believe. Why not use composition instead? Put code that you like into separate child node of object. Heres simple example of how to get child component of given class: static func get_component(owner:Node,class_type:Object)->Node: var comps:Array[Node] = owner.get_children() for comp in comps: if is_instance_of(comp,class_type): return comp return Scene composition: Target | -HP (class_name Health) -Other nodes..... For example we have class with name Health extending Node. Than we can just try: var health = get_component(target,Health)
@Rin-qj7zt
@Rin-qj7zt Жыл бұрын
Why retroactively check the nodes? Why not load something first that queues up all the nodes added in an array, then when interfaces ready gets called, check all the nodes and hand off the work, so to speak.
@tutemic
@tutemic Жыл бұрын
Yeah that would work, but since that autoload _ready only happens once the entire duration of the game, and most main scenes at the start aren't millions of nodes large, it shouldn't make any discernable difference in load time. Plus, if you're having each node report itself to some kind of global initial node array, then you'd want to remove that array from the heap or else it's just needlessly taking memory. Not hard, but that all adds more complexity and probably extra code in more places.
@dringaling
@dringaling 10 ай бұрын
I make a Contract system by using signal, callable and dictionary to archive similar result as C# interface and there is no "magic string". I don't know how to post link to my repos because my comment will be deleted right after. Ex: If you want to implement interface in some class like Enemy: func _enter_tree() -> void: Contract.create(Interface.Hitable, self, _hit) func _hit() -> void: #Do something And in other class like bullet, you can call the method is implemented: func _on_area_entered(area:Area2D) -> void: Contract.do_contract(Interface.Hitable, area) //calling the method is implemented. Contract1.do_contract(Interface.Hitable, area, _damage) queue_free()
@monsieurouxx
@monsieurouxx 3 ай бұрын
I don't understand the syntax _some_variable = Interface.Damageable_ What would it be in another language? Interface is already at the same time a Node and a class. And it has class Damageable inside of it, but it's not instantiated. So .. is Interface.Damageable a kind of namespace? Or is it a static class inside a class? GDScript syntax is infuriatingly cryptic. EDIT: I understood at 33:25 when seeing the "implements.new". So in GDscript you can actually assign a class *definition* to a variable? Uh. That makes zero sense, but if it lets us have interfaces then I'm all for it. EDIT 2: now I'm trying to understand whatever "getscript" could possibly mean when you're not dealing with a Node but only with a plain old class (in this case: node.implements, which contains an instance of class Interface.Damageable)
@devdanielplays...7519
@devdanielplays...7519 26 күн бұрын
The method 'get_script' is being invoked on the *instance*, which is an instantiation of the class represented by node.implements. So the invocation is on an instance, not a class name. Not sure if you know PHP, but you can do something similar. I could have an instance property on Foo called 'classReference' and assign Bar::class (a string with the namespaced class name) to classReference. Given an instance of Foo '$instance', I could then run '$some_class = new $instance.classReference();'. $someClass would then be an instance of Bar.
@monsieurouxx
@monsieurouxx 26 күн бұрын
@@devdanielplays...7519 thanks
@ritzenhauf
@ritzenhauf 6 ай бұрын
still unclear why interfaces are desirable. what's so wrong with has_method style or other ways to ensure things are where you expect them?
@Weeb1367
@Weeb1367 2 ай бұрын
OH MY GOD
@JellySword8
@JellySword8 8 ай бұрын
4:27 I'm pretty confused at what exactly the difficult issue is here. It seems like you could just create a base "Projectile" class that has a "damage" property and then subclass it to have projectiles with different damages. Then you can just use polymorphism to check if the colliding object is a "projectile" to get its damage. Sorry if I'm missing something here, admittedly I haven't watched the whole video yet.
@yapayzeka
@yapayzeka 9 ай бұрын
sorry. i think i disagree because of unnecassary complicaiton. thanks for the tutorial.
@proKaps
@proKaps 10 ай бұрын
I miss Unity 😭
@_.-.
@_.-. 10 ай бұрын
Wow, the interface situation is just MISERABLE. I can't believe it's simply not implemented Godot is what, almost 10 years old now? Did people actually use it during this time in any serious capacity?
@kiyasuihito
@kiyasuihito 4 ай бұрын
This is really weird honestly. Idk why you'd do any of this. Just use the mechanisms the docs suggest amd wait for traits. For example, you could easily put nodes in groups and check that a node is in a group without doing all this extra work. Plus it's more efficient. Idk. +1 for originality though I guess.
@AnagrammaMerk
@AnagrammaMerk 5 ай бұрын
Maybe it would be nice to start with the "better ways" part, so that people know what to look for.. I am very thankfull for making educational content and the video but I for example didnt want to watch the entire hour to figure out how to do this one specific thing :/ I am very thankfull for the chapters though
@EricDeBruin
@EricDeBruin 7 ай бұрын
You're introducing an anti-pattern because of concerns of an anti-pattern. There's a reason Godot doesn't have interfaces. Instead, you should be using composition over inheritance with Godot. Create reusable components that you add to your scenes.
@Mohrahn
@Mohrahn 6 ай бұрын
Exactly this
@tutemic
@tutemic 5 ай бұрын
Yeah I agree. Some folks are totally turned off from Godot when they see it's missing their favorite feature from a different language or engine. This video was mostly an experiment on twisting and using GDScript in ways that you normally wouldn't consider. Expect more zany GDScript experiments in the future!
@vertexdune
@vertexdune 5 ай бұрын
6:04 you make signals sound ridiculous because the signals you're making are ridiculous. i equate this to someone saying that boilerplate is dumb because they write dumb boilerplate, as if there aren't intelligent people genuinely applying themselves to the world of programming. given that you are emitting a signal of the game state from inside the player script and you named it "game_over_happened", i just really don't think you know what's going on. your game state should emit signals relevant to itself. would it make sense for Game_state to emit a signal from Player? no. you are trying to make the player decide when it's game over just because it died. all the player should care about is letting the world know if its dead or not. all the game state should care about is the state of the game. signals from the self should only be emitted from the self. as such, the player shall only emit signals about itself. the game state shall only emit signals about the game state. what makes all this screaming into the void useful is the subscribing, the listening. the game state listens for the player death signal. you could have a generic deceased signal emitted by any character that dies or any object that queue_free()'s or you could have a more specific player_death signal if that's what's important for your project. i cannot stress this lesson enough. you can LISTEN to any thing! and any one! but you can only SPEAK from YOUR mouth. if you want to use signals, use them right. if you dont want to, dont. but the idea that your projects are too complex or that you are too smart or that signals themselves are dumb is just plain arrogant. alhamdulillah
@nikolayrogchev9628
@nikolayrogchev9628 8 ай бұрын
Why do people like GDScript so much is beyond me... To me is total trash, I wish C# had better support :(
@donwald3436
@donwald3436 10 ай бұрын
lol game dev python worshippers tried to make a programming language.
@tukki649
@tukki649 3 ай бұрын
Thank you
10+2 AWESOME ADDONS for GODOT 4
8:36
MrElipteach
Рет қаралды 140 М.
When to Use a Resource Over a Dictionary?
11:06
Coding Quests
Рет қаралды 7 М.
Magic or …? 😱 reveal video on profile 🫢
00:14
Andrey Grechka
Рет қаралды 52 МЛН
王子原来是假正经#艾莎
00:39
在逃的公主
Рет қаралды 25 МЛН
GTA 5 vs GTA San Andreas Doctors🥼🚑
00:57
Xzit Thamer
Рет қаралды 25 МЛН
I Feel Bad For New Programmers
19:12
ThePrimeTime
Рет қаралды 437 М.
Immersive Sim in Godot 4: COGITO - Overview
8:22
Philip D
Рет қаралды 40 М.
How principled coders outperform the competition
11:11
Coderized
Рет қаралды 1,7 МЛН
20 Advanced Coding Tips For Big Unity Projects
22:23
Tesseract
Рет қаралды 181 М.
How to program in Godot - GDScript Tutorial
58:10
Brackeys
Рет қаралды 617 М.
Understanding The Basics of GDScript - Godot Fundamentals
19:12
Game Dev Artisan
Рет қаралды 28 М.
5 Games Made in Godot To Inspire You
4:33
StayAtHomeDev
Рет қаралды 30 М.
Godot Debugging Techniques EVERY Dev Should Know
16:23
Bacon and Games
Рет қаралды 27 М.
Magic or …? 😱 reveal video on profile 🫢
00:14
Andrey Grechka
Рет қаралды 52 МЛН