I'm not pointing this out to be pedantic, I promise, but what you've called a behavior tree is a hierarchical state machine. A behavior tree is a fundamentally very different approach to a logic control structure. The ONLY reason I point this out is because you have an audience of new developers and this could cause a lot of confusion over time as this misunderstanding propagates outward and with state machines and behavior trees both being fundamental approaches to game AI. Similar to the way Roblox kids call incremental/idle games "simulators", which are something else entirely, or how so many younger people say "literally" when they mean "figuratively". It creates a communication barrier between two generations that makes it more difficult for lessons to be passed down. New devs have to fight through a lot of confusion and endure a lot of frustration in their journey to becoming intermediate devs and these kinds of misunderstandings can make the harder parts of the learning curve even harder. The misnomer aside, you did an excellent job of communicating some useful concepts that are usually hard earned through experience and that's very difficult to do well. I sincerely hope you keep making content like this because you're an excellent communicator and we really don't have enough of those in the world.
@betatester03 Yeah I know I put it in the description of the video that I mixed those up. I was looking for a different name to call the nodes and I called it a "Behaviour Tree" and it stuck I had no idea my bad. I made the correction in the description
Not a new developer but a thankful one. I've encountered my fair share of incomplete or confusing tutorials developing my game (vids under channel). I appreciate the effort to help the community !
just to play off you're pedantry, the word "literally" has been used to mean "figuratively" for basically as long as the word has recorded use, so lets not blame the kids for this one. the video lays out the logic behind what has been made so really the only problem that can arise is a viewer using the term "behaviour tree" to some 3rd person whom either corrects the viewer or misunderstands what they're talking about. which really isnt that big of a deal (i mean if they're actually in a serious production situation, then it would be corrected by someone senior)
Massive respect for the way you voiced your opinion in a thoughtful, constructive and still courteous way- god I wish more people spoke with respect like this. ❤ Also mad respect to the channel owner for acknowledging and responding in a reasonable manner too ❤
This coding style is one I've gradually moved towards over time. I didn't realize it until watching this. I always thought I was using inheritance, but it turns out I was employing a hybrid approach. I use this method when writing full-stack programs, and it has saved me so much trouble because it's easier to swap out modules and functions without breaking everything.
I really appreciate your explanation, Nesi. This is exactly why I dropped modding ArmA 3 and decided to just make my own damn game with Godot. Everything custom in A3's config scripting inherits from base classes at the top of the game asset inheritance tree, which results in having to redefine or add unnecessary lines to turn off certain features to each custom class, while the original class is still technically the overriding class, meaning to add new stuff you have to inherit from the base class all of its code, and if you want to change anything in your custom class at the bottom you usually have to hunt down and modify lines that pertain to what you want to change. In this example of a state machine, you can not only pick and choose what to inherit from but you're not stuck with pre-packaged components you might not even use. It's just smarter and more efficient to work with.
The problem with inheritance is not necessarily itself, but the fact that people use it in places that they are not supposed to. It is a principle like any other in software development/computer science, and like any other principle, you need to think about if, and how you should implement it. Its common use probably stems from the fact that it gets taught the earliest in university/courses, and alternatives are taught either later or not at all. A different way to mitigate the problems that you referenced is the decorator pattern, but I would not recommend it, since it can get overly complicated quick, especially if there is a lot of divergence between the classes that exist. I am currently not involved in any projects that would need OOP principles, but if I find myself in a situation like that in the future, I will give it a try. I enjoyed the video, good job. The editing is really good.
The problem with inheritance is it's not visible. Nothing warns you that the class you're editing is a child. Only if it's a parent and only one level deep. Composition flattens the inheritance tree and makes code intention very transparent. The video's example of composition wasn't correct. Composition shouldn't have multiple levels. It's all at the top.
As an OOP pilled dev, the randomize function seems like an oversight in the first place. That sort of manipulation of variables upon creation of the object would be better done in the constructor method. You can have as many constructors with different method signatures as you want. Usually, for this sort of operation, you would have a blank constructor, say, titan(). This would initialize the object, and you could use it to set values randomly. Then you would have another constructor, say, titan(String name, int hp, int speed) and set the in scope variables using those values. There would be no need for creating subclasses.
@@XionicalXionical it was a crude example, it's an excuse to just use Titans to explain because I thought it would be interesting. They're better examples, some issues I've come across were from a similar situation of something I predefined conflicting with something else I want to now inherit from. If you've never run into issue then that's amazing because like I said OOP is great they're just some cases. I'd also highly recommend trying Composition. I've found it easier, that's my personal preference
@@lunarthicclipse8219 There are ways to optimize it quite heavily. If I remember correctly I believe the Jak and Daxter games were made with a custom in house engine that was purely functional and made with Lisp.
Hey congrats on tackling that tech debt. It always surprises me that no matter how long I've been coding I always accumulate tech debt like this and have to go back to refactor it.
Good video structure. I can tell you that you're a lot easier to understand than other channels I've watched on UA-cam and the reason why that is, is because when you are about to explain something you take short breaks before explaining everything and even when you do explain everything you also are taking short breaks in-between each chunk of information to let the viewers mind catch up with the information you're saying. I would say that in the future try to think of doing this more often because it really helps the viewer (me) and also, I can say that it makes you stand out as a content creator as well. Keep up the hard work Nesi. 🙏
@@WhyDidItDo thank you, that was exactly what I was going for. It was something I decided one when I came back to watch my own video. I just felt like there were too many points where I had to actually pause. It's nice to know I made the right call, I will definitely continue
Loved this video. I've seen other explanations of behavior trees and state machines for game development that seemed to overcomplicate the communication between nodes. This was a great video!❤
Inheritance makes sense if you actually have shared logic, but it's easy to overuse because we have a tendency to want to create "logical" inheritance hierarchies. Having a ParticleEmitter base class the efficiently batch renders particles makes sense to me, the only reason you'd ever want to inherit from it is if you want to emit particles while for your titan example you could imagine a friendly titan that just caries you from place to place instead of attacking, in that case the inuitive thing to do is inherit from Titan, but the practical thing to do is inherit from something like Transporter or MovingPlatform
the problem is if you have an abstract motor class that all your motors inherit from, and you have like em drive or warp drive or rocket motor. only the rocket motor emits particles and you need to inherit from particle emitter AND abstract motor. (deadly diamond). something like ECS, or rust's traits solve this.
@@joshuathomasbird Forgot to explicitly mention it in my comment but inheritance also only makes sense when you actually need the shared API between descendants so you can swap them out interchangeably. The motor shouldn't inherit from particleEmitter because that's not its primary function, composition is the right pattern here. ECS is just one way to achieve composition, there are many more
@@pokefreak2112 I think for game development in general, Composition and functional programming is just the right way to go, and Inheritance/OOP really has no place. Sure, using Inheritance might make some things EASIER, but also makes your code messier and more likely to break in the long run. If you make as many things as possible their own separate, self contained, reusable module, then you avoid chain breakages where one messed up part of the program breaks everything else.
@@pokefreak2112 I think maybe you're missing my point about why multiple inheritance is problematic. the motor is just to visualize a concrete inheritance pattern that is easy to understand. if you don't like that one consider quadrilaterals: rhombuses, rectangles, and squares. If your pattern is supposed to compose behaviours objects can have but the pattern can't organize how those objects behave, then it would be incorrect to say that it is a way to acheive that, let alone claim that it's the "right" way. I hope you don't find that out the hard way, nor make someone else's life miserable standing on this hill.
@@joshuathomasbird Harsh. You'd need to provide more context what you're doing with those quadrilaterals. Personally I don't think I'd ever make a quadrilateral base class, but I could see myself making a "collider" class or "renderer" class where subclasses implement a collider or renderer for a specific shape. And yes, you'd use that collider and renderer class to compose entities just like you'd do in ECS. I'm not saying inheritance solves every problem, just arguing that it's useful in some cases and sharing my rules of thumb for when I use inheritance vs composition.
This is probably the best explanation and comparison of the two I've seen yet. All of only one thing is never good, but instead you mix and match them. And sometimes, as you show here, neither inheritance nor composition are the best choice and thus you go to a state machine, or in other cases some different construct, where that is most appropriate.
Some of the best advice I've ever heard was "Favor composition over inheritance". In 30+ years of software development there have been very few times when I've found non-trivial inheritance to be of use. But I'm constantly finding composition to be useful.
Composition isn't just having a list of things on an object. Composition is inheriting multiple interfaces instead of one baseClass. So instead of Enemy : Entity. You have Enemy : ICanJump, ICanWalk, IDamagable, IHasInventory And each of those interfaces handles the default logic. Then later when you want to damage an object, you don't check if it's an Enemy, you check if it's an IsDamagable and call the object's TakeDamage method. Now anything can be IDamagable. Destructible environments, the ground, the player. It doesn't matter what, because all it's promising is that it takes damage. The code's intention is super transparent and easy to maintain. When people say use Composition over Inheritance, they mean direct inheritance. Not inheriting in general. Composition is still a type of inheritance. You just inherit components instead of one root class. If you're not using interfaces, you're not really using composition. Just Lists. Every baseClass you consider creating, instead break it down into many interfaces each responsible for a singular thought.
Interface inheritance to define an object's components has way too many disadvantages. - You need a new class for every combination of components. - You can't dynamically change which components an object has. - You can't have multiple components of the same interface type on one object. - You need the language to support default interface implementations. - Interfaces can't hold data, so any data a component needs has its fields duplicated across all classes that implement it, and accessed through public interface methods which are inadvertently exposed to anyone with a reference to your object. A simple list of components doesn't have these problems.
What a great video and well explained! Becoming more familiar with composition is fundamental for Godot devs. Plus, the delivery and composition of your video is fantastic! Keep it up! Subbed! Thank you!
Also, I don't think this content is considered "long" - since it's a technical topic, feel free to deeper dive into those 15-30+ minute videos. Editing does get a bit more involved though 😅
Great video! Composition is definitely a far more flexible way to compose complex ideas. One small note though, that just might come across a bit confusing to people new to the ideas watching the video, Composition and OOP are not different paradigms. OOP is an umbrella term for a collection of ideas _including_ Encapsulation, Inheritance and Polymorphism. with Polymorphism (the **is a** relationship being considered the weaker of the tools relative to composition the **has a** relationship.) As a programmer myself I always love watching the way people solve problems. awesome work!
Some time ago, I read a reddit post which explained this extremely well (sadly I can't find the link, so I have to paraphrase) and it said that inheritance is basically composition but with extra features: 1. compose the supertype in the subtype, 2. automatically delegate properties and methods to the composed instance (syntax sugar: instead of having to do x.y.z you get to do x.z), 3. Make all subtype instances valid instances of the supertype from the perspective of the type checker. Interestingly, not only does inheritance often mislead new OOP programmers into the problem you described, the 3rd feature, formally called subtype polymorphism, makes a program harder to type check too in certain cases.
As long as you keep inhenritance two levels deep at most, trust me. When you're trying to change a node that extends "Wolf" and you're coming from Entity -> Enemy -> Animal -> Wolf, you have so much code scattered that is hard to keep it in check. The concept of Locality of Behaviour is more important than Single Responsability
You can use both poorly and both correctly. BaseClass inheritance should be done with an abstraction. Not a class you're going to actually create. In the video's example, he was spawning BaseTitans in game. That's already a mistake. And the randomization should have been in the constructor anyway. Bad example.
@@lializ_666 I was also gonna comment on the two-level rule, let's go! To the comment above me, that's actually true yeah, but I've found that in those cases it massively helps to relocate implementations to be more composition-based than inheritance-based. Basically, choose composition when inheritance introduces conflicts
Glad I’m on the right track . I name them “actions” and are basically containerized behaviors. I got priorities setup as well as additive capability which I can either have the next action be the current behaviour or an addition to the current .
Just one thing, though: composition is actually OOP (and should not be confused with ECS). It's just a different way of doing OOP. A long time ago, in the golden days of Flash games, all the tutorials were hyping OOP, but they all advised people to use "composition over inheritance". But then.... everybody used inheritance. :) There's an interesting talk by Bob Nystrom about roguelikes where he shows the benefits of OO composition too.
This is insane quality and super useful stuff! I can't believe you have only 2.5k, at least for now :) As the other guy said, I didn't know this was about Godot until I started watching. Maybe adding "Godot" somewhere in the title or thumbnail would help? Anyways, you just earned another sub. Keep the quality going!!💪
I'm an experienced software engineer and what you've created is called an HFSM: Hierarchical Finite State Machine. When you say behavior tree, especially in the context of game dev, it usually refers to a form of AI that's similar to an HFSM but is more specialized and centric around execution over time. There are lots of videos and information on it.
This looks like fun game to play! Something about biplanes on floating bases on the sky totally sounds like my dream game when I was 10 :D I love composition so much, but I use it so much because I can't use OOP at all, I feel like I'm not a "real programmer" because everytime I have to make interfaces I make total spaghetti and I get disappointed and burned out so fast :(
1). There's no such thing as a "real programmer". Gate keeper skill level is such an 'internet validation' thing. So try not to think about stuff like that just code and have fun and mess up and learn 😂 2). You should learn some aspects of OOP it's super useful. The combination of OOP and Composition I feel is the best place to be. 3). Thanks, I've been working on it for sometime now and I'm just now finding a balance between UA-cam, gamedev, school and work so progress has been slow.
This is incredibly cool! I finally feel like I understand all of it good enough to work like that on my future projects. Right now I have, weeell, I guess you could also say a mix of inheritance and composition, but in a ratio of something like 90% inheritance, 10% composition. By the way, just saying, inheritance is also valid for most projects, you can work around most problems. For example, in my game, all melee weapons a player can use actually shoot projectiles that fly really fast, but self-destruct after flying several pixels (and are invisible ofc)
I'm always worried about how ro format this kind of code, but it really looks so much cleaner by the end. Hope I can use this better in future projects, thank you!
@@NesiAwesomeness the best way to learn is by building and teaching. Great video, your explanation of -behavor trees- hierarchical state machines was great.
This is what includes/mixins/interface Which basically is a feature set that can be added to a class, that then from that point forward I herits naturally but isn't bound or part of the hierarchy. It's the wings problem in the animal tree
My biggest issue with inheritance is that it always makes sense when you're doing it. Like, if i start writing a program from literal scratch, and i make a bunch of simple struct-like things, then eventually I'll see what they all have in common and make a base class. Then you build more and you need base classes for your base classes. Then again, and again, and again, and by the end of it i have an impenetrable layer cake a mile high of class hierarchy. AND to top it all off, i usually don't remember which base classes did what specifically, which bloats debugging time
@NesiAwesomeness lemme ask your opinion on something if you don't mind. I just found your channel and I've been binging lol. Okay, so I never took a compilers or programming language class, but I always wanted to learn them, so I followed this tutorial on UA-cam to get all the basic pieces built and see how it works. Now I'm done with the tutorial and I'm able to start building from it quite a bit. I've already built support for a map type, and interchangeable static and dynamic typing, and a few other easy things. Here's the question. I'm about to start implementing structs, and I want to try to strike the right balance between customization and conciseness. I'm thinking about designing structs to be just general collections of objects to begin with, and functions will be typical functions as well. Like, not methods per se. But I want to be able to add a statement in the definition of a struct that will let me attach a function to it by telling the struct how it needs to handle itself with respect to the inputs and outputs of that function. Example at bottom. What I'm going for is a mix and match approach for structs and functions, but I've never done this before so I don't know if I'm effectively just creating traditional classes at the end of the day. Any thoughts? Example: I have a function add(a, b) that returns a+b. Then I have a struct called Person that has a string property called "name". Whenever I call add with a Person as one of the arguments, I want that Person to feed in its 'name' property instead of its whole self, so I add a line in the definition like this: struct Person { name = 'some name' ... .add
K turns out this is already a thing. But hell, I'm not complaining! I didn't even know it was a thing, so apparently what I thought was a good idea actually was! That's kinda cool to know.
@@ryan-skeldon I agree with you in the cases where the entire functionality of the program is known beforehand. If you're trying to solve a problem that isn't already solved, the usefulness of design hits a hard ceiling pretty fast.
Your video gave me some good ideas. For some reason I was trying to implement those behaviors/actions/states using events, and that let to some weird phenomenon like a character who can negate dead, dying before triggering the skill. Using states is much more sensible. Also, OOP is the paradigm, Inheritance is a property of OOP, and Composition is a pattern of OOP. You have some concepts mixed up there, but the general idea is well explained.
Never thought about using a behavior tree for the character I'm controlling. I did implement a behavior tree in a similar manner to this in my game though, with nodes for each behavior, and it traversing through the tree from top to bottom. Might be worth investigating for my project too..
Quick correction to my video, I kept referring to the "Finite Hierarchical State Machine" as a behaviour tree, these things actually happen to be completely different.
@@NesiAwesomeness it's definitely a very modular approach to modeling the logic for my AI, so I can see how it would be a really easy way to make an extensible character controller as well, I'll have to do some experimenting.
Composition is incredibly powerful. I wrote my own set of systems using composition, ecs, state machines and archetypes. This has given me amazing versitility in writing my engine, adding to it and not even remotely worrying aboit spaghetti hierarchies, multi inheritance and many other issues that i found with oop (oop is still great) I feel that systems that find objects of certain types/composition in the project and add functionality 8s incredibly powerful. Imagine a ball, apple and wheel. They all have very different properties but all roll. Add a roll to them all and let the roll system do the rest. Just a simple example tbf.
In the game engine I'm designing, I'm using a single level of inheritance event bus system by that I mean I have a base entity class of which there is a vector entities which it loops through and calls the perspective function depending on what type of event it is. [Think htmls Dom event bus] And also you have to have some sort of inheritance to properly polymorphize the container for your entities. No game engine that I know of uses truly compositional design [in fact, ECSes rely heavily on the whole polymorphism idea if I understand correctly]
I really appreciate all the thought you've put into this video, and you make a lot of valuable observations. As someone who has been working both independently and inside the industry for 15+ years, I really have to say. None of this stuff matters. Making good games matters. I have worked so many jobs where so much stress and emphasis was put on coding paradigms. It never paid off. The engineers who wrote good code had always been that way, and the ones who wrote spaghetti had always written spaghetti. Adherence to coding paradigms and practices are born out of businesses needing to cater to a lowest common denominator. You need to focus on writing code that achieves the goal of your game. Have you seen Notch's original source code for Minecraft? It's a freaking MESS. It would make even novice coders puke. But it didn't get in the way of the game's goals, and it certainly didn't stop it from becoming the best selling game of all time.
inheritance was perfect in this example if you already knew exactly what you were going to do, but it can become tricky once you start developing and coming up with new stuff. Also I get how it can become complex similarly to circular imports in python, this module needs that class and the module that defines the class needs the first module and it becomes a wooden wheel instead of a tree.
Having well defined configuration settings can come in handy also. For example, I am working on components which require asynchronous data, so there is a use case for a separate initialization code path after construction. The options for guarding this separate execution path from user ( in this case developer ) mishandling are limited in the language I am working in. So I came up with a way to internally validate the initialization invocation, which made it possible to expose the code path, but ensure it is only being invoked internally. This also permits implementations to decide which parts of the initialization procedure they want/need for their own requirements by selectively calling up the inheritance chain. So like lazy, piece-wise configuration, similar to ad-hoc interfaces. Also kind of like an effective form of shared privacy. The language does not have built in OOP interfaces and does have private member access but not shared privacy natively.
Great video! I love the depth of thought put into the video and the game. Have you live streamed development? I recently started doing that and it’s really fun and all the smarty pants in the audience have helped me a lot.
Your randomization method is in the wrong place - you should have a spawner class which handles generating random values and instantiating titans. Data inheritance is usually stable, but logic inheritance usually indicates you’re trying to do too much in your class.
The free fall mechanic is pretty cool, not sure I’ve seen that before. Seems like you have an interesting expandable core with this approach so best of luck building on that to a more fleshed out version.
Inheritance as well as composition have their place and often a mix is the most useful approach imo. you can have a titan that inherits from a titan base class, if there is commonly shared code between them, lets say they all have eyes and blink with them. that does not mean everything has to be shared and the different titans then even can all have their own components. Just don't overdo it with inheritance, since it becomes very confusing very quickly. Use it, if you expect to save a lot of time when changing something in the future, since you only need to change the base class(for example blink twice as fast or whatever). If you have hundrets of titans, it may save you a lot of time. If you only have 3 titans i would not bother anyway. Also in my opinion i would define the behaviour tree in your example more of a state machine - A component would be for example a damage area, that an enemy can have or not have to do damage, imo. But that beeing said, very nice to watch video, i really liked it! Good and clean presentation 👍
Any coding paradigm that you like is good to go. For me the only restriction i set is: Do not repeat yourself. If I have to update the same code in two places I better write a function or inherit it.
@@NesiAwesomeness My bad! I commented without watching. The bottom line is that you can spend hours trying to generalize your code or asset to work in any situation, but in practice your assets will always end up intrinsically linked to your project. The best way to create is to dive straight in and do whatever works, and then once you have it working then clean it up, generalize, optimize, etc. THat is why they say optimization is the root of all evil. You need to create unselfish-consciously and recklessly, and then when you have something apply your critical eye to it.
I get everything you said Nesi, nice video. I still need to understand what exactly is the function "set_behaviour" doing. Like how does it actually change the behavior to one of the child nodes listed in the behaviour tree?
I did a lot of animation, which exactly, the character animation, the motion graphics, the hand writing, the background animation, these all use require different software. Cavalry Blender DaVinci Resolve
I always run into this problem. If the scope of the project is small(most are) and experimental(unique objects all over) you are better off ditching inheritance cause the busywork to get the system to adhere defeats the point of setting up inheritance (same is true with nesting). Inheritance is pretty redundant to begin with as you can just rewrite redundancies in terms of function calls fine and formulate the various types with basic if-blocks. I experimented heavily with bullet outlines across game projects, inheritance structures were never clean always dirty and a complete nightmare to edit/read/update regardless of setup. Falling back on simple principles is actually cheaper(inheritance is usually costly) and way easier to edit/organize/read: add_bullet("shotgun") function add_bullet(type) if type="shotgun" then n=10 else n=1 for i=n do add(bullets,{x=x, y=y, init=function() if type="shotgun" or "likeShotgunWithLessPellet" then dmg=5.... elseif type =...."}) You can compartmentalize everything with functions and if-blocks however you want just like could classes. It's literally the same thing as you could do in classes just you have everything together and you invoke with a single function call. The point is you don't need classes; you can generate however many bullet copies of whatever variants as desired. In regards to using classes: to solve the issues of updating I've been throwing in nested blank functions in the middle of the class's methods. So instead of rewriting the entire method for unique object or extend trees all over I can just update the tiny part. This does mean that classes who don't use it will always call a blank function, obviously not ideal to ever call blank functions, but if you calculate the alternative costs of extending all over it's usually better or irrelevant. Case usage makes every argument irrelevant though, you might have other considerations forcing traditional class outlines. Or maybe you dont need many if any unique objects and dont want to run if-blocks u dont have to...then messy class code is ideal, but can also just solve it without by making separate bullet functions (repeated code can be mitigated with functions returning functions returning the object). I like to compare most language choices to the lua one of coroutines vs switches....u can write in either to get the same job done, but you can always rewrite your coroutine code into cheaper switch code.
Not going to say it's totally wrong for small experiments, but your example with if branches is one of the worst ways to do this. If this starts to scale at all, you'll end up with YandereDev level code that will be impossible to manage, incredibly hard to read, and have way worse performance than a proper use of inheritance and composition. The program will have to worst-case traverse the whole if-tree until it gets to the case that is relevant for it, which will result in many pointless comparisons and thus significant overhead (definitely more than any potential OOP overhead). The proper way to do something like this is having your custom guns implement interfaces (aka the composition patterns talked about in this video) for stuff they can't share with the common base class (e.g. hitscan vs. velocity based bullets) while inheriting what's sensible from the base gun (like bullet count and damage). That way you can feed your parameters in directly or even with some type of config JSON or the likes, and it also solves your "blank function" problem. Interfaces enforce contracts, so you can selectively call only those who implement it. If you do this right, there will be no gun, no matter how special, that can't be modeled like this, even for crazy stuff like bullets that heal or are actually NPC controllers (think nanobots, homing missiles etc.). It will take a bit of planning and foresight to not write yourself into a corner, but it definitely beats having to refactor a gigantic if else block. Especially since every good IDE has features that make editing classes and interfaces as easy as possible, where the same can't be said for hacky if blocks that cosplay as worse versions of a class/closure.
Misfired the comment button earlier and youtube edit seems broken for me. :S But I would say that composition more often refers to functional code, or maybe sometimes with OOP interfaces. I have used the *Item* -> *Tree* inheritance pattern you described though, works well. Lately though I have started using more abstract classes which actually throw if some tries to construct them directly. No doubt complexity becomes its own challenge and it depends how much you like the pain of it for the benefits you can gain.:D I start going sideways after about 4 levels deep in abstraction/hierarchies, but I have seen awesomeness at levels 5 and 6. It just depends what the use case is. I have found that the more I force myself to document closer to the root of my abstractions the more focused I am by the time I am punching out the leaf components where everything is supposed to just do what I want. Essentially, think need near the top, want at the bottom and you should do ok. :-)
For my non-gamedev job i use inheritance. Works, everybody knows it, wide spread.. no reason not to. For gamedev i use what's more appropriate given the situation. Usually this ends up being an inheritance focussed system with some composition. The only reason i actively switch to a different style is if the game / module is heavily performance related. In which case data oriented programming is pretty much the go-to solution. While, concept wise, data oriented programming is also very similar to composition, the core message here should be: use what's appropriate for your actual usecase. Nothing is outright better than other things. Trust me^^
I found myself cerrin on if my code is clear and well writen more than if it's usefull and working and this stop me from coding too long befor i actually start to make it right
The editing, the flow, it's amazing! But some criticism though, at first I didn't know this was about Godot (or atleast, using Godot engine). It would be helpful if you add "Godot" somewhere in the title or description! And, I have a bit of a hard time listening to what you are saying (since i am not a native speaker), It would be better if you add subtitles(?) or practice speaking more! Keep this up and you'll be more popular, im sure!
Definitely will do, thanks for the suggestion about subtitles. I didn't want to make it Godot specific that's why I didn't add the Godot Icon, but I guess I will later
it's actually not about Godot, it just happened that he is using Godot to demonstrate his point. It can be used using any engine or even when not making games
@@joumakesgames I agree, but I thought it would be nice to atleast mention the engine used in this video. Maybe if it's not a specific thing, it can be added in the description
I was taught OOP as *the* way to program at uni for years, but when I got to my gamedev spec and was given freedom to build my own project any way I see fit - I realized how easier and more maintainable things are when you use composition instead. I wasn't even looking for any alternative paradigm or anything, I was just trying to solve problems and logic always led me to structure my projects with composition.
I have no clue what anime those „titans“ are from. But they remind me of David lewandowski‘s animations „going to the store“, „late for meeting“ and „time for sushi“ to be specific… (all on his UA-cam Page) but not a word about his work on „tron: legacy“, „oblivion“ or „top gun: maverick“
My wizard for fixing codes is this process... I encounter a problem... I feed the problem to ChatGPT while thinking about it ChatGPT gives me proposals and I play around with them... I fix the problem without having to figure out where I made the problem... I ask GPT to help me revisit my problem and how to avoid it in the future... I learn and keep going...
Ii think you overused the bad inheritance explanation, because you didn't know how to build your code 2 years ago, inheritance is useful only in a handful, of scenarios. And in the titans case, it is absolutely okay to use it. (You didn't solve the titans inheritance issue BTW Besause it was purely made up so you could rant about it..) When you are building an inheritance system itr has to be Laid Out Beforehand, wich enemies will need wich things, which ones are the minimum requirements, and so on, if you dont take the time to write down, a plan. Don't bother making an inheritance system. When people complain that they're so bad, it is, because they are starting to build it without an idea of what they need what they want or how this coding pattern works Even after the clearly inherited roast, Great video on the component separation.
yo cool vid, i have a question cause i watched at x2 speed and wasnt trying to read code - what if you dont want to follow the player and the plane together? in the case of just going to freefall and have the camera follow the player instead
You can just do that, The Camera switches to the "Follow Free Fall" state when the players State Machine changes to the "Piloting: FreeFall" state. If I want the camera to just follow the Player I'd just have the player remain in the "Follow Player" state
Message bus is definitely cool, but I would caution you to separate it out into multiple specialized buses. Otherwise you end up with the issue of 500 things watching the message bus and getting notified about events they don't care about which can add a lot of extra overhead and lagginess
idk if the game you showed is just for demo purposes, if it isn't, the movement on the platform is very floaty, like they're on ice, when changing directions Also W for jump??? it's always space, W is for looking up or interacting with objects like doors or smth Other than that, neat idea for a game
I'm not pointing this out to be pedantic, I promise, but what you've called a behavior tree is a hierarchical state machine. A behavior tree is a fundamentally very different approach to a logic control structure. The ONLY reason I point this out is because you have an audience of new developers and this could cause a lot of confusion over time as this misunderstanding propagates outward and with state machines and behavior trees both being fundamental approaches to game AI.
Similar to the way Roblox kids call incremental/idle games "simulators", which are something else entirely, or how so many younger people say "literally" when they mean "figuratively". It creates a communication barrier between two generations that makes it more difficult for lessons to be passed down.
New devs have to fight through a lot of confusion and endure a lot of frustration in their journey to becoming intermediate devs and these kinds of misunderstandings can make the harder parts of the learning curve even harder.
The misnomer aside, you did an excellent job of communicating some useful concepts that are usually hard earned through experience and that's very difficult to do well. I sincerely hope you keep making content like this because you're an excellent communicator and we really don't have enough of those in the world.
@betatester03 Yeah I know I put it in the description of the video that I mixed those up.
I was looking for a different name to call the nodes and I called it a "Behaviour Tree" and it stuck I had no idea my bad.
I made the correction in the description
Not a new developer but a thankful one.
I've encountered my fair share of incomplete or confusing tutorials developing my game (vids under channel).
I appreciate the effort to help the community !
just to play off you're pedantry, the word "literally" has been used to mean "figuratively" for basically as long as the word has recorded use, so lets not blame the kids for this one. the video lays out the logic behind what has been made so really the only problem that can arise is a viewer using the term "behaviour tree" to some 3rd person whom either corrects the viewer or misunderstands what they're talking about. which really isnt that big of a deal (i mean if they're actually in a serious production situation, then it would be corrected by someone senior)
They can be structurally equivalent tho, it just depends on how you implement the states of the hierarchical state machine.
Massive respect for the way you voiced your opinion in a thoughtful, constructive and still courteous way- god I wish more people spoke with respect like this.
❤
Also mad respect to the channel owner for acknowledging and responding in a reasonable manner too ❤
This coding style is one I've gradually moved towards over time. I didn't realize it until watching this. I always thought I was using inheritance, but it turns out I was employing a hybrid approach. I use this method when writing full-stack programs, and it has saved me so much trouble because it's easier to swap out modules and functions without breaking everything.
@@Hysorix I actually spent some time with functional programming and just gravitated towards other styles of coding
I really appreciate your explanation, Nesi. This is exactly why I dropped modding ArmA 3 and decided to just make my own damn game with Godot. Everything custom in A3's config scripting inherits from base classes at the top of the game asset inheritance tree, which results in having to redefine or add unnecessary lines to turn off certain features to each custom class, while the original class is still technically the overriding class, meaning to add new stuff you have to inherit from the base class all of its code, and if you want to change anything in your custom class at the bottom you usually have to hunt down and modify lines that pertain to what you want to change. In this example of a state machine, you can not only pick and choose what to inherit from but you're not stuck with pre-packaged components you might not even use. It's just smarter and more efficient to work with.
Whatching that video feels like sitting at some jazz cafe in a noir detective movie
Because of the music choice?
@@NesiAwesomeness Music, but also the way you speak, the overall mood. It's really relaxing.. :)
The problem with inheritance is not necessarily itself, but the fact that people use it in places that they are not supposed to. It is a principle like any other in software development/computer science, and like any other principle, you need to think about if, and how you should implement it. Its common use probably stems from the fact that it gets taught the earliest in university/courses, and alternatives are taught either later or not at all. A different way to mitigate the problems that you referenced is the decorator pattern, but I would not recommend it, since it can get overly complicated quick, especially if there is a lot of divergence between the classes that exist. I am currently not involved in any projects that would need OOP principles, but if I find myself in a situation like that in the future, I will give it a try.
I enjoyed the video, good job. The editing is really good.
Definitely, the point of the video was never to tell people to abandon it just to be careful and in some cases prefer composition
The problem with inheritance is it's not visible. Nothing warns you that the class you're editing is a child. Only if it's a parent and only one level deep.
Composition flattens the inheritance tree and makes code intention very transparent.
The video's example of composition wasn't correct. Composition shouldn't have multiple levels. It's all at the top.
As an OOP pilled dev, the randomize function seems like an oversight in the first place. That sort of manipulation of variables upon creation of the object would be better done in the constructor method. You can have as many constructors with different method signatures as you want. Usually, for this sort of operation, you would have a blank constructor, say, titan(). This would initialize the object, and you could use it to set values randomly. Then you would have another constructor, say, titan(String name, int hp, int speed) and set the in scope variables using those values. There would be no need for creating subclasses.
@@XionicalXionical it was a crude example, it's an excuse to just use Titans to explain because I thought it would be interesting.
They're better examples, some issues I've come across were from a similar situation of something I predefined conflicting with something else I want to now inherit from.
If you've never run into issue then that's amazing because like I said OOP is great they're just some cases.
I'd also highly recommend trying Composition. I've found it easier, that's my personal preference
I personally think OOP has no place in game programming, and a purely functional style is much more suitable.
@@TheOnlyGhxst Is there any game engine that supports purely functional programming?
Purely functional is sadly horrible for performance. It's good for most event driven logic, but NEVER in a game loop@TheOnlyGhxst
@@lunarthicclipse8219 There are ways to optimize it quite heavily. If I remember correctly I believe the Jak and Daxter games were made with a custom in house engine that was purely functional and made with Lisp.
Hey congrats on tackling that tech debt. It always surprises me that no matter how long I've been coding I always accumulate tech debt like this and have to go back to refactor it.
Good video structure. I can tell you that you're a lot easier to understand than other channels I've watched on UA-cam and the reason why that is, is because when you are about to explain something you take short breaks before explaining everything and even when you do explain everything you also are taking short breaks in-between each chunk of information to let the viewers mind catch up with the information you're saying. I would say that in the future try to think of doing this more often because it really helps the viewer (me) and also, I can say that it makes you stand out as a content creator as well.
Keep up the hard work Nesi. 🙏
@@WhyDidItDo thank you, that was exactly what I was going for. It was something I decided one when I came back to watch my own video.
I just felt like there were too many points where I had to actually pause.
It's nice to know I made the right call, I will definitely continue
Thank you for sharing your ways. It is really helpful to see how people manage their structures, and yours seem not only organised, but functional
Glad it was helpful!
Loved this video. I've seen other explanations of behavior trees and state machines for game development that seemed to overcomplicate the communication between nodes. This was a great video!❤
Inheritance makes sense if you actually have shared logic, but it's easy to overuse because we have a tendency to want to create "logical" inheritance hierarchies.
Having a ParticleEmitter base class the efficiently batch renders particles makes sense to me, the only reason you'd ever want to inherit from it is if you want to emit particles
while for your titan example you could imagine a friendly titan that just caries you from place to place instead of attacking, in that case the inuitive thing to do is inherit from Titan, but the practical thing to do is inherit from something like Transporter or MovingPlatform
the problem is if you have an abstract motor class that all your motors inherit from, and you have like em drive or warp drive or rocket motor. only the rocket motor emits particles and you need to inherit from particle emitter AND abstract motor. (deadly diamond). something like ECS, or rust's traits solve this.
@@joshuathomasbird Forgot to explicitly mention it in my comment but inheritance also only makes sense when you actually need the shared API between descendants so you can swap them out interchangeably. The motor shouldn't inherit from particleEmitter because that's not its primary function, composition is the right pattern here.
ECS is just one way to achieve composition, there are many more
@@pokefreak2112 I think for game development in general, Composition and functional programming is just the right way to go, and Inheritance/OOP really has no place. Sure, using Inheritance might make some things EASIER, but also makes your code messier and more likely to break in the long run. If you make as many things as possible their own separate, self contained, reusable module, then you avoid chain breakages where one messed up part of the program breaks everything else.
@@pokefreak2112 I think maybe you're missing my point about why multiple inheritance is problematic. the motor is just to visualize a concrete inheritance pattern that is easy to understand. if you don't like that one consider quadrilaterals: rhombuses, rectangles, and squares. If your pattern is supposed to compose behaviours objects can have but the pattern can't organize how those objects behave, then it would be incorrect to say that it is a way to acheive that, let alone claim that it's the "right" way.
I hope you don't find that out the hard way, nor make someone else's life miserable standing on this hill.
@@joshuathomasbird Harsh. You'd need to provide more context what you're doing with those quadrilaterals. Personally I don't think I'd ever make a quadrilateral base class, but I could see myself making a "collider" class or "renderer" class where subclasses implement a collider or renderer for a specific shape.
And yes, you'd use that collider and renderer class to compose entities just like you'd do in ECS. I'm not saying inheritance solves every problem, just arguing that it's useful in some cases and sharing my rules of thumb for when I use inheritance vs composition.
This is probably the best explanation and comparison of the two I've seen yet. All of only one thing is never good, but instead you mix and match them. And sometimes, as you show here, neither inheritance nor composition are the best choice and thus you go to a state machine, or in other cases some different construct, where that is most appropriate.
Yep, the magic is in the combination
Some of the best advice I've ever heard was "Favor composition over inheritance". In 30+ years of software development there have been very few times when I've found non-trivial inheritance to be of use. But I'm constantly finding composition to be useful.
Composition isn't just having a list of things on an object.
Composition is inheriting multiple interfaces instead of one baseClass.
So instead of Enemy : Entity.
You have Enemy : ICanJump, ICanWalk, IDamagable, IHasInventory
And each of those interfaces handles the default logic.
Then later when you want to damage an object, you don't check if it's an Enemy, you check if it's an IsDamagable and call the object's TakeDamage method. Now anything can be IDamagable. Destructible environments, the ground, the player. It doesn't matter what, because all it's promising is that it takes damage. The code's intention is super transparent and easy to maintain.
When people say use Composition over Inheritance, they mean direct inheritance. Not inheriting in general. Composition is still a type of inheritance. You just inherit components instead of one root class.
If you're not using interfaces, you're not really using composition. Just Lists.
Every baseClass you consider creating, instead break it down into many interfaces each responsible for a singular thought.
mmmm, okay that clicks. Good stuff
How can you describe logic in an interface?
Can you have default implementations in an interface in C# now?
Interface inheritance to define an object's components has way too many disadvantages.
- You need a new class for every combination of components.
- You can't dynamically change which components an object has.
- You can't have multiple components of the same interface type on one object.
- You need the language to support default interface implementations.
- Interfaces can't hold data, so any data a component needs has its fields duplicated across all classes that implement it, and accessed through public interface methods which are inadvertently exposed to anyone with a reference to your object.
A simple list of components doesn't have these problems.
@@TricoliciSerghei Yes
love these educational / devlog style of a vids
I'll make more of these
What a great video and well explained! Becoming more familiar with composition is fundamental for Godot devs. Plus, the delivery and composition of your video is fantastic! Keep it up! Subbed! Thank you!
Also, I don't think this content is considered "long" - since it's a technical topic, feel free to deeper dive into those 15-30+ minute videos. Editing does get a bit more involved though 😅
Thanks so much
My usual videos are 3 to 4 minutes to 10 minutes is kind of long for me.
I'm going to start trying to make videos like 7 to 12 minutes of much longer
@@NesiAwesomeness Sounds good!
Great video! Composition is definitely a far more flexible way to compose complex ideas. One small note though, that just might come across a bit confusing to people new to the ideas watching the video, Composition and OOP are not different paradigms. OOP is an umbrella term for a collection of ideas _including_ Encapsulation, Inheritance and Polymorphism. with Polymorphism (the **is a** relationship being considered the weaker of the tools relative to composition the **has a** relationship.) As a programmer myself I always love watching the way people solve problems. awesome work!
I never said Composition was a paradigm, it's a code pattern, OOP is the paradigm.
Thanks for the notes, maybe it was confusing
Some time ago, I read a reddit post which explained this extremely well (sadly I can't find the link, so I have to paraphrase) and it said that inheritance is basically composition but with extra features: 1. compose the supertype in the subtype, 2. automatically delegate properties and methods to the composed instance (syntax sugar: instead of having to do x.y.z you get to do x.z), 3. Make all subtype instances valid instances of the supertype from the perspective of the type checker. Interestingly, not only does inheritance often mislead new OOP programmers into the problem you described, the 3rd feature, formally called subtype polymorphism, makes a program harder to type check too in certain cases.
Using both is usually the best
As long as you keep inhenritance two levels deep at most, trust me.
When you're trying to change a node that extends "Wolf" and you're coming from Entity -> Enemy -> Animal -> Wolf, you have so much code scattered that is hard to keep it in check.
The concept of Locality of Behaviour is more important than Single Responsability
@@lializ_666 indeed, sometimes it's better to do it the "dumb" way; instead of making a bunch of scattered code, which makes developing a hassle
You can use both poorly and both correctly.
BaseClass inheritance should be done with an abstraction. Not a class you're going to actually create. In the video's example, he was spawning BaseTitans in game. That's already a mistake. And the randomization should have been in the constructor anyway. Bad example.
@@lializ_666 there is simply so many use cases that require more than 2 levels of inheritance that there would never be a "two level" rule
@@lializ_666 I was also gonna comment on the two-level rule, let's go!
To the comment above me, that's actually true yeah, but I've found that in those cases it massively helps to relocate implementations to be more composition-based than inheritance-based.
Basically, choose composition when inheritance introduces conflicts
Glad I’m on the right track . I name them “actions” and are basically containerized behaviors. I got priorities setup as well as additive capability which I can either have the next action be the current behaviour or an addition to the current .
Very good video that got recommended to me exactly when I needed it since im about to run into the same issues
Just one thing, though: composition is actually OOP (and should not be confused with ECS). It's just a different way of doing OOP. A long time ago, in the golden days of Flash games, all the tutorials were hyping OOP, but they all advised people to use "composition over inheritance". But then.... everybody used inheritance. :)
There's an interesting talk by Bob Nystrom about roguelikes where he shows the benefits of OO composition too.
Thanks so much for the clarification. I've learnt so much from these comments.
This is insane quality and super useful stuff! I can't believe you have only 2.5k, at least for now :)
As the other guy said, I didn't know this was about Godot until I started watching. Maybe adding "Godot" somewhere in the title or thumbnail would help?
Anyways, you just earned another sub. Keep the quality going!!💪
I didn't want it to be Godot specific, I was hoping what I was talking about could be implemented anywhere
@@NesiAwesomenesstrue, not mentioning the engine was the right choice as this isn’t limited to single engine. Cool video! I share the sentiment :)
@@blue.pixels thank you
@@NesiAwesomeness You're right, this could be applied anywhere!
I'm an experienced software engineer and what you've created is called an HFSM: Hierarchical Finite State Machine. When you say behavior tree, especially in the context of game dev, it usually refers to a form of AI that's similar to an HFSM but is more specialized and centric around execution over time. There are lots of videos and information on it.
Really enjoy your style of editing, keep up the grind 👍
This looks like fun game to play!
Something about biplanes on floating bases on the sky totally sounds like my dream game when I was 10 :D
I love composition so much, but I use it so much because I can't use OOP at all, I feel like I'm not a "real programmer" because everytime I have to make interfaces I make total spaghetti and I get disappointed and burned out so fast :(
1). There's no such thing as a "real programmer". Gate keeper skill level is such an 'internet validation' thing. So try not to think about stuff like that just code and have fun and mess up and learn 😂
2). You should learn some aspects of OOP it's super useful. The combination of OOP and Composition I feel is the best place to be.
3). Thanks, I've been working on it for sometime now and I'm just now finding a balance between UA-cam, gamedev, school and work so progress has been slow.
@@NesiAwesomeness thank you! hope you have a good time making your game while balancing other things^^
This is incredibly cool! I finally feel like I understand all of it good enough to work like that on my future projects. Right now I have, weeell, I guess you could also say a mix of inheritance and composition, but in a ratio of something like 90% inheritance, 10% composition. By the way, just saying, inheritance is also valid for most projects, you can work around most problems. For example, in my game, all melee weapons a player can use actually shoot projectiles that fly really fast, but self-destruct after flying several pixels (and are invisible ofc)
@@Garniy_Hlopchik sounds great
you got a good style of video
I'm always worried about how ro format this kind of code, but it really looks so much cleaner by the end. Hope I can use this better in future projects, thank you!
The pattern when combining the singleton and observer patterns to manage state is called a reactive store or observable store.
@@ImperiumLibertas I've learnt more from these comments than the research I did for this video 😂
@@NesiAwesomeness the best way to learn is by building and teaching. Great video, your explanation of -behavor trees- hierarchical state machines was great.
@@ImperiumLibertas I'm never going to love this down 😂
I didn't know you could cross out words on UA-cam
Loved the video especially the end product with the attached debugger to see state displays on the entities
This is what includes/mixins/interface
Which basically is a feature set that can be added to a class, that then from that point forward I herits naturally but isn't bound or part of the hierarchy.
It's the wings problem in the animal tree
Support you bro,i will be always there for you😊😊
My biggest issue with inheritance is that it always makes sense when you're doing it. Like, if i start writing a program from literal scratch, and i make a bunch of simple struct-like things, then eventually I'll see what they all have in common and make a base class. Then you build more and you need base classes for your base classes. Then again, and again, and again, and by the end of it i have an impenetrable layer cake a mile high of class hierarchy. AND to top it all off, i usually don't remember which base classes did what specifically, which bloats debugging time
Exactly, or you just need a work around all the time.
It feels like a bandage to the over arching problem
@NesiAwesomeness lemme ask your opinion on something if you don't mind. I just found your channel and I've been binging lol.
Okay, so I never took a compilers or programming language class, but I always wanted to learn them, so I followed this tutorial on UA-cam to get all the basic pieces built and see how it works. Now I'm done with the tutorial and I'm able to start building from it quite a bit. I've already built support for a map type, and interchangeable static and dynamic typing, and a few other easy things. Here's the question.
I'm about to start implementing structs, and I want to try to strike the right balance between customization and conciseness. I'm thinking about designing structs to be just general collections of objects to begin with, and functions will be typical functions as well. Like, not methods per se. But I want to be able to add a statement in the definition of a struct that will let me attach a function to it by telling the struct how it needs to handle itself with respect to the inputs and outputs of that function. Example at bottom.
What I'm going for is a mix and match approach for structs and functions, but I've never done this before so I don't know if I'm effectively just creating traditional classes at the end of the day. Any thoughts?
Example:
I have a function add(a, b) that returns a+b. Then I have a struct called Person that has a string property called "name". Whenever I call add with a Person as one of the arguments, I want that Person to feed in its 'name' property instead of its whole self, so I add a line in the definition like this:
struct Person {
name = 'some name'
...
.add
K turns out this is already a thing. But hell, I'm not complaining! I didn't even know it was a thing, so apparently what I thought was a good idea actually was! That's kinda cool to know.
Spend more time in the design stage before writing code. You'll have fewer surprises.
@@ryan-skeldon I agree with you in the cases where the entire functionality of the program is known beforehand. If you're trying to solve a problem that isn't already solved, the usefulness of design hits a hard ceiling pretty fast.
Your video gave me some good ideas.
For some reason I was trying to implement those behaviors/actions/states using events, and that let to some weird phenomenon like a character who can negate dead, dying before triggering the skill.
Using states is much more sensible.
Also, OOP is the paradigm, Inheritance is a property of OOP, and Composition is a pattern of OOP. You have some concepts mixed up there, but the general idea is well explained.
Great video, keep up the good work man!
Never thought about using a behavior tree for the character I'm controlling.
I did implement a behavior tree in a similar manner to this in my game though, with nodes for each behavior, and it traversing through the tree from top to bottom.
Might be worth investigating for my project too..
@@chonkusdonkus you should definitely, it's super clean and it's something I can use in a different project later too
Quick correction to my video, I kept referring to the "Finite Hierarchical State Machine" as a behaviour tree, these things actually happen to be completely different.
@@NesiAwesomeness it's definitely a very modular approach to modeling the logic for my AI, so I can see how it would be a really easy way to make an extensible character controller as well, I'll have to do some experimenting.
Composition is incredibly powerful.
I wrote my own set of systems using composition, ecs, state machines and archetypes.
This has given me amazing versitility in writing my engine, adding to it and not even remotely worrying aboit spaghetti hierarchies, multi inheritance and many other issues that i found with oop (oop is still great)
I feel that systems that find objects of certain types/composition in the project and add functionality 8s incredibly powerful.
Imagine a ball, apple and wheel.
They all have very different properties but all roll. Add a roll to them all and let the roll system do the rest.
Just a simple example tbf.
This was great! Both from a video and information perspective.
Really enjoyed your explanation and use of examples
@@steve_rico thank you
In the game engine I'm designing, I'm using a single level of inheritance event bus system by that I mean I have a base entity class of which there is a vector entities which it loops through and calls the perspective function depending on what type of event it is. [Think htmls Dom event bus]
And also you have to have some sort of inheritance to properly polymorphize the container for your entities. No game engine that I know of uses truly compositional design [in fact, ECSes rely heavily on the whole polymorphism idea if I understand correctly]
I really appreciate all the thought you've put into this video, and you make a lot of valuable observations. As someone who has been working both independently and inside the industry for 15+ years, I really have to say. None of this stuff matters. Making good games matters.
I have worked so many jobs where so much stress and emphasis was put on coding paradigms. It never paid off. The engineers who wrote good code had always been that way, and the ones who wrote spaghetti had always written spaghetti. Adherence to coding paradigms and practices are born out of businesses needing to cater to a lowest common denominator. You need to focus on writing code that achieves the goal of your game. Have you seen Notch's original source code for Minecraft? It's a freaking MESS. It would make even novice coders puke. But it didn't get in the way of the game's goals, and it certainly didn't stop it from becoming the best selling game of all time.
Excellent explanation! These map perfectly to general software dev (though based on the way you described them I’d guess that’s your background!
I'm purely game development, I have no professional background in software development, just a lot of free time studying programming as a concept
Beautiful video!
Thank you
What did you use to edit? What did you use to create the nodes. Peak tier editing.
Cavalry
awesome video man, great to see stuff like this and massive luck on your game and future videos
Thanks so much
editing is on point
@@thecoweggs thanks, I was trying to make it "invisible" this time, lol
Entertaining and educational plus clearly articulated with visuals. Holy fuck my damaged brain is in heaven for learning. 😂
BIG thankies
@@FirstNameBasis_ you're welcome
inheritance was perfect in this example if you already knew exactly what you were going to do, but it can become tricky once you start developing and coming up with new stuff. Also I get how it can become complex similarly to circular imports in python, this module needs that class and the module that defines the class needs the first module and it becomes a wooden wheel instead of a tree.
I clicked the video and came straight here to say THIS MAN HAS SEEN THE LIGHT AND YOU WILL TOO
Having well defined configuration settings can come in handy also.
For example, I am working on components which require asynchronous data, so there is a use case for a separate initialization code path after construction. The options for guarding this separate execution path from user ( in this case developer ) mishandling are limited in the language I am working in. So I came up with a way to internally validate the initialization invocation, which made it possible to expose the code path, but ensure it is only being invoked internally. This also permits implementations to decide which parts of the initialization procedure they want/need for their own requirements by selectively calling up the inheritance chain. So like lazy, piece-wise configuration, similar to ad-hoc interfaces. Also kind of like an effective form of shared privacy. The language does not have built in OOP interfaces and does have private member access but not shared privacy natively.
Great video! I love the depth of thought put into the video and the game. Have you live streamed development? I recently started doing that and it’s really fun and all the smarty pants in the audience have helped me a lot.
such a great video! beautiful animations and pretty helpful
Your randomization method is in the wrong place - you should have a spawner class which handles generating random values and instantiating titans. Data inheritance is usually stable, but logic inheritance usually indicates you’re trying to do too much in your class.
The pilot freefall mechanic was pretty neat xD
Rendezook as a game mechanic is an amazing idea.
@@FileTh1rt3en I thought so too
The free fall mechanic is pretty cool, not sure I’ve seen that before. Seems like you have an interesting expandable core with this approach so best of luck building on that to a more fleshed out version.
Inheritance as well as composition have their place and often a mix is the most useful approach imo. you can have a titan that inherits from a titan base class, if there is commonly shared code between them, lets say they all have eyes and blink with them. that does not mean everything has to be shared and the different titans then even can all have their own components. Just don't overdo it with inheritance, since it becomes very confusing very quickly. Use it, if you expect to save a lot of time when changing something in the future, since you only need to change the base class(for example blink twice as fast or whatever). If you have hundrets of titans, it may save you a lot of time. If you only have 3 titans i would not bother anyway. Also in my opinion i would define the behaviour tree in your example more of a state machine - A component would be for example a damage area, that an enemy can have or not have to do damage, imo. But that beeing said, very nice to watch video, i really liked it! Good and clean presentation 👍
This was explained really well! Would you be able to link any of your example code or the documentation/sources that you learnt this from?
Love this. A mix of comedy and learning. Great voice over. Keep it up! Haven't checked your channel but I'll def sub. Im interested in 2D tutorials!
The hardest part here is being organized and knowing how to separate the stages. Create the entire workflow and then couple each stage together.
Hell yes just from the thumbnail. Ur on the right track.
Any coding paradigm that you like is good to go. For me the only restriction i set is: Do not repeat yourself. If I have to update the same code in two places I better write a function or inherit it.
It's best to use both, actually.
That was the point in the video
@@NesiAwesomeness My bad! I commented without watching.
The bottom line is that you can spend hours trying to generalize your code or asset to work in any situation, but in practice your assets will always end up intrinsically linked to your project.
The best way to create is to dive straight in and do whatever works, and then once you have it working then clean it up, generalize, optimize, etc. THat is why they say optimization is the root of all evil. You need to create unselfish-consciously and recklessly, and then when you have something apply your critical eye to it.
I get everything you said Nesi, nice video. I still need to understand what exactly is the function "set_behaviour" doing. Like how does it actually change the behavior to one of the child nodes listed in the behaviour tree?
hi, nice video, what are you using for the animations?
I did a lot of animation, which exactly, the character animation, the motion graphics, the hand writing, the background animation, these all use require different software.
Cavalry
Blender
DaVinci Resolve
I always run into this problem. If the scope of the project is small(most are) and experimental(unique objects all over) you are better off ditching inheritance cause the busywork to get the system to adhere defeats the point of setting up inheritance (same is true with nesting). Inheritance is pretty redundant to begin with as you can just rewrite redundancies in terms of function calls fine and formulate the various types with basic if-blocks. I experimented heavily with bullet outlines across game projects, inheritance structures were never clean always dirty and a complete nightmare to edit/read/update regardless of setup. Falling back on simple principles is actually cheaper(inheritance is usually costly) and way easier to edit/organize/read:
add_bullet("shotgun")
function add_bullet(type)
if type="shotgun" then n=10 else n=1
for i=n do
add(bullets,{x=x, y=y, init=function() if type="shotgun" or "likeShotgunWithLessPellet" then dmg=5.... elseif type =...."})
You can compartmentalize everything with functions and if-blocks however you want just like could classes. It's literally the same thing as you could do in classes just you have everything together and you invoke with a single function call. The point is you don't need classes; you can generate however many bullet copies of whatever variants as desired.
In regards to using classes: to solve the issues of updating I've been throwing in nested blank functions in the middle of the class's methods. So instead of rewriting the entire method for unique object or extend trees all over I can just update the tiny part. This does mean that classes who don't use it will always call a blank function, obviously not ideal to ever call blank functions, but if you calculate the alternative costs of extending all over it's usually better or irrelevant.
Case usage makes every argument irrelevant though, you might have other considerations forcing traditional class outlines. Or maybe you dont need many if any unique objects and dont want to run if-blocks u dont have to...then messy class code is ideal, but can also just solve it without by making separate bullet functions (repeated code can be mitigated with functions returning functions returning the object).
I like to compare most language choices to the lua one of coroutines vs switches....u can write in either to get the same job done, but you can always rewrite your coroutine code into cheaper switch code.
Not going to say it's totally wrong for small experiments, but your example with if branches is one of the worst ways to do this. If this starts to scale at all, you'll end up with YandereDev level code that will be impossible to manage, incredibly hard to read, and have way worse performance than a proper use of inheritance and composition. The program will have to worst-case traverse the whole if-tree until it gets to the case that is relevant for it, which will result in many pointless comparisons and thus significant overhead (definitely more than any potential OOP overhead).
The proper way to do something like this is having your custom guns implement interfaces (aka the composition patterns talked about in this video) for stuff they can't share with the common base class (e.g. hitscan vs. velocity based bullets) while inheriting what's sensible from the base gun (like bullet count and damage). That way you can feed your parameters in directly or even with some type of config JSON or the likes, and it also solves your "blank function" problem. Interfaces enforce contracts, so you can selectively call only those who implement it.
If you do this right, there will be no gun, no matter how special, that can't be modeled like this, even for crazy stuff like bullets that heal or are actually NPC controllers (think nanobots, homing missiles etc.). It will take a bit of planning and foresight to not write yourself into a corner, but it definitely beats having to refactor a gigantic if else block. Especially since every good IDE has features that make editing classes and interfaces as easy as possible, where the same can't be said for hacky if blocks that cosplay as worse versions of a class/closure.
Thank you Bro! ♥
You're welcome
Nice explanation of composition over hesitance.
Misfired the comment button earlier and youtube edit seems broken for me. :S
But I would say that composition more often refers to functional code, or maybe sometimes with OOP interfaces.
I have used the *Item* -> *Tree* inheritance pattern you described though, works well.
Lately though I have started using more abstract classes which actually throw if some tries to construct them directly.
No doubt complexity becomes its own challenge and it depends how much you like the pain of it for the benefits you can gain.:D
I start going sideways after about 4 levels deep in abstraction/hierarchies, but I have seen awesomeness at levels 5 and 6.
It just depends what the use case is.
I have found that the more I force myself to document closer to the root of my abstractions the more focused I am by the time I am punching out the leaf components where everything is supposed to just do what I want.
Essentially, think need near the top, want at the bottom and you should do ok. :-)
Sounds kind of like bevy's ECS paradigm. I have no idea what I'm talking about though
I should probably try bevy soon
The goat has uploaded 🔥
inheritance is the root of all evil
>:(
Great video man! I would love a deep dive on the animations you hooked into this proto. I'm having a tough time with animation/trees with Composition.
I'll see if I can put that together
For my non-gamedev job i use inheritance. Works, everybody knows it, wide spread.. no reason not to. For gamedev i use what's more appropriate given the situation. Usually this ends up being an inheritance focussed system with some composition. The only reason i actively switch to a different style is if the game / module is heavily performance related. In which case data oriented programming is pretty much the go-to solution. While, concept wise, data oriented programming is also very similar to composition, the core message here should be: use what's appropriate for your actual usecase. Nothing is outright better than other things. Trust me^^
nice video mate thanks! can you share your godot themes? loved them
@@loonguinho9798 passivestar
@@loonguinho9798 passivestar
@@NesiAwesomeness thaaanks! 🙂
I found myself cerrin on if my code is clear and well writen more than if it's usefull and working and this stop me from coding too long befor i actually start to make it right
The voice remind me to the Max Payne 1 cutscenes voice over 😅
Lol or the wise worm from IG. 😂😂
I've never built a game before but this video has me very intrested. What language is this and what software are you using to run it.
The software is Godot and the language is GDscript
Content with style. Great video
The editing, the flow, it's amazing!
But some criticism though, at first I didn't know this was about Godot (or atleast, using Godot engine). It would be helpful if you add "Godot" somewhere in the title or description!
And, I have a bit of a hard time listening to what you are saying (since i am not a native speaker), It would be better if you add subtitles(?) or practice speaking more!
Keep this up and you'll be more popular, im sure!
Definitely will do, thanks for the suggestion about subtitles.
I didn't want to make it Godot specific that's why I didn't add the Godot Icon, but I guess I will later
it's actually not about Godot, it just happened that he is using Godot to demonstrate his point. It can be used using any engine or even when not making games
@@joumakesgames I agree, but I thought it would be nice to atleast mention the engine used in this video.
Maybe if it's not a specific thing, it can be added in the description
really nice graphics for the video. wow
Inheritance is for interfacing. Otherwise one should compose. Change my mind.
awesome content
Thanks
@@NesiAwesomeness We need more content creator like you.
Everyone else: trying to understand the video and point out mistakes
Me: mmm.... *Juicy montage*
Lovit! My favorite puzzle I haven't solved.
Lol, can you solve it now?
I was taught OOP as *the* way to program at uni for years, but when I got to my gamedev spec and was given freedom to build my own project any way I see fit - I realized how easier and more maintainable things are when you use composition instead. I wasn't even looking for any alternative paradigm or anything, I was just trying to solve problems and logic always led me to structure my projects with composition.
I like how you use strings instead of enums xd.
Components are great. nothing feels better than slapping some component you've made for something else onto a new object and it just *works*.
@@ince55ant I can't wait to use it when I'm making a game for a game jam
I have no clue what anime those „titans“ are from. But they remind me of David lewandowski‘s animations „going to the store“, „late for meeting“ and „time for sushi“ to be specific… (all on his UA-cam Page)
but not a word about his work on „tron: legacy“, „oblivion“ or „top gun: maverick“
i like your video style
Thank you
My wizard for fixing codes is this process...
I encounter a problem...
I feed the problem to ChatGPT while thinking about it
ChatGPT gives me proposals and I play around with them...
I fix the problem without having to figure out where I made the problem...
I ask GPT to help me revisit my problem and how to avoid it in the future...
I learn and keep going...
You are ready young Padawan. Now go and watch Brian Wills why object-oriented programming is bad video.
Ii think you overused the bad inheritance explanation, because you didn't know how to build your code 2 years ago, inheritance is useful only in a handful, of scenarios. And in the titans case, it is absolutely okay to use it. (You didn't solve the titans inheritance issue BTW Besause it was purely made up so you could rant about it..)
When you are building an inheritance system itr has to be Laid Out Beforehand, wich enemies will need wich things, which ones are the minimum requirements, and so on, if you dont take the time to write down, a plan. Don't bother making an inheritance system.
When people complain that they're so bad, it is, because they are starting to build it without an idea of what they need what they want or how this coding pattern works
Even after the clearly inherited roast, Great video on the component separation.
Is your behaviour tree ,also what people also call a state machine ?
yo cool vid,
i have a question cause i watched at x2 speed and wasnt trying to read code
- what if you dont want to follow the player and the plane together?
in the case of just going to freefall and have the camera follow the player instead
You can just do that, The Camera switches to the "Follow Free Fall" state when the players State Machine changes to the "Piloting: FreeFall" state. If I want the camera to just follow the Player I'd just have the player remain in the "Follow Player" state
@@NesiAwesomeness oh it was this simple, thx for clearing it up fam
Message bus is definitely cool, but I would caution you to separate it out into multiple specialized buses.
Otherwise you end up with the issue of 500 things watching the message bus and getting notified about events they don't care about which can add a lot of extra overhead and lagginess
Noted
idk if the game you showed is just for demo purposes, if it isn't, the movement on the platform is very floaty, like they're on ice, when changing directions
Also W for jump??? it's always space, W is for looking up or interacting with objects like doors or smth
Other than that, neat idea for a game
Thanks, duly noted.
Great video clean editing.
What's that theme for godot?
Im guessing the theme is from Passive Star
Passivestar
Inheritance is for people creating libraries and APIs, people using those libraries to create a product should use composition
Agreed