Це відео не доступне.
Перепрошуємо.

Reusability and Modularity Maintained by NOT using has_method - Godot 4 Design Pattern Tutorial

Поділитися
Вставка
  • Опубліковано 26 жов 2023
  • Stop copying what everyone is doing: has_method MESS! In this video I show you a better way to handle Signal Connection between many Killers and many Killable object types. The same principles discussed in the video can be applied to other scenarios as well.
    **********************************************************************************
    NOTE: DO read the pinned comment... I've got important news to share with you!
    **********************************************************************************
    This video is part of a series where I share what I find valuable as I migrate to Godot 4. Yes, I've been a Unity user too...
    If you haven’t watched the previous ones, here’s the link:
    Unity to Godot 4 migration Series: • Godot 4 OOP Game Desig...
    The comments section is complementary to the video itself. Your valuable insight into the subject is what shapes the next tutorials. Don't hesitate to share it.
    By the way, subscribe and hit the bell so you don’t miss what’s coming next.
    Thank you all for your great support!
    The Dependenzi GOD music track is taken from here: opengameart.org/content/5-chi...
    Released under public domain license (cc0). Special thanks to the author: juhanijunkala.com/

КОМЕНТАРІ • 129

  • @whilefree
    @whilefree  5 місяців тому +2

    I'm so excited to inform you that I've just released my Custom OOP Solution for Godot 4.2, called "ReuseLogic Nexus" addon.
    Based on Observer Pattern, Mediator Pattern, Singleton Pattern, and “Signal-Driven” State Machines, this addon is all you need to “Object Orientalize” your projects, reducing the amount of code needed, enabling you to reuse your modules/objects/systems in all your projects with almost no modification.
    Check this video out for more info and the download link:
    ua-cam.com/video/Zl3vo9aSKlY/v-deo.html

  • @valtarijunkkala
    @valtarijunkkala 9 місяців тому +53

    I am going to be honest here, this feels like the most complicated solution you could come up with.

    • @whilefree
      @whilefree  9 місяців тому +6

      Thanks for sharing your feedback!
      The solution is too sophisticated for the example provided. It's useful when dealing with more complicated scenarios. But I have to keep things simple for now.
      If you want to read more about it, search for Observer Pattern and Mediator Pattern. What I share here is a combination of these two.

  • @gwentarinokripperinolkjdsf683
    @gwentarinokripperinolkjdsf683 9 місяців тому +92

    You severely misunderstand the "Single function, single responsibility" rule. There is absolutely nothing wrong with having a "hit" function that decides what behavior should occur when you are hit

    • @whilefree
      @whilefree  9 місяців тому +9

      Thanks for the comment!
      The very first evident problem with having a hit function do all types of behaviors is you will have no clue whether the hit function is supposed to die, laugh, dance, or whatever.
      The name doesn't imply anything, and worse, if you decide to change the behavior, you'll have to get back into a vague "hit" function, and change the code.
      Of course you can use states to handle that situation but in such a case it just makes it worse.
      In a really simple example the hit function works. But in a large project with complex systems, such a workflow would result in a design pattern disaster.

    • @gwentarinokripperinolkjdsf683
      @gwentarinokripperinolkjdsf683 9 місяців тому +31

      @@whilefree nah, just wrong. The function isn't vauge at all, it handles what happens when the object is hit. Just as update handles what happens every game update. If you want you can split the internals up into different funtions like DetermineImmunity or whatever. But "hit" is basically an event listener, it's waiting for something to happen, so from the perspective of the caller (and all functions should be named from the perspecrive of the caller) the most obvious name is hit, or onHit

    • @NihongoWakannai
      @NihongoWakannai 8 місяців тому +5

      ​​​@@whilefree you don't need to know what the hit function does. This design pattern is very common with interfaces, it's a contract and the only thing other classes need to know is that the things implementing the contract will do something OnHit. No one else needs to know what it will do OnHit, that is up to the individual implementation of the contract.
      The purpose of the OnHit function is not to give information to the class calling it, it is to give information to the class being referenced that it has been hit.

    • @whilefree
      @whilefree  8 місяців тому +4

      Thanks for the feedback. There is a way to make fake interfaces in Godot, but the way I'm tackling the problem is a part of a larger OOP system which maintains reusability way beyond what you've ever seen, and reduces the amount of code needed dramatically. No need to rewrite things again and again when you start a new project. It takes some time until I completely reimplement it in Godot and make videos about it. So stay tuned for that and watch the rest of the videos in the series.@@NihongoWakannai

    • @guywithknife
      @guywithknife 7 місяців тому +1

      Having a hit function delegate the hit logic isn’t really any different from having a hit receiver object delegate the hit logic. Slightly different approaches for achieving the same end result, but are conceptually quite similar.

  • @UnstoppableTigra
    @UnstoppableTigra 9 місяців тому +25

    My opinion as a person who studies godot - is that I don’t understand many things, and jokes with quick editing don’t really help me understand the topic of the video. People who have already learned Godot will most likely not watch such videos. I love edits in entertainment, btw
    It looks like you can give great insights on Godot, but I'm having a hard time understanding
    I prefer my tutorial dry, straight to the topic

    • @whilefree
      @whilefree  9 місяців тому +3

      Thank you for your valuable feedback!
      Since I started editing in this style, the growth has been skyrocket (more than 250 subs in less than a month).
      I'll try a slower pace video soon, but the general reaction of people watching it is what "forces" me to edit in the specific way I do.

    • @cntrvsy
      @cntrvsy 9 місяців тому +1

      i understand what you are saying but i don't agree, that people who already know godot wont watch, infact looking at the comments they appear to be target audience, they will watch it as the video goes over philosophy of the code rather than the fundamentals of the engine. This is a design class not really a tutorial. just look at the comments and everyone has a different view of how best to tackle the problem. its not a story of right and wrong, its about advantages and disadvantages of using an approach (not everyone's foot will fit in the same size shoe). so making a lengthy tutorial of this would be pretty out of place, a github repo would be enough for those who are interested as they can clone then compare and contrast for themselves how best to move forward with their own project and whether or not to implement the philosophy.

    • @UnstoppableTigra
      @UnstoppableTigra 9 місяців тому +3

      @@cntrvsy word salad

    • @cntrvsy
      @cntrvsy 9 місяців тому

      @@UnstoppableTigra its not worth a tutorial, he aint here to do the work for you on how to make a game (Godot has documentation) he just here to share how he chose solve a problem he found while making games. it aint rocket science chief. stick to the docs if you want your hand held. the target audience here are people who are already familiar with the engine.

    • @UnstoppableTigra
      @UnstoppableTigra 9 місяців тому +1

      @@cntrvsy I won't read your messages anymore. :3 I expressed my opinion. Only good wishes to the author of the video. I find your messages strange, you're either pretending to be his boyfriend or you're a child

  • @Skaro11
    @Skaro11 9 місяців тому +51

    Maybe I am missing something, but this seems like an over-engineered solution for a simple problem.
    The way I'd do it is have a base class called NPC/Hittable/Killable that has an empty function called Hit()
    Then, Dragon can extend(inherit) from NPC and override Hit with whatever functionality you want, including checking who the attack came from and what it's type (In my game I pass all attack info as an object that contains the damage, element, statuses, etc)
    Like people already mentioned, there is no issue with the Hit function checking additional stuff before applying/ignoring the hit, as it still falls within it's responsibility of handling hits.
    If you want a behavior like laughing, that really shouldn't be handled inside Hit(), then you can call a signal (from inside hit), that is called something like on_immune_to_hit and plug in a different script that will handle this behavior.
    If I understand your example, the only thing achieved at the end, was that the functionality that chooses which method to call was moved from the dragon to an outside node, and it was made possible to configure it in the editor. Instead of has_method you now use has_node to find a reciever.
    To be clear, I am not saying there is no use for the node you made. I made a similar node called death_listener to hook up with any node that can die and call any function on any other node when a death event is fired. Then i can use it to drop items on death, without the enemy class knowing about it's inventory class or vice versa. It can also be made more reusable by having a configurable signal name. But, in your example there is no need (imo) for such a node and it begs for a simpler solution.

    • @whilefree
      @whilefree  9 місяців тому +5

      Thank you for putting the effort and sharing your deep insight on the subject! I appreciate it! :)
      I do agree that the solution is way too engineered for such a simple problem. But my goal in the video is to describe the solution in "simple terms". That's why an oversimplified example is provided.
      I don't really like your approach (emitting a new signal inside hit) because it doesn't change anything. You still have to put conditions in the hit function to see if it should laugh or die.
      In simple projects it works, but for large ones where you need to expand the already existing systems, it's going to turn into a disaster.
      The other thing is modularity; the solution I provided in the video enables you to reuse your objects not only in the same project, but even in your other ones.
      But that's a topic of another video and requires way more complicated examples.
      I believe you got the point of the video pretty well, because your last paragraph is exactly what I did.

    • @Ismail_NotFullName
      @Ismail_NotFullName 9 місяців тому +2

      @@whilefree Noobie here, just wanted to ask:
      couldnt you just have a base behavior to the hit function and then have exceptions to that base behavior?
      for example: you could have a dictionary with every attack exception (fire, water, wind, etc) as the keys and an associated callable with said keys. so when you call hit in the dragon, while passing an attack type, you check in the dictionary if the attack type is an exception in the dictionary (via dictionary.has() maybe?), and if it does, call the callable associated with the dictionary key (in this case, it would be the laugh). And if it doesnt, just do the base behavior.
      im sure theres a reason why this is a bad idea, but I dont know what that is. What do you think about this solution?

    • @whilefree
      @whilefree  9 місяців тому +6

      Your solution works. But what I'm trying to achieve here is the ability to easily "reuse" what I make in all projects, so I don't have to recreate things again and again. Search for "Observer Pattern", "Mediator Pattern", and "Modular Design" to read more about it.@@Ismail_NotFullName

    • @haiiry
      @haiiry 8 місяців тому

      @@whilefree Hey, you've come up with an interesting approach, to clarify it for myself, basically you just created a dictionary ? (or rather Receiver:Function, but as it goes from Attacker to Receiver to Function, it can be simplified to Attacker - Function) Where each key is a child node and it's corresponding value is a function in node?

    • @whilefree
      @whilefree  8 місяців тому +1

      A new video will be published in a day or two, where I demonstrate the actual system. However I don't go into the code because it's too long for a short video and requires a lot of Editor Scripting. I just highlight how the system works from a general point of view. So stay tuned for that. :)@@haiiry

  • @MrAbrazildo
    @MrAbrazildo 9 місяців тому +15

    1:39, it's not terrible. Quite the opposite: it's faster, easy to read, doesn't add unnecessary concepts/things to deal later and it's most used in AAA games. Single Responsibility is not broken, because the f() is taking care of what happens when hit. It's only 1 task.

  • @valentinooscarcollazo5236
    @valentinooscarcollazo5236 9 місяців тому +12

    I think this approach introduces a lot of extra work, while not really making any improvements. Lets say you have a few classes that can be hit by all "25" types of killer zones in your game, then you would have to add the 25 receiver nodes to each class, and in most cases, connect all of them to just a couple of different methods. Of course the actual result is fine, it is not entangled or coupled but its just extra work with no benefit.
    I have to say though that I also kinda dont like the has_method approach, since you may want to use a hit function for other stuff, and you are kinda relying on the name of a function, which could lead to errors... it gets the job done but I do agree is not the best.
    A simpler approach would be to just have a reusable component/node called "htitable/hitbox/etc..", that is detected by the hitters, and receives an attack/hit Object with whatever important info, and then sends it to the "dragon" or "farmer". Then each class manages that data as they please. No coupling, highly maintainable, easy and quick.
    Glad to have content creators worried about writing better code!

    • @whilefree
      @whilefree  9 місяців тому

      Thanks for great insight!
      I do agree with you that adding 25 different receivers is not the best thing to do (it's actually terrible xD). But I have to keep things simple and gradually increase the complexity as the series goes on.
      With some clever Editor Scripting, all the receivers can be managed using 1 single receiver node. My plan is to resolve the issue you addressed and I think the final implementation would be kinda like what you described.
      But it would take a while till the series reaches that point of complexity.

  • @Terminator85BS
    @Terminator85BS 9 місяців тому +6

    nice video, really happy to see more takes on this topic. Trying to prevent spaghetti code is by far my biggest challenge right now.
    that said, i'm not sure if i'd go for this approach. Kinda agreeing with Skaro11 that it's overengineered, having an extra node for each kind of killer on each character feels like massively increasing work for little benefit.
    either way, thanks for posting and adding input to the topic, i totally agree with you that has_method is not the way and i'm sure we'll move to something better if people like you keep working on it.

    • @whilefree
      @whilefree  9 місяців тому +2

      Thank you for sharing your feedback!
      The benefits the solution provides is more evident when you use it in more complex systems. I had to keep the scenario simple to make it easier to understand.
      You don't actually need to have 1 receiver node for each killer type. The sender-receiver system can be twisted and improved. The one I provide here is just a basic example.

  • @sheepcommander_
    @sheepcommander_ 9 місяців тому

    Thank you!!! It's an obvious solution in hindsight but so many people have instructed people to "Just duck type it!" that I didn't think about it...

    • @whilefree
      @whilefree  9 місяців тому

      You are welcome! :)
      More advanced content are on the way. This is not the whole thing. Stay tuned.

  • @bbrainstormer2036
    @bbrainstormer2036 9 місяців тому +5

    Man, I wish godot had interfaces

    • @whilefree
      @whilefree  9 місяців тому +1

      There is a way to implement "fake" interfaces in Godot. But my strategy is to get things out of the code section as much as possible. It speeds up the workflow in the long run if modularity and reusability are maintained.

    • @ze2like
      @ze2like 7 місяців тому

      Nodes are the secret sauce.
      It's hard to grasp at first, especially when coming from traditional OOP.
      The Godot idiomatic way of having an interface implemented seems to be the following:
      - create a script that extends Node with your desired class_name, say IntfNode. it will serve as a contact for your interface, and will be listed in the New node... dialog
      - create a node inside where you want to implement the interface. that node will extends from the previously created IntfNode class_name
      - extend the script on the newly created node to implement the desired behavior

    • @bbrainstormer2036
      @bbrainstormer2036 7 місяців тому +1

      @@ze2like Yeah, that's true. I just wish there was a way to mark a node class as abstract. That would help greatly. I guess there is if you use c#.

    • @ze2like
      @ze2like 7 місяців тому

      @@bbrainstormer2036 that would be nice, indeed, either in the form of interface or abstract class !
      That... Or a full-blown Traits system :)

  • @luckyknot
    @luckyknot 6 місяців тому

    Love your sense of humour, nice insights for decoupling code!

  • @ryanscott642
    @ryanscott642 7 місяців тому +8

    I think its so funny the people that don't see the value in this. I very much appreciate this approach because you are right, the single hit function just doesn't cut it when your games get bigger. I think this is an elegant solution.
    Thanks for making videos about more advanced patterns. Godot has needed this!

    • @whilefree
      @whilefree  7 місяців тому

      Thank you for sharing your insight. I'm glad you find value in my videos. :)

    • @Archkyrie11
      @Archkyrie11 5 місяців тому

      They don't see the value because there really isn't much added value. The Hit() method then delegating what happens when the object is hit is perfectly acceptable, is able to scale, and definitely does not break single responsibility because the functions single responsibility in this example is to determine how the object should proceed when it is hit. This is an over engineered solution to a problem with an already existing solution

    • @thomasarp5895
      @thomasarp5895 5 місяців тому

      @@Archkyrie11 Having a common hit() function violates the open-closed principle (I think mentioning SRP is a mistake from @whilefree - hit() alone does follow SRP, but anytime I see a switch with "enums", i die a little inside. Those are notoriously breaking when you alter the list of constants).

    • @Archkyrie11
      @Archkyrie11 5 місяців тому

      @@thomasarp5895 you are completely wrong on the open/close principle here. This doesnt violate that at all. Having a base hit() function in something like a parent class or an interface(which i know you cant do in godot) and having children implement the specifics of the hit function is 100% a valid way to go about this.

    • @thomasarp5895
      @thomasarp5895 5 місяців тому

      @@Archkyrie11 Please explain how that approach follows the open/closed principle. From wikipedia:
      "In object-oriented programming, the open-closed principle (OCP) states "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification";[1] that is, such an entity can allow its behaviour to be extended without modifying its source code."
      The whole point of composition and the reason game engines use it so extensively is to _attach_ functionality to existing objects. If you need to alter the source code of every mob in your game when you alter an enum, you are obviously breaking the "extending without modifying the source code" part? Please tell me how I'm wrong.

  • @widearchshark3981
    @widearchshark3981 8 місяців тому +2

    As somebody attempting Godot YT videos myself, I appreciate that you are putting tutorials to try and help people out.
    But this... This ain't right. The Single Responsibility Principle isn't "broken". The "hit" method is still dealing with being hit. I feel like your solution is just as complex as what you are saying is a problem!
    And of course, you could use layer masks. If you're saying that the dragon isn't hurt by fire, then keep the killable object off of the "fire" layer.
    But please, keep this stuff coming. The more people spreading the good word of Godot, the better!

    • @whilefree
      @whilefree  8 місяців тому

      Thank you for sharing your insight! :)
      Of course you can use the "hit" function as a "mediator" here which leads to different functions like "laugh", "die" whatever. But the way I've set it up makes it way more elegant.
      Have a look at this video I just uploaded to see how you can enhance the system to handle the problem once and forever:
      ua-cam.com/video/b850VmiYpbI/v-deo.html

    • @widearchshark3981
      @widearchshark3981 8 місяців тому

      @@whilefree Hey mate. Thanks, but this video is nearly incomprehensible! I'm still not clear on what you're achieving. This code if anything is more confusing to understand ! I'm still not clear on why you couldn't use collision layers/masks in your example. Perhaps you were thinking more generically about the problem?
      I wish you the best for future videos. Honestly, I'd suggest making them longer and clearer. Saying "not for the scope of this video" is REALLY frustrating, given that's what people have come here for.

  • @ze2like
    @ze2like 7 місяців тому

    Thank you! You helped a developer to understand Godot better ! It's hard to get it right at first 😅

    • @whilefree
      @whilefree  7 місяців тому

      You are welcome! :)

  • @Alexey_Pe
    @Alexey_Pe Місяць тому

    First I came up with this concept, then I implemented it, then I found your video.
    For me, these are raycasts and static bodies, when the ray touches the receiver, I check the class using "is", then the raycast node emits a "connection" signal then the node components connected to the signal themselves determine what to do with it. There is also a connection signal from the receiver side.

    • @whilefree
      @whilefree  Місяць тому +1

      Thanks for sharing your knowledge! :)

  • @Soroosh.S83
    @Soroosh.S83 9 місяців тому +6

    Thanks and This techniqe is good but need more explaination in depth and not just a fast paced example

    • @whilefree
      @whilefree  9 місяців тому +2

      This video is not intended to be a "slow" step by step guide. The viewer's time is valuable to me. If you find it hard to follow up, simply slow down the video and pause at critical moments.
      The code provided is a starting point for you to create your own Signal-Interaction systems.
      Good Luck Coding! :D

    • @earth2george
      @earth2george 9 місяців тому +2

      I agree with Scroosh.. This lesson/tut is amazing, but I had to rewind 4 or 5 times just to get it.
      That said: Excellent Lesson, thank you! - And I would definitely be interested in more thorough videos in the future if you make them.

    • @hiiambarney4489
      @hiiambarney4489 9 місяців тому +5

      I dunno though. Gotta say we are not viewing tik toks here we intend to learn something that can be valuable to us in a hobby / job that is already at odds with general attention span decrease. Not saying this vid should be 10 minutes for this one topic but some of this "valuable" time could have been distributed more, let's say, favorably towards teaching.@@whilefree

    • @whilefree
      @whilefree  9 місяців тому +2

      I'm working on a more detailed explanation. It will be released soon. :)

  • @slimebuck
    @slimebuck 8 місяців тому

    i learnt the power of reusable code. I once had a project with 20 different enemies, multiple chests, item pick ups, destructable walls, and it all had its own scripts and code.
    I re made my project using everything I had learnt about coding to make it better than before, and I went from hundreds of scripts to... about 6 scripts.

  • @waporwave5066
    @waporwave5066 9 місяців тому +2

    i would be careful to assume that people disagreeing with your method do not believe in reusability, or mean to insult you. I do see the problem here and how some solutions would cause headaches to expand upon later, but I'm not sure your method is the best way of resolving this. I'll think about this problem some more because you did bring up an interesting point.

    • @waporwave5066
      @waporwave5066 9 місяців тому +1

      like the reliance on strings, which have no garuntee to actually be the same name as a method on the object. Could possibly be very many instances of function names and receiver names, and if these change or were mistyped, now no receiver is registered, or function call fails. Which brings it back to a similar state of checking if a function exists. I do agree tho that having one event with multiple reactions becomes a problem if we just have one function.
      btw, here is perhaps a example scenario more grounded in shared game knowledge:
      "We have spikes; if a players touches them they die, if an armored enemy touches them, they bounce away.
      We don't want the spike code to know who hit it. This moves the behaviour of an object away from one place, and towards many different objects, which also makes the spike code longer and less related to it's function."
      A hit() function, and a receiver node are both acting as receivers here. The spike sends out a signal to listeners, and then each object implements it differently. I do think a hit() could be a little obscure, but in your method I feel like I would end up spending that time instead writing out and checking correct object and function names, and implementing this duplicate sender receiver code for each object. I'm mainly thinking, that there is probably a better way to implement a cleaner solution using built in godot stuff.

    • @whilefree
      @whilefree  9 місяців тому +1

      That's just a joke to make people laugh! xD
      I did thank everybody who posts comments right after that funny scene and asked them to repeat it. There is no right or wrong way of doing things. It just depends on the project.

    • @whilefree
      @whilefree  9 місяців тому

      @@waporwave5066 Thanks for the insightful comment.
      There are smarter ways to refer to the receiver. I used strings to make the code simpler and be able to just focus on the solution.

  • @fmmaks
    @fmmaks 6 місяців тому

    polymorphism is everything

  • @zenovak5177
    @zenovak5177 9 місяців тому +4

    I think Implementing this via a dependency injection would be a cleaner approach to solving this.
    Instead of using a bunch of signals, we create a bunch of method that a dragon-like object can do depending on the killzones:
    Where NPC inherits from the character node / game mechanics node tied to the engine
    public class Dragon: NPC {
    public override void gotHit() ...
    public override void gotFire() ...
    public override void gotWater()...
    }
    Then inside each different kill zones would do:
    (firezone)
    public void OnBodyEnter(Node body) {
    NPC npc = body as NPC;
    npc.gotFire()
    }
    (waterzone)
    public void OnBodyEnter(Node body) {
    NPC npc = body as NPC;
    npc.gotWater()
    }
    But Im not too sure how this will scale compared to your solution 🤔🤔🤔

    • @whilefree
      @whilefree  9 місяців тому +2

      This is also a valid solution. I'm not sure about the scaling either, but this approach makes it harder (imo) to reuse your objects in your future projects. What I'm after is a workflow which enables you to reuse your objects cross-project.

    • @barebonesdev
      @barebonesdev 8 місяців тому +1

      ​ @whilefree is object / code reuse really the bottleneck when using godot? How much does it help to make your code cross-project? How many projects are you working on where you can share code that's built like this? How many of your games share code? Genuinely curious, I've not used Godot extensively for multiple projects

    • @whilefree
      @whilefree  8 місяців тому

      I'm a Unity user migrating to Godot. These videos are about the concepts I've implemented in Unity long ago. Now I'm porting them to Godot. I've tested it in Unity and it worked pretty well in different prototypes. Have a look at this Unity talk for more info:
      ua-cam.com/video/raQ3iHhE_Kk/v-deo.html&pp=ygUcVW5pdHkgdGFsayBnYW1lIGFyY2hpdGVjdHVyZQ%3D%3D
      And stay tuned for the moment I combine these concepts with State Machines... ;)
      @@barebonesdev

  • @ElectricNox
    @ElectricNox 8 місяців тому +1

    In addition to this being messy, I think the simplest method for your "hit" example would simply be only disabling collision for the flaming dragon character with all bodies tagged "fire", no?

    • @whilefree
      @whilefree  8 місяців тому

      You can do that, but it's not as flexible as the way I tackled the problem. Disabling the collision mask ignores the collision entirely, removing the chance of making the dragon "laugh" upon contact.
      The way I did it makes it possible to change the reaction of the dragon in real-time, maybe based on its powerups, states, etc.
      So it's not just a matter of putting objects into groups. It's a matter of enabling them to switch responsibilities if necessary.

    • @SuperParadine
      @SuperParadine 4 місяці тому

      @@whilefree
      Your way is also not flexible. Lets say I have a unit that attacks with a flame-water bullet, that should hurt all types of creatures. In this situation your dragon will laugh and die at the same time.

    • @whilefree
      @whilefree  4 місяці тому

      @@SuperParadine I don't get what you mean. The flame-water bullet is a new type of sender with a different effect.
      Anyways, this video is going to teach you the OOP philosophy necessary for managing large projects. I've created an addon based on these concepts which helps you maintain all the OOP principles discussed in the whole series. First tutorial + download link:
      ua-cam.com/video/Zl3vo9aSKlY/v-deo.html

  • @valerianosky
    @valerianosky 9 місяців тому

    Nice video, while it's true that in the end I couldn't follow the example without pausing and going back and foward a couple of times.

    • @whilefree
      @whilefree  9 місяців тому +1

      Thank you for sharing your feedback!
      That's how you are supposed to follow such a video. The general reaction of people (based on UA-cam analytics and number of subscribers) is that people are really enjoying this type of editing. I got more than 250 subs in less than a month!
      However, I'll try a slower-pace video soon to see what happens. Because many people are complaining in the comments section it's hard for them to follow the tutorial.

  • @emmanueldolderer1225
    @emmanueldolderer1225 24 дні тому

    I feel like all this is doing is basically trying to simulate an interface with nodes, although this would be much better handled by all the different types of NPC/entity objects inheriting from a single abstract Entity class or something to that effect. Then, you just write “if collided_node is Entity” when checking what entered the damage zone. Then, inside of the Entity class you could define an abstract function that takes in a Hit object from the hit source that specifies things like damage dealt, type of damage (fire, water, etc.), and so on. Then, the entities that inherit from the Entity class can override this function to react differently to different types of damage. Much more graceful and it works much better with Godot’s node inheritance philosophy.

  • @baron523
    @baron523 8 місяців тому +2

    A lot of people in this comment section don't understand the danger has_method has. The magic string reference will not autofill, you will eventually refer to a non-existent string and you will not get an error but the functionality you intend will not occur. You need errors to debug things that don't work properly in an efficient manner. Watch Tutemic's video on implementing interfaces in gdscript. I'm seeing a lot of defense of bad architecture 'because AAA does it'. Yeah, and Diablo 4 can't implement more stash space because every client loads every player's stash because they can't figure out dependency. I'm honestly bewildered at how many people are poo-pooing the observer pattern.

    • @whilefree
      @whilefree  8 місяців тому +1

      I hope when I become a BIG UA-camr and I create my Godot OOP Solution things may start to change.
      Specially when they see me make a new game mechanics in a few minutes, based on reusable modules created before.
      I'm not gonna surrender, and I will never worship the DEPENDENZI GOD xD
      I either make games in the "proper" way or I don't make games at all! >:)

    • @Andersmithy
      @Andersmithy 4 дні тому

      This doesn't even remotely remove the magic string of has_method. In fact it introduces more. Not only is the name of the receiver node stored as a string in the sender, the method the receiver called is also stored as a string. This literally magic string searches for a child, and the child calls has_method on it's parent.

  • @Ryan-lp3qw
    @Ryan-lp3qw 9 місяців тому +6

    I’m sorry but I wouldn’t pick apart your videos if you just shared your experiences with the engine. But you giving bad advice with authority and throwing shade at other Godot tutorial videos, which is very confusing for new developers who are trying to learn the engine. Here are some thoughts:
    1) You are being too strict about the Killer being blind to the Target. If you blindly defer the resolution to the Target, you can’t do anything with the Killer area and that has it’s own problems. For example, in your last video, if the bullet collides and kills an object, you probably want to queue_free() the bullet, otherwise it would keep flying forever and one bullet could kill hundreds of enemies. Same thing here, if you have say a water shield and walk into a flame, maybe that Killer area should be put out. You can’t do these things if you are blind to what body has entered the Killer’s area.
    2) If the problem you are trying to solve is nested if statements you can use guard clauses, flags, type checks, and switch statements. If you are trying to avoid if statements altogether, or an “if jungle”, you can use types and polymorphism.
    3) hit() is a perfectly valid solution to the design problem in the last video. I would not recommend what you mentioned around the 1:38 mark, as that would break SRP as you pointed out.
    4) Exporting string pointers is usually not the best to do because names change, people misspell things, etc. I still do it for some things, but I wouldn’t in this case. For the DeathZoneReceiver, it would be better to export the Node itself (exporting node references is new to Godot 4). For the function calls you can create an exported enum, so that a designer can select a list of available functions from a drop down menu.
    5) While I think this design is better than your last video, I wouldn’t do this because it is over-engineered. You need to add nodes for every Killer type onto every Target type to effectively do what you could have done with a switch statement. Nodes are not the most lightweight object and can add up quickly if you use them as components a lot. Using exported Resource scripts can help with this, but I would just use the if/switch statements.
    On a fireball Killer area, I would just do:
    func _on_body_entered(body) → void:
    if not body is Actor:
    return
    if body.type == ActorTypes.FIRE:
    body.laugh()
    if body.type == ActorTypes.GRASS:
    body.take_dmg(body.health)
    if body.type == ActorTypes.WATER:
    queue_free()
    There are a million ways to do this, and expand upon it of course. But in my example, the Killer has knowledge of what body entered the area, and you can perform logic to the area itself, which is critical to things like a traveling bullet. I think your idea of dynamically changing types is interesting, but this can also be solved in simpler, more memory friendly way, like exporting flags for the weaknesses of the Target object. Also, as a side note, the memes in your videos take up way more screen time than actual productive code examples.

    • @whilefree
      @whilefree  9 місяців тому +3

      Thank you for your effort and the long insightful comment! I appreciate it. :)
      I have to strongly disagree with you here (on most aspects, but not all). I’m not giving bad advice. I do (and have done) extensive research to find a proper way to maintain Reusability, not only in a single project, but cross-project. Here I’m just applying the already existing and tested Design Pattern to Godot. It has been applied to Unity before. What you see in this video is a little piece of puzzle, glued to many other pieces, which in the end, is going to provide a neat, clean workflow for managing large, and even huge “extensible” projects.
      I really like and appreciate the way you grouped your opinion into sections. So let’s consider them 1 by 1:
      1) You completely missed the code in the video. I DID get access to the Killer inside the body, through the receiver. It’s passed as a parameter, and I even print it on the screen: “The dragon dies for touching WaterZone”. You are supposed to pause the video on critical moments. (I’ll explain why I do the edit in this way in a second). So, in my solution, you can do whatever you want to the zone.
      2) No, the problem isn’t just that. The if jungle is just a simple example to show the viewer that the hit function approach isn’t a neat one. The solution I provide enables you to reuse your objects, not only in the current project, but in the future ones. Of course it has a lot more to offer, especially when it comes to project management.
      3) A single function handling all kinds of “hits” is a really bad choice when it comes to complex systems and design pattern. I hope my future videos clear things up. Just imagine a huge project, and see how difficult it would be to alter a behavior or to extend one.
      4) I agree with you here. I just wanted to keep the code small and simple.
      5) You can always create a custom data-structure class if performance is a concern. But I don’t think the Killer-Killable scenario costs much on performance. Because the nodes do nothing until the collision signal is emitted. Are there going to be 1 million different Killer “types” colliding with 1 million Killable “types” at the same time? I don’t think the majority of Godot projects have so many interacting objects anyway. So practically, this won’t be an issue (imo).
      Finally, your sample code is what I strongly disagree with, because it just makes a Design Pattern disaster when it comes to large projects. I need to emphasize that the body DOES have access to the zone in the example provided in the video:
      Zone code:
      func _on_body_entered(body):
      if body.has_node(body_entered_receiver):
      body.get_node(body_entered_receiver).on_body_entered(self)
      Receiver code:
      func on_body_entered(sender):
      print("Body entered the zone " + sender.name)
      if body_entered_function:
      body_entered_callable.call(sender)
      Dragon code:
      func laugh(_body):
      print("The dragon laughs for touching " + _body.name)
      func die(_body):
      print("The dragon dies for touching " + _body.name)
      Finally) Why do I edit it like this? Because the ordinary way of editing got 60 views and 1 subscriber in general. This new method gets 4000 views and 200+ subscribers in a single week. Which one do you think people like the most? In such a video you are supposed to pause the video in critical moments. The code can be paused and examined, the memes and jokes can’t.
      However, I’m gonna make a follow-up video, re-examining the concept in this video in a slower-pace format. The number of views/likes/subs is what determines how I “have to” edit. Making a video like the one you see here is at least 10X more time-consuming than a standard regular tutorial. Actually, this 4-minute video took 30+ hours to record and edit…. :|
      Thanks again for your attention and sharing your feedback! :D

    • @Ryan-lp3qw
      @Ryan-lp3qw 9 місяців тому +1

      ​@@whilefree Ah okay I see the body being passed, I apologize for misunderstanding this as a continuation of your last video. I think you got views because of your titles, that's how it got me. I wanted to find out why I was using signals wrong this whole time, so when you make a bold claim, you challenge yourself to have a bold design that all of us are missing. And if you don’t, you’re going to get some push back from experienced devs. Also if you don’t take more time to show your ideas, the newer devs are going to criticize you too. I genuinely hope you find your market audience for the amount of work you are putting into your videos, I know they aren’t easy to do.
      Going back to the code, I know my sample was not scalable, but if you have 3 death zones it’s an OK solution. Sometimes time spent making code more modular isn’t worth the effort. You now have to create nodes for every object henceforth and fill in the exported fields to get them to work. Where if you specify a little more in code, you don’t have to do that. I find this is often more readable because it’s encapsulated in fewer areas, and still re-usable in other projects with a little tweaking.
      I like and dislike some things about this method, I think it boils down to personal style and what the goals of the game are. For instance, you are restricting yourself to the effect of one function, ie “die” or “laugh”. I would rather have explicit logic (not as modular), performed in an “event” function that resolves the event. Also your die() and laugh() functions now take a “body” as input, which is not necessary to that functionality, and if you try to perform operations on the body, you need to check if it exists every time the function is called. There are plenty of situations where your actor will die or laugh and it’s not a result of entering an Area3D.
      If I was making a bunch of death zones in my finished game that requires more logic I would either use polymorphism, or most likely, dedicate a class to resolving death zone effects that takes the zone and body as input, attach it to the death zone and call inside the body entered callback. This doesn’t break SRP or encapsulation principles and you can extend as much as you want.
      This is good because as you scale, you may require more logic like: does the player have poison resistance? Do they have a poison blocking amulet? Will the player die from the next tick? Does this do damage to armor pieces in addition to health? It’s hard to consider these things with your modular example pointing to “die” or “laugh”, and if you do point to an “event” function that does all this, why offer the modularity in the first place?
      Also I’d be careful with adding a bunch of nodes like that. I throw nodes in my scenes too, but I try to limit them to encapsulating different functionality, not simply having them as unique pointers. I’ve never tested the upper limits to how many nodes can be in a scene, but I’ve seen people in forums with their bullet hell or rts games talking about a bunch of node objects slowing them down. It’s usually cause they have like 10,000k nodes in a scene, but all this takes is 200 units with 50 child nodes to get there. Don’t quote me on that, but it might be something to keep in the back of our minds as we make our games. If it can be solved without a bunch of nodes, I’d prefer to extend from the significantly lighter RefCounted or Resource and call those inside node objects.

    • @whilefree
      @whilefree  9 місяців тому +1

      You have valid points in your solution, but I haven't shared my whole idea yet, because it would probably take a 4 hours detailed video...
      Considering you don't like adding nodes, you'd probably don't like my design pattern, because it's based on turning everything into "lego pieces" and creating new systems by rearranging them.
      This is the approach I've chosen and I'll try to make games in that style as I go on. Whether it does a good job in a real project or not is what we'll see in the future.
      I again appreciate you for spending the time and sharing your valuable insight. :)@@Ryan-lp3qw

  • @TechCowboy
    @TechCowboy 9 місяців тому

    I think some diagrams are needed, because I'm not grasping the implementation.
    dragon walks into fire
    This has the dragon calling the receiver (via a method call?) found in which object? Is the receiver a different object from the dragon?
    I've been re-watching 2:18, so many times trying to understand your implementation.

    • @whilefree
      @whilefree  9 місяців тому

      Sorry to hear that. A new video is published which shows things in a more user-friendly format.
      The dragon calls a method inside the receiver if the body has the receiver as a child. The dragon is the "sender" here.
      However, putting the name of the receiver in a field and using it like that is not good practice. I just wanted to make the code simpler. In a future video I'll provide a more sophisticated method of accessing the names which reduces the chance of a typo to absolute 0%.

  • @tomtomkowski7653
    @tomtomkowski7653 8 місяців тому +1

    The correct answer: Interfaces + inheritance.

  • @jaumesinglavalls5486
    @jaumesinglavalls5486 9 місяців тому

    did you have this published in github or similar? I will like to take a look, at the code itself, without replicating it by myself, thanks!

    • @whilefree
      @whilefree  9 місяців тому +1

      The system is still not complete. In the future videos you'll see an improved version of it which better maintains OOP principles.
      My main plan was to share the code when it gets to a minimal level of efficiency. But for the moment to make it easier for you:
      The complete sender code can be found at 2:48
      The complete receiver code can be found at 2:56
      Hope it helps.

    • @jaumesinglavalls5486
      @jaumesinglavalls5486 9 місяців тому

      @@whilefree thanks, yeah, I will take a look, I expect next week, one question, I'm trying to do a Poc of a turn based strategy game like advance wars, and I'm wondering how to connect the units with the interface, I was thinking on a buss implementation or when the units/buildings are created connect the signals, (I already have a factory to build them) What's your opinion?

    • @whilefree
      @whilefree  9 місяців тому

      I'm still experimenting with Godot (Unity user migrating here), so I can't be 100% sure on how such a system is supposed to be designed, maintaining compatibility with my Architecture plan.
      However, my "guess" for the moment is that I'd use the same sender-receiver system here. It would be "builders-buildables" relationship. Adding appropriate "buildable" node to the unit, it would automatically "register" itself inside the valid buildable list. Then the builder would know what items to show in the interface.
      With some clever Editor Scripting, the system can become generic, and you would be able to add different unit "types" on the fly right inside the inspector.
      But this is far beyond what I've already covered in the channel. I have planned to make a video about "Pickers-Pickables" and after watching that, this comment would make way more sense.
      If it doesn't make any sense right now, it's okay... xD
      @@jaumesinglavalls5486

  • @steve16384
    @steve16384 2 місяці тому

    A very entertaining video, but im not sure your solution is the best way of doing it.

    • @whilefree
      @whilefree  2 місяці тому

      Thank you. Here I'm just showcasing the idea. The final version is available in my OOP addon, ReuseLogic Nexus. Have a look at this playlist:
      ua-cam.com/video/kJ1Zh1qCP7w/v-deo.html&pp=gAQBiAQB

  • @cannibalgoose8685
    @cannibalgoose8685 9 місяців тому

    In a game with a slightly larger scope, more nodes to be processed are more costly than a function with ifs and elses.
    In the long run, your 'solution' might affect the program's performance. It seemed to me that when trying to be within a paradigm, you ended up not thinking much about the performance consequences and other stuffs. The 'Single function, single responsibility rule' can and must be broken if you need: no cop will put you in jail for that. It's not a rule at all, it's just a guiding tip that you may follow when it's applicable.

    • @whilefree
      @whilefree  9 місяців тому +1

      Thanks for the feedback! :D
      In this channel the focus is to do things in the "proper" way. No sweeping under the carpet kinda thing.
      A single node doing nothing just waiting for a function call has almost 0 performance cost. And the logic only happens when there is a collision/condition. So I'm almost sure that there is no performance cost here.
      However, adding a new receiver for each kind of relation is not a neat solution. But I have to keep things simple and expand gradually as the playlist goes on. Later I'll show a new implementation which makes it possible to handle many-to-many relationship with only 1 receiver node attached. It will be possible to make the receiver sensitive to a wide range of "sender types". This way the number of necessary nodes will decrease dramatically.
      A little bit of clever Editor Scripting is needed though.
      Stay tuned for that!

  • @dnkbmc
    @dnkbmc 8 місяців тому

    Hmmm...I don't get it. Why is it bad for the Dragon to decide what happens to it inside the "hit" function? The killer sends an "Attack" object to the "hit" function, and the Dragon decides whether to laugh or to die based on the "Attack.type" and "Attack.damage".
    What if you have enemies with different resistances to certain types of attacks? Like it could be immune to one, take only 50% damage from the other and die instanctly from the third. This Sender/Receiver pattern only makes things more complicated and harder to follow.

    • @whilefree
      @whilefree  8 місяців тому

      This approach is going to maintain reusability, eliminating the need to re-design systems. Watch the rest of the videos in the series to learn more about it.

  • @vectorlua8081
    @vectorlua8081 9 місяців тому +1

    match > if jungle :)

    • @NonsenseNH
      @NonsenseNH 9 місяців тому

      match body.type:
      "fire":
      print("woah fire")
      "water":
      print("woah water")

  • @lucas_pscheidt
    @lucas_pscheidt 3 місяці тому

    wouldn't be easy to just use groups inside the hit function?

    • @whilefree
      @whilefree  3 місяці тому

      That does work too, but what I'm after is to create reusable modular systems which can be used in different projects with no modification needed. Watch the rest of the videos in the series to see what I mean.

    • @lucas_pscheidt
      @lucas_pscheidt 3 місяці тому

      @@whilefree ok, thinking now better, if I would use your way of thinking in a larger project, wouldn't it be bad that I would need to have dozens of nodes inside my character node for receiving different information? Genuine question, bc on your example you used "get_parent" to call the funcition after the receiver received the infomration if I am not mistaken, and if I were to have dozens of nodes, I would be better putting them all inside a singular node called "Receiveirs", but that would cause a problem because of the get_parent() that was used in each receiver, right? Sorry if I couldn't express myself right, english is not my main language

    • @whilefree
      @whilefree  3 місяці тому +1

      @@lucas_pscheidt You are absolutely right and this is exactly what I've done in ReuseLogic Nexus addon. A single receiver node must handle all receive types to prevent unnecessary node addition.
      This video is kinda old and is only discussing the concept. Step by step I've improved the system and turned it into a complete OOP Solution.

    • @lucas_pscheidt
      @lucas_pscheidt 3 місяці тому

      ​@@whilefree ​ that's so cool, I will try to use this system on my next project. I started learning godot and game dev this year and I want to start the right way, with clean code and scalable projects. I think your addon will help a lot on that, so thanks for sharing this with godot community. Also, I am so sorry for all those negative comments or critics, people only want to try to criticize others, but few people like you really try to make something better and new, and I appreciate a lot of people like you, so keep up the good work

    • @whilefree
      @whilefree  3 місяці тому

      @@lucas_pscheidt Thank you for your nice encouraging comment! :)
      As soon as the I release the first stable version of the addon (and make a comprehensive crash course on it) I'll start a discord server dedicated to the addon, and beyond that, any concept which helps manage projects in large scales. The contribution of people like you is what is going to help the community as a whole.
      I'm glad you found value in my content. If you had any questions, don't hesitate to ask. :)

  • @whilefree
    @whilefree  9 місяців тому

    Hey everyone! Hope you enjoy the content! The previous video got 200+ subscribers (in a week). The growth has been skyrocket! Let's see what this one does... Thank you for your great support! :D

  • @artemail6744
    @artemail6744 2 місяці тому

    obligatory algorithm comment

  • @trolleymouse
    @trolleymouse 9 місяців тому +2

    I have no idea what you're trying to say.

    • @whilefree
      @whilefree  9 місяців тому +1

      Thank you for sharing your opinion. Slower-pace tutorial is on the way, where I re-examine the concept.

    • @TechCowboy
      @TechCowboy 9 місяців тому

      @@whilefree I agree with you that the "has_method" system is extremely flawed, so I am very interested in avoiding using this approach, however, you zoom through your example. When/If you redo your tutorial, please walk through the routines that are called as you enter the body.

    • @whilefree
      @whilefree  9 місяців тому

      @@TechCowboy Many people were requesting this. So I made a more detailed explanation in a slower pace format. I guess I forgot to put the link at the end of the video. Here it is:
      ua-cam.com/video/1-47vcxG1WQ/v-deo.html

  • @michaelf.4341
    @michaelf.4341 8 місяців тому

    This is beautiful. And that's how component aggregation should work from my point of view. I was struggling with reference passing mess or has_method-trickle-down repetitions in Godot since I really miss interfaces. Love this and gonna try out soon.

    • @whilefree
      @whilefree  8 місяців тому

      I'm glad you liked the solution. Stay tuned for what's coming next. When I get to state machines the true power of decoupled modules will show up...

  • @astrahcat1212
    @astrahcat1212 8 місяців тому

    Maybe an odd opinion.....
    When you're beginning, like your first 3-4 years programming, you're just gonna make mistakes all over the place, and you might as well just keep making the thing work and do it anyway.
    Also, modular design is okay if you want, but worshipping modular design as you often see over the last 20 years is overrated and can slow you down, compared to really focusing and putting an insane amount of time and effort into naming conventions, organization, and commenting code cleanly, just keeping the code as clean as possible, and refactoring so functions/methods do one thing and one thing only.
    Remember some of the most technical games of all time weren't programmed with a modular programming style, for example Roller Coaster Tycoon was programmed in assembly, all of the games before the Saturn, PS1 and N64 era were assembly, that includes huge SNES JRPG games from Square Soft etc..., Final Fantasy 6, Chrono Trigger, Mario RPG didn't obsess over modularity, and didn't use OOP style, and were programmed in assembly. OOP design only took off when Java showed up.

    • @astrahcat1212
      @astrahcat1212 8 місяців тому +1

      Think of how for example a large corporate department store manages a large space. They have a godly amount of organization, organization and staying clean is absolutely everything. You walk into a Rite Aid or CVS or something and you see they re-arrange things over and over again, that's akin to refactoring and renaming things over and over again as your codebase ages to be cleaner. You want to be as non-lazy as possible at cleaning, always clean, refactor, and have your functions do a single function, and modularity you can have if the project calls for it, but if you're creating just a single game and you're gonna throw out all the code anyway at the end, modularity doesn't really matter so much, or if the system you're creating is just for yourself, or even if it's a larger project like an adventure game or RPG or something, you don't absolutely need to worship modularity like it's required, I know you're not personally, just so many are being misled thinking it's a requirement. Java didn't even exist in the 90s when most of the best engineers were on the job innovating.

    • @whilefree
      @whilefree  8 місяців тому

      Thanks for the feedback!
      Of course this kind of content is not for beginner programmers. However, whether OOP design accelerates development cycle or slows it down is a question which will be answered practically when I complete my module library in Godot and start making actual games.
      Stay tuned for that. :)

  • @devilmaz
    @devilmaz 5 місяців тому

    you're just proxying signal connections, why not just use signal connections lol

    • @whilefree
      @whilefree  5 місяців тому

      Keep watching the rest of the videos in the series to see what it's all about. ;)

  • @theyellowarchitect4504
    @theyellowarchitect4504 9 місяців тому +1

    Good video, good explanation, but the example was not clear at all. I am not referring to minor things like making the water collider blue so the viewer notices, but it wasn't even obvious who was the receiver and when he collided.

    • @whilefree
      @whilefree  9 місяців тому +1

      Thanks for sharing your feedback! I appreciate it.
      I'm surprised, because I did put "Sender Code" big on the screen when I was showing the sender code, and did the same thing for the receiver one.
      I'm gonna make a follow-up video where I re-examine the code in a slow-pace format. That's going to be the next video I upload. Stay tuned for that.

  • @olmrgreen1904
    @olmrgreen1904 8 місяців тому

    wtf