Godot 4: how to implement interfaces in GDScript!

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey everybody this is Siobhan welcome back um this is actually being recorded as part of a live stream for the 2D tutorial but what we want to talk about is how to use interfaces with GD script that's I think a sought after feature for a long time and to talk a little bit about you know how to implement interfaces object-oriented programming interfaces is what I'm referring to um let's talk a little bit about why we would want to use an interface and so to do that from the conclusion of the extended live q a video that I guess we'll Post in the description um if you haven't seen that go ahead and watch it we're going to create some system where it would maybe make more sense to use an interface and let's talk about it from there so the situation that's very commonly you know that you can't commonly run into especially with the Shoot Em Up and I felt irresponsible leaving this beginner tutorial off at a point where you would naturally run into this next you know I feel like it was kind of a cheat to not show you um how to deal with this problem so if I come back to the script you'll see that every enemy when you or let's go ahead play so that you can see what's happening right now so if I shoot and I when the laser collides with an enemy notice how it immediately destroys the enemy and you get score for doing so right it destroys the enemy instantaneously um and I know some of you who maybe want to actually make a Shoot Em Up would realize that in what Shoot Em Up do all enemies always get destroyed in one hit right they usually have health right and so if they have health how do you implement a health system and that is naturally going to lead you down the path to okay well if there's a health system and you have different types of projectiles lasers missiles other types of things maybe the player can upgrade their lasers so that they do more damage well how does the enemy know how much damage to take so for example let's say that we have health right we had created this system in the uh live q a that we were just doing um where we have a health system right we start with 10 Health perhaps and let's say that when the player or when the enemy is uh hit you know we don't immediately destroy the enemy right we don't destroy destroy the enemy immediately we like reduce its Health right and we can do that by saying you know like Health minus equals and I don't know let's just say five let's just use some magic number for now and so now what we can do is we can check hey is the health less than or equal to zero and if so then go ahead and um destroy it and this is these are comments from the actually the beginner tutorial from before like kind of you know disregard those but basically what we're saying is we're going to reduce the health and if the health is less than zero then let's go ahead and destroy the enemy and let's increase the score right and so we have this health variable it's a member variable each enemy has its own health and they start with 10. so now when I go ahead and hit play and I run this and I shoot and I shoot then it destroys it and I shoot now has five health and I shoot and then it destroys it so now they each take two shots all right so very good well you can see uh that while that works and that's pretty cool especially if you're a beginner you're like wow there's a health system it all it was took was that okay cool hopefully this makes sense this little health system that we've created but the problem of course is well five that's a magic number like how what is it do we set it up here do we say like export there like damage taken is three you know it's like wait a minute like is it always going to be three like how do we what if we want to make it so that the projectile itself kind of dictates the amount of damage that is done to the enemy and you see how this is a problem okay the problem with this is that um who knows how much damage this enemy should take the projectile nose right so the laser perhaps has its own value right we say there I don't know damage amount or something like that and let's say it's five right so very cool say that uh a way to use a colon like a silly person um damage amount is five well how do we then convey this damage amount to the enemy right do we just say like uh this is like laser dot damage amount right um well you can see that's obviously a problem because well I guess we'd have to make this a static variable for it to see that but how does it know it's getting hit by a laser now are we going to complicate it by saying like is it a laser if health is like less than equal you know up here would say like if laser then do like minus equals five if missile then do minus equal 20. you can hopefully see how that's already really bad it's like well hold on what if there's an upgrade system where lasers are now seven it's like well I guess we've changed the static variable what if some of the lasers are five some of them are seven it's like oh gosh because like there's a power up right it's like well now are we shooting like upgraded laser is going to create a separate class for like upgraded lasers you see how like it you start to like think about these things it's okay to like Wonder it's okay to like while you're trying to go to sleep or you're in the shower or whatever the heck you're doing you're trying to think of like a better way to write your code and those are things that just come to your mind you might also think like oh I know we should do signals because people say signals are good good good good signals yeah well you can see the problem with using signals is this whole thing that we just did in the live q a again watch that if you haven't already uh if we do this game overhappen dot emit it's like oh okay um under the player like we can pass in a number right so we can create a signal called like uh uh laser hit enemy or something like that and then what put that in the game State it's like okay well maybe it would put it somewhere else it doesn't matter where you put it the problem is that that signal is going to be connected to All Enemies right laser has hit enemy or whatever it is we're gonna just fire that signal uh from the laser perhaps let's say yeah like this like is in group enemy let's do Q3 and are we gonna just like emit that signal from here well what all of the enemies are gonna get it you know what I mean because all the enemies are subscribed to it so it's like are they all then going to take damage how do you make sure that it's only taking the damage of that particular enemy oh well I could pass in the other area so that it can check to see if it's itself at this point if that even makes sense to you then good job you're in you know pretty good you're Advanced to a certain level but don't do that you can see how it really starts to get Icky uh when you do all that so you don't do that instead um what you would do is use interfaces perhaps but guess what we don't have interfaces and let's before we talk about how interfaces work let's talk about what you can do here right now with gdscript out of the box so right now we have health minus equals and effectively we want to say the laser here so instead of this on area entered let's make it so that we're going to create a function inside of the enemy and we're gonna say take damage let's say and uh amount will be like the amount of damage that it's taking and then we'll do this junk we're going to put it in here and I'm actually going to comment out this stuff um and oh just comment out all of this stuff and just put in a pass it's like he's reading my journal of learning all the all of this the hard way yeah uh very good so right we're gonna do this take damage function right and so under laser we can say hey guess what if the you know is in group enemy then let's queue free right because if we did it here if we said like other area dot take damage and then pass in the damage amount remember self.damage amount because this refers to this variable that belongs to the lasers because it knows how much damage it does then we're going to say you know take damage self dot damage amount if we do this we'll see the problem with it is as soon as I shoot it'll be like crash wait a minute take damage doesn't belong to player.gd remember as soon as you just fire the laser it's going to collide with the players uh you know Collision shape and so it's going to crash it's going to crash the code because the player doesn't have a take damage function only the enemies has a take damage function so I guess we can come back in here and be like oh well duh we wouldn't put it here you would put it here inside of the enemy other areas I take damage and so now when we hit play and we shoot it doesn't destroy the player and guess what look it works we're able to do that it's like oh that's cool foreign very good a couple problems with this one of the problems is we are now making assumptions about a couple things first of all remember how the enemy can take care of itself well now the laser is taking care of the enemy right so that's kind of a problem second of all we're making assumptions that enemy always has a take damage function maybe it doesn't right so how about okay okay instead of saying the enemies take damage function because maybe it's not just the enemy that takes damage maybe there's like power-ups you have to shoot like it blasts their Shield like maybe the power-ups have like a shield so you have to destroy the shield in order to be able to get the power up or maybe there's like you know NPCs that take damage also that are not enemies or like you know environmental things like a tree who knows like maybe they take damage too um like asteroids those aren't enemies and they take damage so what we can do is we can ask this question we can say other area dot has method now method is just a member function it's a function that belongs to an object in other areas the object we're going to ask the question hey does this object this other area have a method called take damage and if it does we'll then call the take damage function this cannot crash ever this might crash this can't crash why because we just checked if it has a tank damage function so if it does we can call it now it might crash if like we change the number of arguments that take the image expects and stuff but you know what that you can fix that if you really wanted to but it requires a little bit more effort so now when I hit play you can see that well the player doesn't have a take damage function so it's not getting just you know wrecked anymore but these enemies do and so they're able to take damage and so this right now is currently to my knowledge kind of like the way that you have to deal with interfaces right you're able to you have to like ask that question is does it have a take damage function well you can see the problem with this is because yeah there might be things that take damage that don't take damage from lasers perhaps right like maybe a tree or a maybe the player we add a take damage function you can always open like that and suddenly it makes things messed up the other problem with this is what if we change the name like what if later on enemy is like it's not called take damage we call it it's like well take damage amount I think people are getting confused and as soon as you do this the problem with this is not only does it break it breaks in a way that it doesn't tell you that it's broken it the game just doesn't pave correctly anymore you see how that's a problem you when you want it when it breaks you want it to break spectacularly especially in the debugging situation you want this to break in a way that like it crashes the game and tells you why it's broken so that you can quickly fix it and you might be like well I'm not going to break it I'm just going to keep it called take damage whenever I want you famous last words when you say that kind of thing as soon as you have a hundred scripts and you accidentally like merged your files in some weird way or you accidentally puts you know you've misplaced something or you accidentally like put a tab here now it's like a sub function or something weird yeah call me back and say like okay I blew it I don't know why it's broken and what if like now a hundred different things take damage it's like how do you update that it's just gonna be it's such a squirrely mess and this is what we call for those people who are in the know this here where is it laser this here is called a string reference a string reference a string reference you can't auto fill it right I mean to some extent you can like with the like uh I think some things like string reference autofill like the is action just pressed but like what you have to spell it exactly right right and if anything changes it breaks and this is not inherently an error because it's not referring to anything it's just a magical string right it's a literal string value it's not attached to anything it's just you're being used as a key to find a method so you can see how this is potentially a problem so how can we maybe make this better that's kind of what I want to get to now you can use this because that's what like tons of people do they use exactly this in order to handle this problem of being able to decouple this idea of enemies and damage and if you want to be able to create some category where you're going to create a group called takes damage like you could do that you can say like is other area in a group called take damage well what if the idea of something that takes damage is more complex than just it has a particular method right you could have a whole group for it but then that still doesn't create a guarantee that it's going to break the code right if like you know it doesn't follow this whole like it has that method just because it's part of a group called it takes damage doesn't mean it has a method necessarily you have to write that in in spurious kind of hard to track logic throughout your code so what if we wanted to put this in like one place because back in the day let's say back in the day with object oriented program programming principles we have this thing called interfaces okay an interface allows you to basically say hey this object belongs to a particular class right like the enemy class right it takes stuff from the node 2D class but it can only be one class you can't say class enemy and also like damageable object or something like that like you can't do that it's going to be like what the heck are you doing because something can only be one thing right so uh what if I wanted the enemy to be able to be something that is damageable but it also is something that is you know has score or whatever it is interfaces allow you to specify some characteristic about this class this object that guarantees that it has a particular set of methods and variables and that's what we call an interface all right so if we wanted to do that let's let's go ahead and um we can Define interfaces let's let's create a um uh Auto load so under project settings I'm going to create a new thing called interface okay um and I'm gonna add it and I'm going to create a script called interface.gd I'm going to go ahead and click create very cool and I'm going to open this up again what I've done is I went to autoload tab I created a new script called interface.gd I made the object called interface capital letter because classes have that capital letter Pascal case is what it's called um like capitalized camel case and I'm going to open that interface.gd script so here it is interface.gd I'm going to get rid of this stuff because all this does is it's handling interfaces so if we were using another programming language like c-sharp or something like that we could just say something like interface right uh I'm not going to get into the exact syntax of c-sharp so we'll just make it basically like pseudocode we would be able to say like interface and we could create a thing called damageable right except you would typically not like all caps it like that you say like interface damageable and then you would specify what are the terms of being damageable right so this is like what people say a contract you know like when you sign a contract you're saying like Okay I'm going to come into work at 8 30 in the morning every day or else like I've followed I've like failed my job responsibilities and you know all that kind of stuff you have a contract it says if you do this then you get paid this right similar kind of idea or an interface says like okay it has these methods right has these methods right it would be something like take damage is one of those functions right and it has like these properties like you know so basically this is this is how an interface works and whatever anything what we call implements damageable so for example under enemy we're going to say hey it implements it implements damageable what that would then say is that this function or this code will crash if it doesn't follow the rules it doesn't actually have a take damage function and it will tell you it'll say it's done it crashes and by doing so by it follow it by it committing to follow the rules of what damageable means then you can make assumptions about it in things like laser like hey if it implements take damageable then we know it has a take damage function we also know that has this function and it has this variable and has these things doesn't that sound a little bit better and it crashes if it doesn't work right instead of hey it doesn't work and for some reason it's just not doing it just doesn't work it doesn't crash the game it just doesn't work so that's what we're trying to do here with GD scripts how do we do that so I'm going to go ahead and come back here to interface and I'm going to go ahead and hopefully you're following along because this is where it gets a little bit tricky perhaps um let's go ahead and create a inside of this interface let's create something called uh let's create a class and we're going to make it called damageable okay this is where this is going to be a little bit tricky for a lot of you so if you don't know what classes are I'm not going to take the opportunity right now this assumes you know some basic object-oriented programming principles like what a class is I've talked in the live q a about how to set a class name but you can create a class this is what's called a subclass it's a class inside of this interface GD class that's okay so it's a sub class that's fine we can define a class within a class that's totally legitimate even inside of Duty script and so inside of this class we're going to say that it has the function called take damage and so far this is totally legit right and it has uh you know it we're not going to worry about the actual parameters we could if we wanted to but we let's say we don't have time and just so that it doesn't crash we're just going to say pass because this we're not telling you how take damage works because everything's take damage Works differently right maybe someone has a shield maybe something catch is on fire whatever so take damage Works differently depending on who is implementing this damageable interface okay so we're just going to say pass and we're going to let the each class that implements this damageable function figure out what this take damage method means to them so here we go we have this class it's called damageable and then inside of enemy we're going to implement that okay we're just going to do it in an interesting way we're just going to create a variable and we're going to say implements right it's the same thing it just has the word bear in the beginning and we're going to say an equal signs okay now we've got some equal signs and stuff and we're going to say yeah unfo's like what genescript has interfaces it does now so look if you don't have any idea what I'm doing it looks like I'm just a Madman on fire that's okay just you can follow along you don't even have to understand what it's doing and if you get it to work through how I'm doing it I want to design this in such a way that you can use it without even understanding really entirely how it works if you know it works great but you know like how it works that we got it to work as long as you understand how to use it that's what I care about anyways so implements equals uh I don't know let's do um let's go ahead and say the class so interface oh oh yeah it's a it's an auto load right um remember this is an auto load so it's a uh it's a Singleton we can access it from anywhere interface um dot what do we what do we want to call it we want to call it interface dot uh damage right we spell this correctly uh did I spell oh my gosh I wrote I call it it interface I wrote interface folks folks no must fix it can't be called interface for the sake of all things good it must be called interface and now we must come to the project settings and fix it immediately remove retry it should be called interface my gosh interface thank you this failed right this time oh my gosh how did this let me do this uh okay hopefully is it gonna crash uh uh where is this and float and operator have I crashed something else by doing this self-deposition.y oh did I delete something no I didn't maybe it's because the export it wanted it to be above hey thanks GD script for being so particular about where I put my export annotation all right very good it works see I love like making these mistakes in front of you um so very good so implements equals interface dynamical okay so what I've done here again now that it's spelled correctly a hopefully fast forwarded through that um I have this Global This Global variable basically This Global uh sorry auto load interface and it has a subclass and it called damageable so when inside of enemy what I've done is I've set a variable to the class itself I haven't instantiated it and I'm not planning on instantiating it here so we're just like it's just the class okay this is it this is all you need to do to be able to use it okay so now how does it actually work so inside of a laser right now we say if other area has method take damage let's say okay that's Nate Ctrl K what I want to do instead is I want to ask if it implements that interface right so now what we can do is we can say if uh this implements variable is inside of other area okay you use this um in keyword here thing in order to check to see if this variable with this name is inside of other areas so now if I hit save and I hit run and we see ta-da and then I shoot shoot and it destroys it look see because it has the variable implements right well of course you see the that's not all that helpful like you know if I um say implements Z then you'll see that it's it's broken okay that's fine um which is a problem that we're not really going to be able to fix but that's okay the bigger problem though is the interface doesn't even work right if I come to uh interface igd and I like you know call this take damages with a z you'll see that it still works because all that we've asked of the object is that it has a variable named implements and right it also doesn't I could Implement something else other than damageable and this would still work so we've got some work to do right yes and so uh I don't know different Neo XD um x y d says like yeah but we have a string reference again yes so it's not perfect because it's not it's not really built into duty script but the nice thing though is that we will always use implements no matter which script is using the uh you know interfaces we're going to use implements whether it be damageable or has you know Shield or whatever the heck it is that you know whatever interface that we are going to Define we're just going to make sure that we write implements okay and so therefore there's only one string we're not and we're not changing its name because we're assuming that like implements is just you know the name of this particular string that we use for interfaces unfortunately making it throw an error though would require a little bit more under the hood stuff that's just probably outside of what you would want to do until they officially Implement envelope interfaces into the GD script all right very cool so uh let's go ahead and keep going but um what I want to do now is um well actually let's go a little bit further let's go a little bit further so inside of laser if it implements uh uh let's make sure that it implements the correct thing right so what we can do is we can say if implements in uh this other area and what we can also do is we can say if other area dot implements remember we know this exists because we've checked to see it exists and we're going to say if other area dot implements is equal to specifically the interface damageable right so now if I do this and I go ahead and use the colon very cool now when I hit play and you can see that hey guess what it still works which is amazing I love it and if I wanted it to implement something else like so let's say we have implements but we do make it called damage damageable Z and of course like we have to fix it here too uh no why why am I doing that let's like just actually make another interface so I we can just create another one called like class uh playable because it's like a game where we have different playable characters and stuff like that and we do play something awful gosh darn probably should have come up with a better idea and for some reason we want the enemy to uh Implement that instead right it's playable so now when we hit play and we hit run it doesn't crash anything but it doesn't do this anymore right which which is awesome because even though we are checking to see if implements is another area implements is there but it doesn't Implement specifically this particular interface okay it'll only work if the other area specifically implements this damageable interface right now if in an official implementation of this in GD script it would probably be something like you know does other area implement this particular interface but notice how you can use like autocomplete and if anything changes we call it something else like takes damage then this will crash because there is no you know uh subclass called damageable anymore so it does break now see if I called a damageable z or something stupid it will crash right there it is it's like what the heck is this it doesn't exist anymore right so uh let's come back and let's call it damageable and see things are crashing good right that's how we want it to work okay so we kind of have some system set up but again we don't have we haven't figured out this part yet right now it the enemy can have no function called take damage right if this was called take damage Z even though the interface has said that it should be take damage without a z then notice how the code still works right it still works we don't want it to work oh it says non-existent function take well yeah it's because I was calling the function elsewhere that has not that's immaterial to this whole concept of interfaces it died because it's elsewhere in the code was I referring to the enemies take damage function it didn't work anymore anyways so I wanted to crash because it didn't follow the interface properly okay so how are we going to be able to do that all right um let's go ahead and uh uh let's create a function inside of interface and we're going to make this function allowing us to check notes um Alan says you could use something like has method this feels like shoehorning static language features yes and uh you probably came in a little bit later that is exactly what we did do down here I don't want to get sidetracked here we already did the has oh sorry it's under laser has method all right so coming back to interface let's go ahead and do check node we're going to check to see if it's uh it can take any kind of node because any kind of node can enter can Implement interfaces so check node um we're going to check this node to see if it has these functions and properties and signals and all that kind of stuff and so how are we going to be able to check this node well first of all let's make sure that this node actually um has this Implement variable in it right because we want to check every node right if we want the interface system to work and to be robust we want to be able to check every darn node in the game and so if it doesn't have implements then you don't have to check it right I'll only check it if that word implements is in there and then after we've done it um what we can do and this is what's really kind of interesting what we're going to do and this is what's going to be kind of interesting is uh let's go ahead and um let's figure out what methods are part of this node okay let's figure that out right so we're how do we figure out what methods are part of the node and what methods are part of this damageable class that's how we have to do it so what we're going to do is we know that the node that is coming in Implement is trying to implement an interface because it has this word implements remember very this variable exists right implements as soon as it exists we assume that what's coming to the right of it is an interface that's part of this autoload in this case damageable so inside of interface what we're going to do is we're going to instantiate that class that it's currently referring to so what we can do is we're going to say there I don't know instance equals uh and this is going to be the instance of the interface right so you can call it interface instance or something like that I don't care and I'm going to say uh node dot implements remember the variable is called implements and we're going to create a new instance of this class remember there's a damage Vault and there's playable enemy happens to have interface die damageable and so interface.damageable is what is being stored inside its implements and because what's being stored is just this un uh instanced class we are able to instance it here by doing new and now instance has a copy of in this case damageable so now what I want to do is I want to figure out well what is inside of this instance and there is let's go ahead and print so I can print and inside of the print let's do like instance dot get method list right something like this right instance.get method list so if I print this and I hit play you can see oh gosh it has uh let's get rid of this where did I put this these like numbers I think it was under player please please stop doing this that was from the the live thing we need to do check node so um let's have the enemies check themselves as soon as they respond so let's do uh interface dot check node and this is just for testing we're just going to pass itself in okay so I'm going to hit play and now you're going to see I'm going to hit pause and then I'm going to come back to the output you can see that this is all the junk that's inside of any enemy that is spawned right except it's not any enemy that spawned this is the stuff that's inside of if you remember and you look carefully not the enemy because we're not doing node.net get method list we're getting the whatever the nodes Implement variable is uh the instance of whatever class that Implement variable is set to which is this damageable what the heck what is all this stuff I don't want all that stuff hint string usage hint class name whoa so here's what's kind of weird it's this little get method list gets like all the methods associated with any object which is fine but I want just the methods that the user defined in the source code okay the source code meaning this particular script so what we can do is get script method list which is the methods that were defined in the script now if I hit play uh non-existent function is that oh man I think huh where does it get this the script methodless I'm pretty sure it exists under object so if I were to be like self.get script method list if anyone in the chat here knows what the actual name of this function it says non-existent function but I'm pretty sure it exists oh it belongs to the script duh this is like some kind of some kind of crazy Wizardry does that work it does very cool now when I look at this it uh has this array and this thing right here take damage so this array including this like object in it it's the whole list right and it's the first item here and it's an odd it's a dictionary and the name of the function here is this take damage thing okay so if what I want to do is I want to get the name of the function right so uh well what is it called name oh gosh so uh that is the the method list here and if we want to get the first item which is zero and then we do that name and we hit run take the image take damage take damage notice how it's always the first here is the only method in there but in order to make this a little more clear and this is where we're going to go is what we're going to do is I'm going to get this script method list just like we had done before but to make this a little bit more clear a little bit more easy to read um I'm going to iterate through it so I'm going to say for each method in this instance get method list and I'm actually going to embig in my code editor so you can see better and let's print each method's name okay now when I hit run take damage take damage take damage so you can see that there is only taked image because this four in loop again the for Loop not going again but for those of you who aren't familiar this is a for Loop where we go ahead and we Loop through this particular array this particular list all right and so each method inside of this method list we're going to print its name and you can see it's only take damage exists okay very cool so what we've done here is we're able to now know that this method name uh exists inside of damageable now what I want to do is I want to make sure that every one of the functions that are inside of here so for example if I did another one called it's damageable I don't know take critical damage or something like that which is not a very good thing to do but let's say we we did it and we did pass you can see that when I hit play you'll see that it has both take damage and take critical damage both of those are now functions that exist inside of this method list so instead of printing it what I want to do is I want to assert that it exists so what I can do by asserting is I can assert that the method name here is inside of node the node that we're checking we're making sure that the method name that's part of this instance of whatever it is that we've instantiated which is this damageable class which belongs to implements we're going to make sure that it's method the each method again for each method you can find that method inside of the node that is supposed to be implementing this damageable interface so now by asserting that what I can do is I can actually put in a comma here and I can well let's just see if it crashes right so if I run this and it goes hey look no crashing and it works still very cool now let's test to make sure that this works by coming over to the enemy function and I'm going to do take damage except um I don't know let's say that we didn't have this function anymore right we call it take damage this is a with a z like we always keep doing it doesn't have the function anymore it immediately crashes and it says assertion fail what it assert it failed because it tried to you know assert that hey this method is inside of this node and that assertion was wrong so it will crash that's what an assert does it basically is like a throw error if this condition is false okay that's what happened here so now if I come back to enemy and I'm going to come back to making it called take damages this is really promising it appears like it is working right but I want it to be more verbose because like okay assertion failed assertion what so what we can say is um I don't know something like interface error uh node and we'll just give the name of the node um let's put a space here does not possess the you know and uh it's too many let's just put this on a new line here um does not possess the uh method.name [Music] thod right now and you can you can use these little you can create a new line like this and that'll work and keeping the plus here the operator on the end of the line makes it a little bit easier to read that that's what we've done here so now if I go to hit run and okay it's working again because I renamed it back but if I come back and I call make a call take damages with a z so I can crash it again it says interface error enemy does not possess the take damage method boom it tells us exactly why it broke it's because we at some point either accidentally you know we weren't thinking or someone broke it in our team or something like that it will break and it'll immediately tell us how and why because it doesn't have the take damage function anymore you see how that's really really really cool hopefully that makes sense uh maybe it doesn't but you can re-watch it if you want I'm going to uncheck this little check node I'm no longer going to have because I don't want you to have to do this right I don't want eat you have to like manually say check node inside of the ready of every type of script because I want this to work out of the box just because you did this just because you did very implements equals interface damageable and because you created this little class thing up here at the top so I wanted to automatically check the node whenever it spawns right and so to do that inside of the interface what I'm going to do is unready and again I might seem like kind of a Madman so like again positive you you need to or ask questions in the chat if you're still here um what I want to do is I want it to check every darn node when they're born so what we can do is we're going to get the tree from our autoload script and I want to there's a a signal here called node added so if I look at it it says it's emitted whenever a node is added to the scene tree that looks perfect so I'm going to come back to interface and we're going to connect just like we had done before watch the live q a if you weren't part of this uh and I'm going to connect what check node um to this and this tree added thing or node added thing notice how it it has a node it'll pass a node automatically to the function that is connected to this signal See it'll pass in the node that was um added so very very cool so here we do we say check node and I'm going to go ahead and play again and notice how uh it's okay it's working because I think I fixed it I keep fixing it unfix it take damage to the Z okay so I run here notice how this darn one is still working wait a minute not exist okay okay okay okay it didn't work uh automatically unfortunately I was hoping that that ready function oh darn I'm missing an underscore guys come on folks help me out here I'm missing it underscore what all right now I'm gonna hit play okay still it doesn't work but now it works that is in like it works and it doesn't work what we wanted it to do is we wanted it to not work it broke I love your tutorials thank you all right so um you see how it works it says that does not possess the take damage function but do you see why it worked though it well I mean well we saw why it worked but why didn't it work right away what because this see like every time uh an object is added to the tree it's going to use this check node function on the Node that was added to the tree it's going to come in here it's going to check to see hey it doesn't Implement does it have a variable called Implement if so we're going to check to see hey if the thing that is implementing is it going to you know have the characteristic have the methods of the class it would supposedly inter you know implementing so to speak um the interface that it was implementing is kind of like faux interface that it's implementing um well if that was the case then why didn't it crash instantly notice how there's an enemy right there that enemy had the variable that enemy had the uh implements variable why did it not work well you can see the reason why it didn't work is because this signal will only emit once something has been added to the tree after this has run when does it run after the this darn tree already exists right this tree is here already this is not going to run until the game starts and this tree already exists we want this to run we want to check every node that already exists not just the ones that are born after everything is has started now if someone else has again this is like coming out of my own crazy mind so if anyone else has like a better way to be able to check every node this is just you know me let's say like this is me writing a way to get every node and also if there's a better way to get a remote As far as I know you can't get every node right you can do get children right so you can do something like you know self dot get children like you can get children but that only gets the direct children like it doesn't get all of the descendants too and as far as I know there isn't a function called like get all the darn descendants okay so what if we created our own function called get all the darn descendants not literally but let's create a function and let's call uh get all descendants is descendants spelled with an A or an e I'm so bad at spelling that word is it an A or an e is a descent dance right someone tell me in the chat and we'll fix it if necessary so it'll take in a node and this function is supposed to return all of the design it's not just the children all of the descendants well here's the problem with getting all of uh something because we know we're going to have to do this whole like get children thing right because that's how you get children and we want to get the children children well we could use like a like a nested for Loop of some kind but we don't know how many nestings we need to do so what we're going to do is we're going to use something gasp we're going to use what's called recursion okay so here's how we get all the children right so we're going to say there I don't know children of this particular node equals get children and we're going to Loop through these children we know that that's something we're going to do again if you don't know where to start just start with what you know you know you're going to be getting children and you know you're going to Loop through them so we're going to do so for child in children right so this is going to get every child that needs children well what are we going to do well we're going to return um the result of appending those children together so we're going to do uh return Calling this function get all descendants um and we're going to we're going to assume that this is going to return an array and we're going to do append array I believe in fact if you don't know I'm just going to go ahead and like come over here and I'm going to just try it out right so we're going to do like this Dot append array yeah so there it is it allows you to like combine two arrays together so append array and we're not going to append array we're going to return um the result of appending the array to whatever we got from the get all the sentence which was an array there we go that's it um but what are we appending the array to right we're going to get an array from this but we have to start with an array so let's assume that we're just going to start with the ring so we're going to say there uh I don't know array let's just say all descendants because whatever um ants equals and let's start out with just this node inside of an array and then after we're done with all this let's not return it let's just append the array so all this sentence is equal to append this uh all descendants wait does is append array destructive as in like um I think it is so I think I don't have to like say I think I can just do append array and it'll like append directly to this array I think um and we need to pass in a variable here right so uh we're gonna pass the child in and let's then return uh all citizens okay so hopefully this made sense actually I don't know if it didn't make any sense that's totally fine but uh if there's a different better or faster built-in way to be able to get all the descendants of something then you know let me know but again what we're doing here is we're going to start with an array that has just the node in it and then we're going to get all the children of the this particular node and then we're going to go ahead and we're going to call the get all descendants function that we are writing we are inside of the get all descendants function we're going to call the function in itself and then for each child it's going to start with this each child is going to be inside of its own loan array and then each child's children are each going to be inside of their own loan array and eventually they're not going to have this Loop because they're not going to have any children so ultimately they're just going to be this and once they end with this that's basically what we call the tail each of them are just going to be this and so what we're going to do is we're just going to return this just empty thing and it's going to Bubble back up and it's going to go ahead and append this to whatever you've gotten so far and ultimately it should be just everything okay so very good let's actually test to make sure this works so let's go ahead and print um the result of calling the get all descendants um of the scene trees Uh current scene so that's just going to be the like the main scene it's just all the darn Children of the bait scene so I'm gonna go ahead and play and cool no stop okay come back to outfit look main space background player ship graphic Collision shape enemy spawner timer everything is there and it's all inside of this array so you know what I want to do I want to instead of just print uh like getting all the descendants I want to check everything so I'm going to create a thing called their all the darn nodes and uh uh is okay I'm too many things so all the DAR nodes and I'm going to say for each node in all the darn nodes let's go ahead and check the node and we're going to pass node to that so we're going to call node check node at the very beginning on all the existing nodes that are at the beginning so now I want to hit play it's instantaneously going to crash because there was already an enemy right there at the beginning right if I look back at my scene tree there's an enemy right there in the very beginning and so instantaneously it's going to crash because it got all the darn nodes and it looped through all of them I checked all of them and that darn enemy didn't have a take damage function because again it was expecting that it had a take damage function because it was going to Loop through all of the functions that the this damageable interface was expecting and it was going to check inside of the node check node thing it was going to create an instance of this class because the enemy is implementing this implements variable is getting this uninstantiated class interface damageable and then every single then uh in order to check it's going to Loop through all of the methods that are inside of this little faux interface and it's going to check to see it's going to make sure hey is that method inside it's going to say hey that method is inside of the node that we're checking and it wasn't there so it that assert is going to fail and it's going to crash and give this specific error message and it only works in the debug if you were to export this to like you know the App Store and still it would still work if you like you know actually exported this project assert just works inside of the like little testing debug mode so now if I come back down here and I change take damage back to how it's supposed to be spelled now it doesn't crash and it works um some of you are saying like hey you know what like isn't there slightly better way to do it um yeah absolutely what I'm just doing this on the Fly come on folks um but look it plus we haven't done this for variables and signals and stuff like that but you can do that yourself right I don't want to do that right here but inside of your check node what you can do is you can just like come in here copy and paste and instead do get script like see if I did self get script um dot get oh come on I'm gonna go inside here so we can just look at the methods that exist inside of the script um notice how there's get property list right we already did get method list there's get method list there's get property list there's get signal list right so come on use the gears someone post this on the internet right so um you see how that works I can now do a for Loop to check all the variables or sorry all the properties which are member variables all of the signals and everything um so maybe it's not perfect and um won't breadfirst search be better than uh depth first search here and like this thing happening at the beginning of here is like come on if I you know I have to resist the temptation to get into like a debate about this right now um but basically what I'm gonna say here is that there are better ways you can do this let's just say that there's better ways you can do this you can Loop this this better but the way that I've implemented it here assuming that this is even like there's a you know there might be a better way to get all the descendants and I just don't know about it um like this is going to take a fraction it's going to be like a millisecond you know what I mean it's going to be incredibly fast and we're gonna be able to get all the descendants uh inside of the tree as it exists and as soon as it runs you're no longer doing that anymore you're only doing it already and you're going to check the nodes um through this connect whenever something is added to the tree and this is using a built-in feature so if that's not optimized then that's not your problem it's a good problem that is an engine feature uh so is the given node limited to implement only one interface awesome thank you definitely xyd if no xyd exactly so not only did I not show you how to do the whole like property like get properties and interface sorry signals and stuff like that which hopefully you can take from here but I also make it I also currently made it so that only works with uh this interface damageable well guess what if you can make it so that it has you know interface dot what was the other one playable or something stupid um right you can make it so that it's a an array and then all you have to do in here to make that work is just change check node so you're not you're checking to see if it's in here and then check to see if implement is an array and if it's an array then Loop through and check each uh each of those faux interfaces if it's not an array then just check it as if it's a single interface all you have to do is like update the check node and everything still works and boom now you have it work with arrays or just single right and it just works right out of the box for the sake of time I'm not showing you how to do that because hopefully you can do it yourself um I trust one of you will go out there and actually do this um but here's the thing if you just did it up to this point and you're just doing method lists and you weren't going to update it to the new thing maybe I'll do a separate video or like actually or maybe like I'll just post like a GitHub link if someone does do the solution or I just do it later I can just post maybe a git Hub link to it so that you can add it to your own project but all you would need to do even if you like have no idea like all this recursion junk that we've done and like the classes and all that kind of stuff for someone who's just using this feature who knows generally how implements works all you have to do all you have to actually do to get this to work once you have this code in there whether you wrote it or not to be able to use the code all you have to do is create any subclass you want inside of interface this interface you just add this as an autoload you add how whichever functions you want and this is the contract whatever function or properties you put inside of that subclass is what any node that implements that interface is bound is by contract required to implement as in to Define inside of their own function definition right and so all you have to know how to do is create this little class and whatever the heck you say are things that need to be in there and then how to be able and then it'll break it'll crash your you know your game if you know you um implemented it and you didn't follow those rules and so all then you have to do to implement the interface is just do this very implements equals interface dot whatever right um and that's hopefully relatively easy enough to do it's you know not that much more tricky than this will have this method thing and then inside of where you're actually going to be using it to be able to use it all you have to do is you just do this little if implements in other area now again this is still a string literal string it's a string reference but I think that's better than doing this like littering your code with has method and like all sorts of different string references that you could do this on the other hand is always going to be exactly the same one right it's always going to be implements so whenever you're using interfaces it's always going to be influence and so if you really want it you could probably go in further and like make this a little bit more safe if you really wanted to uh again I'm up for suggestions I can think of something that would be easy for beginners right off the bat so implements in other areas and then to check to see like which interface you're looking for you just say you know if that implements is equal to exactly the one that you're looking for and this is Auto completable and this does crash not only does it crash if it doesn't possess those methods but it will crash if you somehow screw this up if let's say you change the name of the interface later on down the road it'll crash in all the checks too right and then it works like this so again if you're kind of a beginner and you generally know how to work with interfaces then it's really hopefully not that difficult to be able to use them and I think it's a little bit stronger than using has method all over the place or has property or whoa this guarantees by contract that any that this other area possesses all of the features of of this little subclass here very cool hopefully that worked um so I was given a limited to implement only one inner oh yeah I answer that question okay well I think that's it I said I was gonna end in one hour it's been an hour and 47 minutes like usual um hour 47 minutes from the live q a but from this particular segment where we talked just about Implement interfaces um maybe shorter but thank you all for coming out today hopefully you learned something um and hopefully uh more of this kind of content will come out just weird things that I'm trying that are Advanced insights into using Godot and GD script and things like that so give me your feedback say what you liked say what you thought could be improved like And subscribe but otherwise uh I guess good luck with all your projects and wishing you peace
Info
Channel: Tutemic
Views: 22,380
Rating: undefined out of 5
Keywords: Game development, game engine, code architecture, best practices, object-oriented, oop, inheritance, composition
Id: pBs6c902P0Q
Channel Id: undefined
Length: 60min 15sec (3615 seconds)
Published: Sun Sep 03 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.