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

  Рет қаралды 15,721

while(free)

while(free)

Күн бұрын

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.or...
Released under public domain license (cc0). Special thanks to the author: juhanijunkala....

Пікірлер: 134
@whilefree
@whilefree 7 ай бұрын
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: kzbin.info/www/bejne/kJ2Wp6Jvlrh-ors
@valtarijunkkala
@valtarijunkkala 11 ай бұрын
I am going to be honest here, this feels like the most complicated solution you could come up with.
@whilefree
@whilefree 11 ай бұрын
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 11 ай бұрын
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 11 ай бұрын
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 11 ай бұрын
@@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 11 ай бұрын
​​​@@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 11 ай бұрын
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 10 ай бұрын
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.
@Skaro11
@Skaro11 11 ай бұрын
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 11 ай бұрын
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 11 ай бұрын
@@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 11 ай бұрын
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 11 ай бұрын
@@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 11 ай бұрын
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
@UnstoppableTigra
@UnstoppableTigra 11 ай бұрын
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 11 ай бұрын
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 11 ай бұрын
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 11 ай бұрын
@@cntrvsy word salad
@cntrvsy
@cntrvsy 11 ай бұрын
@@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 11 ай бұрын
@@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
@ryanscott642
@ryanscott642 10 ай бұрын
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 10 ай бұрын
Thank you for sharing your insight. I'm glad you find value in my videos. :)
@Archkyrie11
@Archkyrie11 8 ай бұрын
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 7 ай бұрын
@@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 7 ай бұрын
@@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 7 ай бұрын
@@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.
@MrAbrazildo
@MrAbrazildo 11 ай бұрын
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.
@widearchshark3981
@widearchshark3981 11 ай бұрын
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 11 ай бұрын
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: kzbin.info/www/bejne/mGmYYYmjnr6jmKs
@widearchshark3981
@widearchshark3981 11 ай бұрын
@@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.
@luckyknot
@luckyknot 8 ай бұрын
Love your sense of humour, nice insights for decoupling code!
@whilefree
@whilefree 8 ай бұрын
Thank you :D
@valentinooscarcollazo5236
@valentinooscarcollazo5236 11 ай бұрын
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 11 ай бұрын
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 11 ай бұрын
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 11 ай бұрын
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.
@Alexey_Pe
@Alexey_Pe 4 ай бұрын
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 4 ай бұрын
Thanks for sharing your knowledge! :)
@bbrainstormer2036
@bbrainstormer2036 11 ай бұрын
Man, I wish godot had interfaces
@whilefree
@whilefree 11 ай бұрын
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 10 ай бұрын
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 10 ай бұрын
@@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 10 ай бұрын
@@bbrainstormer2036 that would be nice, indeed, either in the form of interface or abstract class ! That... Or a full-blown Traits system :)
@sheepcommander_
@sheepcommander_ 11 ай бұрын
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 11 ай бұрын
You are welcome! :) More advanced content are on the way. This is not the whole thing. Stay tuned.
@slimebuck
@slimebuck 11 ай бұрын
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.
@whilefree
@whilefree 11 ай бұрын
Exactly!
@ze2like
@ze2like 10 ай бұрын
Thank you! You helped a developer to understand Godot better ! It's hard to get it right at first 😅
@whilefree
@whilefree 10 ай бұрын
You are welcome! :)
@steve16384
@steve16384 4 ай бұрын
A very entertaining video, but im not sure your solution is the best way of doing it.
@whilefree
@whilefree 4 ай бұрын
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: kzbin.info/www/bejne/oXuUi5tnpqiDbdk&pp=gAQBiAQB
@waporwave5066
@waporwave5066 11 ай бұрын
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 11 ай бұрын
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 11 ай бұрын
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 11 ай бұрын
@@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.
@baron523
@baron523 11 ай бұрын
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 11 ай бұрын
I hope when I become a BIG KZbinr 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 2 ай бұрын
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.
@tomtomkowski7653
@tomtomkowski7653 11 ай бұрын
The correct answer: Interfaces + inheritance.
@NesiAwesomeness
@NesiAwesomeness 4 ай бұрын
Exactly
@zenovak5177
@zenovak5177 11 ай бұрын
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 11 ай бұрын
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 11 ай бұрын
​ @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 11 ай бұрын
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: kzbin.info/www/bejne/qJK0ZJx-naqSgc0&pp=ygUcVW5pdHkgdGFsayBnYW1lIGFyY2hpdGVjdHVyZQ%3D%3D And stay tuned for the moment I combine these concepts with State Machines... ;) @@barebonesdev
@ElectricNox
@ElectricNox 11 ай бұрын
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 11 ай бұрын
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 7 ай бұрын
@@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 7 ай бұрын
@@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: kzbin.info/www/bejne/kJ2Wp6Jvlrh-ors
@Soroosh.S83
@Soroosh.S83 11 ай бұрын
Thanks and This techniqe is good but need more explaination in depth and not just a fast paced example
@whilefree
@whilefree 11 ай бұрын
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 11 ай бұрын
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 11 ай бұрын
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 11 ай бұрын
I'm working on a more detailed explanation. It will be released soon. :)
@emmanueldolderer1225
@emmanueldolderer1225 3 ай бұрын
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.
@lin7105
@lin7105 Ай бұрын
I'm a little slow.. This video is quite old, but Im still gonna ask this question Basically every "entity" such as player would have a receiver that has the function that should be called? Like the firezone tells the receiver that something has touched it, and the receiver (which is unique to the "entity") knows what function inside the entity should be called?
@whilefree
@whilefree Ай бұрын
Kinda yes, but this is only the demonstration of the idea. Watch the rest of the series to see the actual OOP solution.
@Britishblue.
@Britishblue. Ай бұрын
Ive been learning godot. I have an item system where different items have different use cases. Each item has an "item_use" variable. The player has an item component and decides what the item should do based off the "item_use". For example if the use is "mine" an item swing component will be made which swings the item. If its "place" an item place component is made which places the item. So if I want a sword and a pickaxe, i can reuse the swing component for both of them and make seperate "mine" and "damage" components depending on if the item is a pickaxe or a sword. Whats the best way to access my components and scenes? Currently im just preloading the swing / place component scenes at the start of my item component script. And in my swing component I need to access my tile map so I can handle the breaking of blocks. But the tilemap doesnt show up in the inspector if i use @export so I have this long piece of code that finds the correct node. @onready var worldTileMap : TileMapLayer = get_tree().current_scene.get_node("WorldTileMap"). Is there any way for me to just set it in the inspector to clean it up a bit ? This is the one thing thats a pain is actually accessing the other nodes. Thanks
@whilefree
@whilefree Ай бұрын
I have an inspector scripting series in my channel. Have a look and see if it gives you clues on how to find the solution to the problem. You need to read a lot of documentation to find out how to show things in the inspector, and I've tried to demonstrate in my videos on how to find the information inside the documentation efficiently.
@dnkbmc
@dnkbmc 10 ай бұрын
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 10 ай бұрын
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.
@fmmaks
@fmmaks 9 ай бұрын
polymorphism is everything
@TechCowboy
@TechCowboy 11 ай бұрын
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 11 ай бұрын
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%.
@cannibalgoose8685
@cannibalgoose8685 11 ай бұрын
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 11 ай бұрын
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!
@Ryan-lp3qw
@Ryan-lp3qw 11 ай бұрын
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 11 ай бұрын
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 11 ай бұрын
​@@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 11 ай бұрын
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
@jaumesinglavalls5486
@jaumesinglavalls5486 11 ай бұрын
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 11 ай бұрын
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 11 ай бұрын
@@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 11 ай бұрын
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
@trolleymouse
@trolleymouse 11 ай бұрын
I have no idea what you're trying to say.
@whilefree
@whilefree 11 ай бұрын
Thank you for sharing your opinion. Slower-pace tutorial is on the way, where I re-examine the concept.
@TechCowboy
@TechCowboy 11 ай бұрын
@@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 11 ай бұрын
@@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: kzbin.info/www/bejne/Z16XaKmZraxkjbM
@lucas_pscheidt
@lucas_pscheidt 6 ай бұрын
wouldn't be easy to just use groups inside the hit function?
@whilefree
@whilefree 6 ай бұрын
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 5 ай бұрын
@@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 5 ай бұрын
@@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 5 ай бұрын
​@@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 5 ай бұрын
@@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. :)
@NerdGlasses256
@NerdGlasses256 Ай бұрын
This seems so insanely complicated that I won't care about it at all,instead I'll just keep using has_method() and if it happens to turn into unmanageable spagetti code, that's a problem for future-me.For now I feel like I'd just have to implement a diferent system that produces the same result, but works tidily and efficiently with has_method. If I can't do that, then f it my code will be shitty, and then what? Technically all code is kinda shitty, it just has to work.
@valerianosky
@valerianosky 11 ай бұрын
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 11 ай бұрын
Thank you for sharing your feedback! That's how you are supposed to follow such a video. The general reaction of people (based on KZbin 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.
@michaelf.4341
@michaelf.4341 11 ай бұрын
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 11 ай бұрын
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...
@vectorlua8081
@vectorlua8081 11 ай бұрын
match > if jungle :)
@NonsenseNH
@NonsenseNH 11 ай бұрын
match body.type: "fire": print("woah fire") "water": print("woah water")
@whilefree
@whilefree 11 ай бұрын
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
@theyellowarchitect4504
@theyellowarchitect4504 11 ай бұрын
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 11 ай бұрын
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.
@astrahcat1212
@astrahcat1212 11 ай бұрын
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 11 ай бұрын
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 11 ай бұрын
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. :)
@artemail6744
@artemail6744 5 ай бұрын
obligatory algorithm comment
@devilmaz
@devilmaz 8 ай бұрын
you're just proxying signal connections, why not just use signal connections lol
@whilefree
@whilefree 8 ай бұрын
Keep watching the rest of the videos in the series to see what it's all about. ;)
@olmrgreen1904
@olmrgreen1904 10 ай бұрын
wtf
Зу-зу Күлпаш 2. Интернет мошенник
40:13
ASTANATV Movie
Рет қаралды 558 М.
How it feels when u walk through first class
00:52
Adam W
Рет қаралды 20 МЛН
小蚂蚁会选到什么呢!#火影忍者 #佐助 #家庭
00:47
火影忍者一家
Рет қаралды 99 МЛН
Using Composition to Make More Scalable Games in Godot
10:13
Firebelley Games
Рет қаралды 235 М.
I Created My Own Custom 3D Graphics Engine
26:29
Inkbox
Рет қаралды 93 М.
Godot Scripts I add to Every Game
12:34
Aarimous
Рет қаралды 26 М.
Immersive Sim in Godot 4: COGITO - Overview
8:22
Philip D
Рет қаралды 43 М.
Let's Program Doom - Part 1
25:13
3DSage
Рет қаралды 441 М.
How Games Make VFX (Demonstrated in Godot 4)
5:46
PlayWithFurcifer
Рет қаралды 350 М.
Optimizing my Game so it Runs on a Potato
19:02
Blargis
Рет қаралды 620 М.
How You Can Easily Make Your Code Simpler in Godot 4
6:59
Bitlytic
Рет қаралды 440 М.
Зу-зу Күлпаш 2. Интернет мошенник
40:13
ASTANATV Movie
Рет қаралды 558 М.