Godot 4: how to implement interfaces in GDScript!

Поделиться
HTML-код
  • Опубликовано: 1 авг 2024
  • Want to up your coding game in Godot? Try implementing interfaces in GDScript.
    I went ahead and cleaned it up and posted an initial ready-to-use version of the interfaces.gd file on GitHub for you to use in your own project!
    github.com/tutemic/gdscript-i...
    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!
    This was originally recorded as part of the extended advanced insights and Q and A livestream for the absolute beginners shmup tutorial. You can find that livestream here: ruclips.net/user/live58jR5T1usP0
    The project for this video was constructed through the absolute beginners tutorial found here: • Godot for absolute beg...
    You don't need to watch either of those videos to start with this video if you're rather just watching this one!
  • ИгрыИгры

Комментарии • 144

  • @bocdagla
    @bocdagla 5 месяцев назад +5

    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  4 месяца назад +1

      Good points!

  • @spy_brightscale
    @spy_brightscale 9 месяцев назад +10

    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!

  • @tutemic
    @tutemic  11 месяцев назад +19

    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!

  • @darius_defiant
    @darius_defiant 9 месяцев назад +6

    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!

  • @ShiloBuff
    @ShiloBuff 10 месяцев назад +13

    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).

  • @stedunn563
    @stedunn563 10 месяцев назад +21

    From a c# background wanting to use gdscript, not having interfaces is annoying. Thanks for the video man

  • @kevinscales
    @kevinscales 10 месяцев назад +9

    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 11 месяцев назад +2

    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.

  • @Rin-qj7zt
    @Rin-qj7zt 11 месяцев назад +44

    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.

  • @tarsyth3433
    @tarsyth3433 11 месяцев назад +2

    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.

  • @yoloyojick
    @yoloyojick 6 месяцев назад +11

    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 6 месяцев назад

      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 5 месяцев назад +3

      Sounds you designed composition, over interfaces, which some would argue is better.

    • @w花b
      @w花b 5 месяцев назад +1

      ​@@samiyokimYeah that's how Godot works so it makes sense

    • @HuangShengHong
      @HuangShengHong 5 месяцев назад +2

      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  4 месяца назад +1

      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_ 10 месяцев назад +7

    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.

  • @soganox
    @soganox 5 месяцев назад +2

    Fantastic idea. Very quick to set up, and can definitely save headaches in the long run. Thank you!

  • @stevencoghill4323
    @stevencoghill4323 9 месяцев назад +1

    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 10 месяцев назад +2

    We've waited a long time, thanks for coming back!

  • @bissash05
    @bissash05 9 месяцев назад +4

    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 RUclips 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  9 месяцев назад +2

      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 9 месяцев назад +1

      @@tutemic Thanks for the videos, I have to say I got a lot of value in those hours, really appreciate your work 😁

    • @u9vata
      @u9vata 9 месяцев назад +2

      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 9 месяцев назад +1

      @@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.

  • @KyleLuce
    @KyleLuce 9 месяцев назад +4

    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 6 месяцев назад

      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

  • @BlazertronGames
    @BlazertronGames 5 месяцев назад +3

    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.

  • @maxmustermann3938
    @maxmustermann3938 10 месяцев назад +2

    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.

  • @42ultra
    @42ultra 6 месяцев назад +1

    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!!

  • @desmondbrown5508
    @desmondbrown5508 5 месяцев назад +1

    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.

  • @Caio_Raphael
    @Caio_Raphael 11 месяцев назад +1

    That's a really cool problem. I'm really enjoying the tutorials! I'm glad you are back :)

  • @ShiloBuff
    @ShiloBuff 10 месяцев назад +2

    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 2 месяца назад

      it's another way to call hardcoded constants, it's a "magical" number cause it comes out of nowhere 😉

  • @Roncaroto
    @Roncaroto 10 месяцев назад +2

    Wow!! You're alive! lmao, glad to have you back man.

  • @ShiloBuff
    @ShiloBuff 10 месяцев назад +6

    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 9 месяцев назад +1

      It should really run a linter / formatter for stuff like that :/

    • @bill_makes_games
      @bill_makes_games 2 месяца назад

      I am at version 4.1.3 and this error doesn't occur ( no problem with "@export var [...]" being after "var implements = [...]")

  • @PangolinMontanari
    @PangolinMontanari 11 месяцев назад +7

    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  11 месяцев назад +1

      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.

  • @TrashHeap64
    @TrashHeap64 4 месяца назад +1

    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 :(

  • @Sylfa
    @Sylfa 8 месяцев назад +2

    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.

  • @wreckingballgames
    @wreckingballgames 11 месяцев назад +18

    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!

  • @thegamedevclub
    @thegamedevclub 7 месяцев назад +2

    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!

  • @Nitbandier
    @Nitbandier 10 месяцев назад +3

    Best tutorial i have seen about godot.

  • @Alicorn_
    @Alicorn_ 7 месяцев назад +1

    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.

  • @dudehobo
    @dudehobo 11 месяцев назад

    this is really good can help me implement things that components cant/hassle

  • @luismaschietto
    @luismaschietto 10 месяцев назад +1

    Please keep going, I love your videos.

  • @mleii1169
    @mleii1169 9 месяцев назад +1

    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.

  • @TheLayeredKing
    @TheLayeredKing 8 месяцев назад +3

    The lack of interfaces has very much pushed me towards C# for game logic, at the very least.

  • @ShiloBuff
    @ShiloBuff 10 месяцев назад +3

    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.

  • @infernopyromaniac
    @infernopyromaniac 9 месяцев назад

    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!

  • @Betegfos
    @Betegfos 6 месяцев назад +1

    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?

  • @LichiMan
    @LichiMan 11 месяцев назад +3

    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  11 месяцев назад +5

      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 11 месяцев назад +4

      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!

  • @pedrodelgado4125
    @pedrodelgado4125 2 месяца назад

    This workaround has saved more lives than penicillin

  • @lalalala99661
    @lalalala99661 4 месяца назад +1

    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.

  • @domi6369
    @domi6369 7 месяцев назад +1

    Hello, is it possible to foreach() the project files and check scripts that way instead of checking nodes?

  • @soran2290
    @soran2290 10 месяцев назад +1

    How create two data binding un godot?

  • @badunius_code
    @badunius_code 6 месяцев назад

    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.

  • @jRsqILVOY
    @jRsqILVOY 9 месяцев назад

    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.

  • @tukki649
    @tukki649 2 месяца назад

    Thank you

  • @TheEngomusic
    @TheEngomusic 8 месяцев назад +1

    nice one!

  • @Garretlike
    @Garretlike 4 месяца назад

    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?

  • @dibaterman
    @dibaterman 9 месяцев назад

    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.

  • @alexsandrzhemanov5710
    @alexsandrzhemanov5710 5 месяцев назад

    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().

  • @PubliusMontis
    @PubliusMontis Месяц назад

    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

  • @eano22827
    @eano22827 9 месяцев назад

    Thanks!

  • @KENISEG
    @KENISEG 6 месяцев назад +1

    20:40 - 22:40 KEK
    :D
    i like it

  • @Caracuan12
    @Caracuan12 11 месяцев назад +1

    What is the disadvantage of using hurtbox/hitbox instead of the interface?

    • @pirateKaiser
      @pirateKaiser 10 месяцев назад +1

      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.

  • @qwitwa
    @qwitwa 10 месяцев назад +3

    get_all_descendants could be find_children("*"), which is recursive by default, no?

    • @tutemic
      @tutemic  10 месяцев назад +3

      Hey yeah that would probably work! Haha, doesn't hurt to get some recursion practice in though, eh?

  • @thomsencummings8471
    @thomsencummings8471 6 дней назад

    couldnt you defined Implements as a const in Interface autoload, so you dont have to do the string compare?

  • @ilonachan
    @ilonachan 10 месяцев назад +1

    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  10 месяцев назад +1

      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.

  • @malcolmcolindixon
    @malcolmcolindixon 10 месяцев назад +2

    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 10 месяцев назад +2

      pretty interesting, worth a discussion in the godot forums

    • @malcolmcolindixon
      @malcolmcolindixon 10 месяцев назад

      @@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 10 месяцев назад

      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

  • @lincolnpepper816
    @lincolnpepper816 6 месяцев назад

    using a hardcoded keyword for the variable that stores the interface ("implements") seems kinda gross. Is there a better way?

  • @truenincillo805
    @truenincillo805 5 месяцев назад

    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 5 месяцев назад

      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

  • @rhyspuddephatt
    @rhyspuddephatt 7 месяцев назад

    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

  • @festerdam4548
    @festerdam4548 10 месяцев назад

    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  10 месяцев назад +2

      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.

  • @josefGebbels
    @josefGebbels 4 месяца назад

    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 4 месяца назад

      Also we can use array in the add function

  • @Lmfaocj
    @Lmfaocj Месяц назад

    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.

  • @badunius_code
    @badunius_code 6 месяцев назад +1

    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

  • @IraKane
    @IraKane 10 месяцев назад +1

    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  10 месяцев назад +3

      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 RUclips 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 10 месяцев назад

      😁@@tutemic

  • @SheepUndefined
    @SheepUndefined 9 месяцев назад

    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.

  • @jamesrivettcarnac
    @jamesrivettcarnac 5 месяцев назад

    I would love golang style interfaces in gdscript

  • @KENISEG
    @KENISEG 6 месяцев назад +1

    so hard for me ; _ ;
    I will continue to use the has_method until my rectum starts to hurt

  • @Theraot
    @Theraot 11 месяцев назад +5

    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  11 месяцев назад +4

      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 11 месяцев назад +4

      @@tutemic EditorPlugin

  • @nftsasha
    @nftsasha 6 месяцев назад +2

    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!

  • @Sylfa
    @Sylfa 8 месяцев назад

    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  8 месяцев назад

      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!

  • @Weeb1367
    @Weeb1367 Месяц назад

    OH MY GOD

  • @Rin-qj7zt
    @Rin-qj7zt 10 месяцев назад

    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  10 месяцев назад +1

      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.

  • @jeffvenancius
    @jeffvenancius 9 месяцев назад

    Maybe it would be better to just have string arrays for interface instead of full functions(?)
    Something like
    class damageable
    var functions := ["take_damage"]

  • @dueddel
    @dueddel 5 месяцев назад +2

    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. 😘👍

  • @VuNguyen-tq1qe
    @VuNguyen-tq1qe 9 месяцев назад

    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 2 месяца назад

    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)

  • @onreadyvar
    @onreadyvar 9 месяцев назад +1

    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.

  • @proKaps
    @proKaps 9 месяцев назад

    I miss Unity 😭

  • @wizardscrollstudio
    @wizardscrollstudio 8 месяцев назад +10

    I would not follow this suggestion because inner classes in GDScript are incredibly inefficient data structures.

    • @charlesabju907
      @charlesabju907 6 месяцев назад +3

      Thanks for this input. We could do a simpler version without classes though, right? Like using a Dictionary or something

    • @charlesabju907
      @charlesabju907 6 месяцев назад +2

      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 5 месяцев назад +2

      What would be the most efficient way to do this then?

    • @wizardscrollstudio
      @wizardscrollstudio 5 месяцев назад

      @@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.

  • @JellySword8
    @JellySword8 6 месяцев назад

    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.

  • @ritzenhauf
    @ritzenhauf 5 месяцев назад +2

    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?

  • @_.-.
    @_.-. 9 месяцев назад

    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?

  • @yapayzeka
    @yapayzeka 7 месяцев назад +3

    sorry. i think i disagree because of unnecassary complicaiton. thanks for the tutorial.

  • @kiyasuihito
    @kiyasuihito 2 месяца назад

    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 4 месяца назад

    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 6 месяцев назад +1

    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 5 месяцев назад

      Exactly this

    • @tutemic
      @tutemic  4 месяца назад +1

      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!

  • @kishirisu1268
    @kishirisu1268 7 месяцев назад +5

    We dont need 5th wheel in GD script, all you need is stop thinking as C#/Java developer, GD script is not C# or Java.

    • @atilathenun
      @atilathenun 6 месяцев назад

      As someone who's trying to avoid switching to C# what do you recommend as best practice to replicate this kind of thing? Or is it okay to keep using has_method/has_node?

    • @irascib1e
      @irascib1e 6 месяцев назад +6

      Speak for yourself! I'm a gdscript user and this is exactly what I've been looking for

    • @monsieurouxx
      @monsieurouxx 2 месяца назад +3

      May God save us from people who say such things as "we don't need typescript, JavaScript is perfect the way it is"

    • @ThaAftrPartie
      @ThaAftrPartie Месяц назад

      @@monsieurouxx js devs left the chat with that one

  • @vertexdune
    @vertexdune 4 месяца назад

    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

  • @donwald3436
    @donwald3436 9 месяцев назад

    lol game dev python worshippers tried to make a programming language.

  • @nikolayrogchev9628
    @nikolayrogchev9628 7 месяцев назад +1

    Why do people like GDScript so much is beyond me... To me is total trash, I wish C# had better support :(