[00:02] on it returns the file size. Now imagine you have a folder and calling size on that returns the combined size of everything inside. Now that idea where one thing is treated the same thing as a multiple is basically all over the place [00:17] multiple is basically all over the place in menus, HTML, UI frameworks, game engines, pricing bundles, basically anywhere you have a some sort of tree. So today I'm going to build a tiny little game engine in Python to [00:31] little game engine in Python to understand a pattern called composite. Yes, I found another design pattern. In fact, I still have like a list of 10 or fact, I still have like a list of 10 or 20 of these patterns at least to go and [00:43] then I'm sure in the meantime I'll make up more. So for the time being we're all good. There's going to be plenty of pattern videos in the near future. But the point of this video is not really that we're building a game because [00:55] pattern you're going to see it everywhere and you can apply it in many different situations. The best part is by the end of the video our entire game loop is going to boil down to just two lines of code. [01:09] Let's dive in. I have very basic setup here. It's a canvas class that just allows us to draw some text on screen and that's what we're going to use to create our game world. Now of course our game needs objects. [01:29] an Aryan codes video should always have a protocol class. So then we have that out of the way. From typing we're going to import now. Okay, we have the game object protocol [01:44] Okay, we have the game object protocol class. So what is a game object? Well, should a game object do? Well, in essence one thing that a game object essence one thing that a game object should do is it should be updated. [01:58] Another thing that we want to do with the game objects is render it. it's going to need a canvas object. That's the class that I [02:10] canvas object. That's the class that I defined right here. type. Final method that I'm going to add is a tree method that will return a [02:23] string representation of the game object tree because that's going to be helpful for debugging. And let's also give that an indentation so that gives us some an indentation so that gives us some control over what the tree looks like. [02:36] So this will be a string. This is a very generic object class in a game and it works for basically any type of game object. It can be a bullet, it can be a player, it can be an alien, whatever. But the interesting thing [02:50] about a game is that we don't have single objects. These objects are often grouped. Right? If we have a car, it's going to have a steering wheel and wheels and doors and all sorts of other things. The idea of the composite [03:03] pattern is [clears throat] that we use exactly the same interface for a single game object or a group of game objects. And by the way, patterns like this are only useful if you understand why they work and when they don't. That's exactly [03:17] what I focus on in software design mastery. It's not just a course about patterns and principles, it's a complete system for understanding software design fundamentals. Learning how to make better design decisions while AI helps [03:30] to learn more, join the waiting list at arnon.gold/mastery for free. The link is in the description. Now that we have this interface, we can create a class composite game object. And [03:45] it's also a game object so it's going to have these methods right here. The only difference is that this one contains children. Before I continue, let's import data class and let's turn composite game [04:00] object into a data class. This is going to have children. to have children. And that is going to be a list of And that is going to be a list of game objects. [04:16] default factory of list game object like so. And field we also need from the data classes package. from the data classes package. Let's add a few helpful methods. [04:39] Like so. But now we can implement the interface that we defined right here. we can simply iterate over [04:52] iterate over self.children. And call the update method on the children. And rendering [05:15] complexity. Here, let's say our game object also has a name and an alive boolean. Objects in games tend to die pretty quickly, so I think that's going to be [05:28] helpful. And of course, we're also going to need this here because we need to to need this here because we need to implement that interface. And now we can even do things like update the children and filter out the [05:42] update the children and filter out the children that are no longer alive. >> Like so. And another thing you could add is a few hooks. So, we could have a on [05:56] is a few hooks. So, we could have a on update [06:09] And then we can call these from the render methods. can implement these methods and then we can do something whenever the object is can do something whenever the object is rendered or updated. [06:26] now composite game object is basically a list of other game objects, but the interesting thing is that a child can itself also be a composite game object. And then when we call update, it's going to recursively call [06:40] update on all of the children. And the same goes for rendering. And we could also add an implementation for this method that then does exactly the same So, here I already made an implementation for this, which is pretty [06:54] simple. It's going to add all the children to this list of lines and then it's going to return that as a string. And also that works recursively because we're calling three on the children. Now, what we can do is have a game class [07:07] that has a root game object. It has some other things as well that we're going to need in the game, like the width, the height, the score, uh these kind of things. And then we also have inside the game class, we have an update and a [07:20] render method. And it's going to be helpful if we call update on the game object that we actually pass the game class along with it because then the object can get some information from the game. So, let's also pass this here as [07:35] game. So, let's also pass this here as an argument like so. And let's also update the interface right here. [07:48] game loop, it's really simple. There is just in the update method here an update And there is a render call, and that's basically all that is needed. All of the different objects, they take care of themselves of updating and drawing [08:03] anything about what's actually in the game. It doesn't know about players or bullets or whatever. It only knows that there is one object called root. And [08:15] whether that object represents a single game object or an entire hierarchy, it doesn't matter. Now, once we have this system of game objects and a game class, we can now build a level. So, from engine, I'm going to import the [08:30] composite game object, and now we can create a build create a build level function that returns level function that returns a composite game object. [08:46] hierarchy, which is going to be the level. And this is going to have multiple other game objects. So, one of them could be the world. And we can have another composite game [09:00] And we can have another composite game object. And there we can also define other game objects inside the hierarchy. So, we end up with this whole hierarchy of objects right here. Now, of course, it depends [09:14] on the game you're building what kind of hierarchy you're going to have, right? So, what we have here is a couple of example game objects. For example, I have a sprite class that has a name, it has an X and a Y position. Uh it has a [09:29] character representation because this is just a text-based game, but most just a text-based game, but most importantly, it has an update render three methods so that adheres to the game object interface. [09:41] is a sprite. Uh and it this has an on update implementation. So, if you give specific commands, it's going to change the way bullet class, which again is a sprite subclass. It [09:57] implements on update. And we have an invader. Oh, wait a Space Invaders. What's going on? Which is also a sprite. We have a bunker. We have an invader fleet, which is again a composite game [10:10] object. So, as you can see, in code we're building up this whole structure, the whole hierarchy of things that are in our level. And then you can use these objects to actually build the complete level. [10:23] invader fleet, it has bullets, it has the player, it has bunkers, and it has a score HUD. The most interesting part here is that each of these objects is very nicely decoupled because they all [10:38] focus on their own behavior. Like the player, for example, it just focuses on on its own behavior, right? Depending on the commands, it's going to do something. The bullet is the same. Like it simply moves in the Y direction and [10:56] it sets itself to false if Y is negative. We also have invader that basically checks, "Hey, am I colliding with a bullet?" That's very rudimentary collision detection here. But if so, then oh, the bullet is no longer alive [11:10] and I'm also no longer alive and the game score is incremented. Right? And then you have a simpler object like a bunker, which just renders itself. Basically, it doesn't need It doesn't have any behavior. The invader fleet is [11:25] a composite game object, so it manages a number of invaders, which it adds right here. And so, you see we built up all this behavior in a very structured way, which is really neat. Now, I know you've been waiting for this for a long time, [11:39] so let's actually play this game as we have it right now. This is what it looks like. I'm the A character from Iron Colts, obviously. And I can basically move to the right, and then I can fire some bullets like so. [11:57] I'm not, by the way, a uh very good player, but well, at least I can do something. And so we can now play this little game. It's really neat. Now, once the data is a [12:10] tree, then many operations naturally become recursive. Like updating is recursive, rendering, printing a tree is recursive. But searching can be recursive, too. Here's an example of how you could implement that. So we have [12:25] find all, where we're going to look for all the objects of a particular type. And it's going to return a list, and we're going to keep track of the matches, so initially that's zero. And then we just check, "Hey, is the root an [12:38] instance of the class that we're looking for?" If so, then we're simply going to append it. And then if it's a composite game object, then we call this function on that particular child. And that way we recursively search for a [12:51] particular game object. Or here is another find function that looks for a group with a particular name, and it simply searches through the children. the name matches, then we're simply going to return that. And otherwise, [13:06] it's going to raise some lookup error that the group is not found. The nice thing, by the way, about using the modern generic syntax of Python what I'm doing here, is that if you call this function, you actually get back the type [13:18] that you expect. For example, here we have the Invader class that searches for bullets, so that calls find all, but then the bullet is actually going to have the correct type because we use the generics here. And [13:31] same in the invader fleet, this looks for the invaders, uses find all, and there actually invaders is now a list of invaders, so that's what you need. In my opinion, a nice example of modern Python making a classic design pattern feel [13:46] much cleaner. Now, while I was working on this example, I remembered an old game I built in JavaScript like 10 years ago or something. Uh this is what it looks like, it's called Penguin Pairs. You can uh play the game like so. [14:00] Unfortunately, since this is JavaScript, it's actually still runs in the browser. So, I have my levels, and what I can do here is create these pairs of penguins. uh you can only make a pair if it has [14:13] penguin falls into the water, so you can't create a pair anymore. So, you have to do stuff like this, and then we can make a pair like that. And then here we have another level, so if I click this [14:28] penguin first, well, that's not going to work because it's just going to crash. >> [sighs] >> And then we can do this one like so, and then we have this one. So, here uh I think we first need to do this, [14:45] then this, then this. So, you see it's already uh starting to become uh kind of interesting. If they have different colors, they don't equal lights, so [14:57] we have to make them matches like this. Somehow it's I've played this game so often while I was working on it that uh in in the back of my head of how to solve these. So, here uh unfortunately, [15:11] solve these. So, here uh unfortunately, we have to sacrifice the green penguin in order to solve the puzzle. Sometimes that happens, right? Sometimes that happens, right? Um we also have multi-color penguins, [15:24] Uh, let me just do this and this one pairs with basically anything. Uh, seals, they don't match. They you simply move them like this. I won't play the rest of [15:36] this level, but maybe uh, you want to try and finish this one on your own. that even though I built this way 10 years ago, I used the composite pattern even though I didn't know it was called like that. Here, for example, the [15:51] structure of all of these various game objects. And as you can see here, I have the JavaScript file which has a game object. Uh, yeah, it's a bit weird the way I did this. It worked with like prototypes [16:03] before we had uh, reasonably well-working classes in JavaScript. But, in any case, uh, this is kind of a game object class, but has position, velocity, it has a parent. And interestingly, there's also a game [16:17] object list which has exactly the same interface. So, this has a uh, let me just check where that is. Uh, we have handle inputs, we have update, and we have draw, very similar to update and render that I had in the Python [16:32] the game already has exactly this structure. And if I recall correctly, you can even see the specific game objects here. So, for example, here we objects here. So, for example, here we have the animal class, which actually is [16:47] a game object. You see, it's a sprite game object. And this has handle input, it has update, and etc. Over here, I have the level class, which is a game object list. And [17:03] this has animals, sharks, that's another level that I didn't show you, by the level that I didn't show you, by the way. Uh, different kinds of buttons, etc., etc. So, essentially, the composite pattern is already in this [17:15] game, and it works incredibly well. So, what are some advantages of the composite pattern? Well, client code treats one object and many objects exactly the same, which is the main power of the pattern. [17:27] Uh the object model mirrors real-world hierarchy, which is nice. And if you add new groups or new kinds of objects, it rarely changes the existing code, which is really neat. And the game loop, as you saw, becomes really small. We just [17:42] the rest of the rest is done recursively. Now, that's also one of the cons, because recursion has a performance cost. And if you have very large trees, maybe huge levels, maybe you're building, I [17:56] don't know, GTA 6 or something, well, then you're probably going to get a Python. So, don't do that. And the second issue is that the composite only thing. You still need to handle communication between objects, and [18:10] that's a separate design problem. And if you're not careful, you may even end up with a bunch of coupling that all of these objects then depend on each other another area where you need to pay attention, especially in games. [18:24] Um also, another con is that if you have large trees, these are notoriously difficult to inspect or debug without having some visualization tools. having some visualization tools. So, don't use the composite if your data [18:39] isn't hierarchical, or don't build trees just because you now know this pattern, right? Also, don't use it to force unrelated objects behind some same interface that's actually not really the same. In the end, if half of the methods [18:53] empty, then yeah, maybe the abstraction you chose might be wrong. Now, if you enjoyed this video and like to see more practical design videos like this one, don't forget to like and [19:07] subscribe to the channel so you don't miss any of my upcoming videos. In short, composite is one of those patterns that appears everywhere. File patterns that appears everywhere. File systems, menus, UI frameworks, games. [19:19] Once you see this hierarchy, treating individual objects and groups the same leads to much simpler code. But, I'd like to hear what you think. What's the most interesting place you've encountered this pattern? Let me know in [19:32] YouTube thinks you might like this video next. Thanks for watching and see you next. Thanks for watching and see you next time.