Using Composition to Make More Scalable Games in Godot

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
think of every component you build as your own Godot node ask yourself this what is the minimum set of reusable functionality that you can put in node form that is guaranteed to be useful across a number of unique scenes you may have heard the phrase prefer composition over inheritance when you are making games in the Godot engine but what exactly does this mean what is composition how is it more preferable sometimes than inheritance so composition is essentially the composing of functionality out of smaller parts of functionality this is actually how Godot works as one of the main pillars of the engine so I'm going to create a new entity for my game and I'm going to show you exactly how composition can be used to make a more maintainable and a more scalable game I'm going to create a new 2D scene and I'm creating an entity which has a base of a character body so I'm just going to go ahead and create my character body 2D so there's my node that gives me movement functionality now this character is kind of bland so we probably want a Sprite so let's add a Sprite and let's bring over this asset so I've actually already used composition here I've used the character body 2D so that I can use movement and collision functionality but I didn't have a way to provide visual to this node so I had to use a different node for that which is the Sprite 2D this doesn't have any movement capabilities associated with it whatsoever it only displays my Sprite and the character body 2D is actually not complete either because I need to add a collision shape 2D which is responsible for holding a certain shape so you can see I'm already composing my entity just using the built-in Godot functionality I've provided a visual with the Sprite I've provided a collision box with the Collision shape 2D and I'm using the character body 2D for movement of course I can do a lot of other things too if I need a timer I can add a timer to this node if I need audio I can put an audio stream player each of these has a very specific set of modular functionality this audio stream player 2D is not unique to this scene I can use the audio stream player 2D node anywhere that I want my game because it's minimal and it's modular and it's designed to be used in many different contexts in a generic way so we can actually extend this sort of design pillar by creating our own components that have game specific logic that allow us to build the game in a scalable manner so for instance this entity needs to be able to be shot and killed by the player well one thing I could do is I could use inheritance and I could define a entity-based class that has all of the methods that I need to handle collisions and take damage from player attacks the problem is that I paint myself into a corner by basically saying all of my entities have to use this behavior and as soon as I have an entity that's going to have different behavior that is going to need to engage with the game in a slightly different way I may actually be limited by the functionality that I've written into my entity based class and I may need to further fragment the classes in order to get more granular with the behavior if you have a lot of entities in your game or in my case I have a lot of different gun types so here I've got a worm gun and I've got a shotgun do you see how they have wildly different Behavior the worm gun shoots worms that crawl along the floor and move in a random pattern and the shotgun is more predictable and shoots three bullets at a time with the amount of guns with different behaviors that I have in the game extending them all from the same Base Class would become very unwieldy very fast and it would limit my ability to make new wild gun designs so that's the problem with inheritance so what can we do to use the composition pattern in our own way alongside the Godot way of doing composition I'm going to show you how we can do that by building out this entity for my game right now so what does this entity need to do well it needs to accept hits from the player so I'm going to go ahead and instantiate a child scene and I have this health component and health component has a configurable amount of Max Health as well as this suppressed damage float which is UI related that's it and this health component if we go into it it's written in C sharp but it's pretty easy to understand this health component it has a max health value and it has a current health value the only thing that this health component does is it has methods for dealing damage and healing and also setting up the initial health and other health scaling related methods here that that's it it also emits signals when the health changes or when the health hits zero there is no other logic in this health component all it is doing is keeping track of the current health number and emitting signals related to health changing or health depleting to zero so this alone doesn't do anything I could apply a million damage to this entity and nothing would happen why because again this is only keeping track of Health okay so how do we actually get the entity to die well the entity needs to be able to receive hits again this health component only keeps track of the health number it does not handle collisions so what I need to do is I need to add my hurt box component and my hurt box component has a base of area 2D and down here it has a way to connect a health component so this health component can actually be filled in with our health component and now if I go to my hurt box component what does this do well this is the same sort of thing as the health component this has methods related to Bullet collisions so there's a method here can't accept bullet Collision that determines the validity of accepting a collision and then there there's this main method here which is handle bullet Collision which takes a bullet as an argument and then it does everything it needs to do to handle that Collision including emitting a signal that can be listened to by other nodes the other thing it does is it takes that health component and if the health component is supplied it will call damage on that health component with the damage that it is receiving from that bullet and that's it so this is using composition in the same way that the character body 2D is using composition the character body 2D works by itself but it doesn't work entirely until you define a collision shape well in the same way I have the hurt box component which is fully capable of accepting all collisions but the collisions will have no consequence until you've connected the health component to it so that it can actually change the health of the entity but this hurtbox component by the nature of it being an area 2D actually has another component that it needs which is another Collision shape 2D which defines the hurt box area of this entity so I think you're probably understanding the pattern by now so I have another component for this called a velocity component let's take a look at the velocity component you might have guessed this by now this velocity component only contains methods related to manipulating a velocity variable all it does is provide acceleration functions or deceleration functions or functions for manipulating the velocity or getting details about the velocity that's it so it keeps track of a calculated velocity that I can then use in any context that I want it doesn't even need to be related to a character body 2D and the nice thing about doing this component design too is that you can just export your variables so you can change for instance my max speed or my acceleration coefficient so this velocity component won't move the skull until I explicitly call the move method on it but it also doesn't do any pathfinding so we're going to introduce the pathfind component and this pathfind component accepts velocity as an exported node so I'm going to drag the velocity into there and why does the pathfind component need velocity well the reason is because I have a method in here called follow path which will take the current path and accelerate the velocity in the direction of the current path so that's why a velocity component is needed but again this class only has methods related to navigating in a 2d environment and that's basically my entity setup now with this pattern you are still going to need to write scripts in the same way that you have to write scripts if you're just building out your scene with the built-in Godot nodes right but the way that you should think about it is that your root script so the script that I'm going to add to this skull root here is only designed to be the glue that connects everything together it should be a lightweight script that just handles the specific logic that your entity needs and the specific interactions between each component so let's go ahead and write that right now again this is C Sharp but it should be pretty easy to follow so look at how lightweight this script is going to end up being so with just this Bare Bones script my entity is now going to move toward the player so let's go ahead and test that there we go I have an entity following my player and again I don't need to do any code to change things like the movement speed or like the health so let me show you so my velocity component here let's turn up the acceleration coefficient and turn up the max speed to be very high and then I'm going to take my health component and I'm going to turn up the max Health to 100. so now if I run it look at how much faster the enemy is moving and also how much more damage I can do to it before it does so that is basically how you can design your game to be maintainable and scalable think of every component you build as your own Godot node ask yourself this what is the minimum set of reusable functionality that you can put in node form that is guaranteed to be useful across a number of unique scenes health is an obvious one hurt boxes are an obvious one so think about that where this can go wrong though and you have to be careful about is you don't want to get so granular that you end up needing to build like a hundred components in your tree you should still use a script for the very specific functionality for instance I don't do my state machines in a generic way I don't do them in a node-based fashion I just write the state machine Logic for each entity because because they end up being so specific to that entity's behavior that it's hard to modularize into a reusable system so what I did was I made my basic state machine class reusable but then I just have to write the code and yes there's a lot of repeated code a lot of times the target acquisition is very similar a lot of times the attacking is very similar but they're different enough to the point where it's not worth trying to come up with a generic solution it just ends up becoming more complex so that's it that's how you can make your games a little bit more maintainable and scalable using composition in Godot I hope that you learned something and I hope that this was useful subscribe to stay up to date with gun game and I will see you in the next video thanks for watching this video please consider liking and subscribing check out the links in the description to sign up for my email list at firebelly.com to learn how to build a 2d platformer in the Godot engine and to support my work by purchasing one of my games on steamor itch.io all those links are in the description [Music]
Info
Channel: Firebelley Games
Views: 183,456
Rating: undefined out of 5
Keywords: game, game development, indie game, devlog, godot, c#, pixel art
Id: rCu8vQrdDDI
Channel Id: undefined
Length: 10min 13sec (613 seconds)
Published: Sat Dec 24 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.