 Hi everybody, I'm Piper. So, day job, I'm a Python developer. Specifically, I do server work, a little bit of full stack. But my hobby is games. I really, really enjoy games and I like teaching people to make games. So if anyone's been to PyGotham before, I have given this talk, or I have given a talk about PyGame three or four times at major events and some smaller ones, a couple of tutorials. I often get pointed at when people start asking about making games in Python, among the Learn Python NYC community. So when it comes to games, games have become my hack space. When I wanna learn a new concept, I generally pick up a game idea and I start building it. For example, at one point, I decided I didn't understand MVC well enough. So I built an MVC framework from scratch. Don't do that by the way. Other things I've done, I've taught myself linear algebra, trigonometry, things I didn't learn in school because that's what you need. So games have been my big way of pushing myself. I also do some other stuff. I'll build a new server for something but it's not the thing that drives me. So in this talk, I wanna cover a handful of things. It's going to be less code specific. There's actually no samples in the slides right now. So it's mostly ideas. The big thing I really wanna focus on to start with is game loops. A lot of people I've met when they start making games aren't really sure where to start. So the first section of this talk, that's what I'm gonna focus on. What a game loop is, how it's done, just the absolute bare basics. After that, we're gonna move on to patterns and anti-patterns. In learning to do games in Python, I've read a lot of books and there are a lot of bad habits that get taught. They make sense because you use the simplest concepts to teach things. The problem is as you start getting better and building more complex games, those patterns are going to be the thing holding you back. So I'd like to point out the better way to do things so that you know ahead of time and can make the decision on when to break. After that, I'm gonna talk about scenes and design patterns for scenes. Scenes is a concept I will discuss later. It's a word you will see a lot if you go and research game development and game design. If anyone's ever touched unity, the scene object is a thing that is core to the way that engine works. So scenes are important. We're gonna talk about those for a little bit. And then after that, I'm gonna get into three different game frameworks that are available in Python. That is not an exhaustive list. There's quite a few. I'm going to focus mostly on 2D gaming mostly because that's what I like doing. And really they all kind of run on the same C code in the background. So there's not really a lot of difference for your hardware, but there's a lot of different opinions among these different things. Working on way too many things. So let's get into the game basics. So the first thing you need to know, games are infinite loops. If you've ever built a server, if you've ever done a GUI, anything like that, you're doing all of the same things. You've got an infinite loop process that will run for as long as you need it to run. You're gonna deal with inputs. You're gonna process information and then you're gonna give output. That's it. That really is all a game is at the very bottom. In this case, we're really looking at these three things because these are what you'll do in a game. Because games are real time unlike, say, a server. You have to keep doing your updates much more often than other types of long running processes. With input, input's kind of funny. It's actually a fairly hard problem, especially in Python. We don't have a lot of good tools for handling it. In my designs, I tend to use the SDL library, which is the simple direct media layer library. It's written in C. There are Python bindings. And this gives you more or less direct access to the keyboard, to the screen, to the mouse, things like that. It also can get you access to the CD-ROM drive and a bunch of other things, but mostly you don't need those. But the way you use the data you can get from SDL is going to change things. And it's gonna really be based on the games you wanna make. If you need something where every input counts, say, like a fighting game, you're probably going to want to check every single event. So most hardware uses event systems. You will get input that is, you know, a key was pressed. If anyone's worked with JavaScript and the document APIs, same sort of idea. Any of those events, you can also get other places. So mouse clicks, things like that. You'll get all these inputs. And then the pattern that most people will use is taking those and applying them to variables in different ways. A really common one I see is you have your up, down, left, and right buttons bound to arbitrary keys. And then you just listen for every event and change whether it's pressed or not in your code with each event. It's not the best for action games. So I will talk a little bit about that. You can check during the simulation loop for the state of keys. Usually better if you're not worried about every input counting. So if there's some error zone you can work with, definitely use that. It makes your code a lot cleaner. But once you've decided how you're handling it, that input becomes your player. You literally don't have to think of your player as another person. You can think of it as simply those inputs. And once you start doing that when you wanna test your games, it's pretty easy to inject a stream of events if you want to. So that's kind of why I wanna just focus on that. It took me a long time to realize you could do that. And then when we get to the simulation, that's where all the fun stuff happens. If you're writing a game, that's where the problems are. Interaction between objects, movement, gravity is a really fun one to try to solve, by the way. And so in that loop, pretty much the most common type of update you're gonna put on any object is moving it. Because almost everything moves in a video game. So almost always you're gonna want some sort of universal function for moving things and reuse it as much as you can. Because it's all basically the same. The second is collision detection. Again, almost every single game has some form of collision detection. I mean, action games are obvious, shooters fighting games. But even things like if you have a point and click adventure game, the interaction of the mouse and the game objects is another collision. So this is a very important thing and getting your brain around it is important to being able to develop other things. The nice thing is there's a lot of tools out there. Most of the libraries you get will have some sort of collision detection function available for you. Pi Game has a really good one. Pi STL, less so, but it exists. And again, it exists in pretty much everything. There is some sort of collision detection done for you. But if you wanna go and design your own, two really easy ones to implement. Circle collision is a very, very simple calculation. We're talking two variables. It's essentially two vectors and two points and one comparison. So that's a good one if you wanna go research that and build that, it's a good one to start with. The other one is access-aligned bounding boxes. That's the name you'll see. Basically again, you're just testing for intersection between two squares essentially. And then the last thing, and this is where code reuse gets a little less useful, behaviors. So the way things interact. So once you know if something's collided with another thing, you need to know what those two things should do when it happens. And there's a lot of models for this. If you're using a proper physics engine, that will define a lot of the interaction when two things meet. If you're not doing that and you're using something like kinematic movement, which means you've programmed your movement, you're going to have to define those interactions yourself. So that's another important thing to think about. Then talking about rendering. So I am not a graphics programmer. I'm going to say that out front. I tend to avoid doing graphics for as long as I possibly can. I'm going to use primitives as long as I can. So most of my games are a bunch of colored squares shooting particles at each other. This does not necessarily mean that's what you have to do, but the rendering is a separate concern from most of your programming. Animation loops really don't need to be hooked to anything but the state of the object. They need to be hooked to the state of the object, not like a specific point in that state, if that makes sense. At one point I was going to design a small example for this of a coin flipping. And really all you need is the flipping state and the animation can just look at time and do the animation for you. The game object and the animation don't have to be connected at all. It's actually the better pattern for most of your work. We'll talk about that more later. But yeah, so really most of your libraries are going to give you some sort of hardware access. As I mentioned before, the word that gets used in the SDL world, which again is what I know the best because I do 2D games, it's called Surface. It's essentially a software buffer. It's in memory access of all the pixels. And then you have a render that pushes all of that to the screen. And that's all that is. You tend to not do direct pixel access when you're using these, mostly because it's expensive. You also have to lock the object, which in Python is its own little pain. But there's usually draw functions and things like that. Blitting is really gonna be your most common way of getting images onto the screen. Basically what a blit is is you take one software image and apply it to another one. Really useful tool. Again, all of the libraries I'm gonna talk about later do it in some way, shape, or form. Mostly through an SDL call that does it for you. And that's really the basis. If you can make this happen, you can turn this into a game. Now obviously it won't be much. You really get one scene here. So things can get a little bit more difficult when you wanna start adding menus and different levels. So this is really where to start. Making a tiny arcade game, this is all you need. So as I mentioned, anti-patterns. There are so, so many. I'm going to just talk about a handful. Mostly things that really it is good to learn now so that you don't do them. It really will make debugging easier later, most of them. It will also make more complex things much easier to do. So pretty much all of these patterns are great if you're writing an 100 line example for somebody, which is why it ends up in code books. That's not really what you're gonna be doing when you're really making games. So let's avoid them. So the first one that you'll catch, I don't have a code sample as I mentioned, but almost every tutorial is going to teach you per frame updates. And basically what this means is you have your game object and you want to move a certain number of pixels every frame. It works for a small sample, for a small example. The problem is it's totally fixed on the processing power of the computer. Get a faster computer, get a faster object. Not really the best way to design. The big key there is back off and parameterize your movement. So instead of saying five pixels a tick, say 100 pixels a second or some other similar value, and then you just have to make sure to give these functions the time since the last update so that they can move. So really easy fix, it will make your game so much easier to balance. Just like I said, this is a good one. This is the other problem. Pixels is the worst unit you can pick. Pixels are by definition discreet. You cannot render at a half pixel. We're not discussing points, we're not talking high resolution or high density monitors. Just don't go there, trust me, I've gone down that rabbit hole too many times now. So basically if anyone has done matrix transformations and things like that, that's essentially what you've got to do here is you're going to have the game, the game space and you're gonna have the pixel space. And all you really wanna do is when you go to render, you're translating from one to the other. The nice thing there is you can actually use continuous units. So you can move one third of a game unit and it just works. Can't do that with pixels. If you move one third you either don't move or you do move and you have to pick one. So you get a lot less jittery responses when you do it this way. So if back at the game loop, so that sample, not that it was much of a sample, our render loop and our simulation loops were fixed together. So you've got one frame of simulation for every frame of rendering. So that means that in general, you're locking yourself out to about 30 to 60 frames a second depending on what you're working with. There's really no reason to do this. And again, this is another one it took me a long time to get through it. But once you figure out how to separate those in a useful way, it's a really easy thing to do where you can let the rendering happen at 30 frames a second, 60 frames a second, and then the simulation can run 120 frames a second or more if your computer's fast enough. With the only catch that rendering is extremely expensive, especially when you're using things like SDL. At least one of my games, I'm stuck at about 15 frames per second because it takes 33 milliseconds to just render the string. That's none of my simulation. So you may end up with that bottleneck again and that's when you start exploring other options. But yeah, now once you fix that, it's just really easy to start moving things around, changing how you render becomes really simple because it's a completely separate thing, compares stuff, hooking up the same game to a web view becomes almost trivial. And then the last one that I'm going to talk about is mostly a pet pee. Don't use wild sprues. The only way to get out of a wild sprues loop is a break or a return break. If you have any reason to say you're locking objects as part of your game loop, you need to also unlock them when you're done. If you break out of a loop, that will not happen. So switch up your true, with the running variable of some sort, running whatever you want. I like the wild running. It's just a really nice set of code, nice. And then you, when you need to end the loop, you just switch the variable to false. The variable to false. Much cleaner, much cleaner. This also gives you the advantage of you can do set up and pare down as a separate and separate. So if you start a new scene, starting with you can do your set up, you can run your loop, run end the loop, do whatever turnaround you need to do it and then return to the old one. So this is actually where I'm going to get into where I'm going to get into because this is where that gets taught, is where I'm using this model, using this model. So scene design, sorry that this is just a, so when you start, my first game, my first game is an atrocious, it's my conversion line, my DS line, multi-nested loop. I think it next came to 40 minutes. I mean, we're all, I think it's the, we're all, I think it's the, we're all a lot better than that, we're a lot better than that, sometimes we just do what you have to do. The problem is that we, the priority is that we have to find anything, or find anything first of all. If you need to find exactly where you need to find exactly where the loop is, you don't have to do the problem. But the other thing is, but the other thing I couldn't reuse any, I couldn't reuse any, I couldn't reuse any of that loop, I think it's like our L loop, some of the files using the same basic structure. So the first thing I learned to do, the first thing I learned to do was to start using, start functions for my loop. So you would define, you would define your menu loop, your menu would be a single function, it would be a single, you're app loop calls it, you're app loop, very cool thing it does, it's a very good thing it does. That menu loop, when that menu loop, start again, start again, hops forward, that hops forward, runs another function, runs another function, ends it, when the game ends it, sends it out and back, sends information back. Really nice, really simple, nice, really simple. Problem is, asking data back and forth, asking data back and function, sending it a little bit of guilt, especially when you start to separate and you start to separate and you're seeing like two or three intermediaries, intermediaries, because the only way to get data is to get data around and use global variables, use global variables, and we all know we don't want to do that, we all know we don't want to do that as much as possible, it's very messy, it's very messy. And the other problem, and the other way to do it is you can't always do it in GnRamper and be able to ramper around and pull it back, be able to return back, be able to return back. It worries a little ugly, and a little ugly, and it's really easy to mess around and you know, generally I don't do that, generally I'll not find out if we're a little later, three or four weeks later. So, one of the examples that I'm going to do with Eagles, that I'm going to literally use, really, really well in this talk, so all you want to do is to write all the functions and write them all together and let them run together, I haven't been able to do it, I haven't been able to do it, of course it's probably, of course it's probably my opinion, better in my opinion to, might not be to, I forgot to tell, I forgot to tell you what I was seeing. So a scene, a scene, as far as game development, is a section of the game, a section of the game that is just, so going back to Tetris, going back to Tetris, the title screen, is the title screen, is a scene, it's not much, pretty much, pretty much, it just doesn't really the one thing, and I'm waiting for you, then you get your menu, then you get your menu, it's a separate scene, it's a separate scene, it's a loadup separately, and then you move through the menu and select options, and then you start your game, you have another scene, you have another scene, then you have your high score, then you have your high score, another scene, another basically easy, so basically the gb part, this is pretty much part of what it's going to be, it's going to be a, catch what it's going to be today, but it's going to be jrpgs, every battle is its own, every battle is its own scene, generally those will reuse a lot of the same codes, so it's not as bad as you think, but every map is going to be its own, any full screen menu is generally going to be its own, so there's just a lot of little pieces there, and so that's the thing you have to be aware of, but moving to a classic-based scene, what this lets you do is create an API for your scene, that say every scene has a run function, and that's the actual running loop, but you don't have to put a setup in your running loop anymore, and you can put it into the init function you need to, so when you need to spawn that player, figure out what enemy should be in the scene, where you can put it separated here, now you can put it separated here, code a little bit more, very easy to find what goes where, you can also plug in things, passing in information, or passing information between functions becomes much easier, it's a classic namespace is awesome, and if you pass scene to one scene, to a scene it spawns, the lower scene can modify the upper scene, so this is just all little tricks you can do as soon as you have a more robust object to work with, and then in a couple of cases, you might want essentially a single thing, single thing classes are really big in game design, Python does not do single things very cleanly if you're trying to make them a class, but we have modules which are inherently singletons, no matter where you import a module from, it's going to have the same data as any other import, so if you make a module figuration module, and change values there, any other module in the game can look at those changes, so if you need a singleton object, modules are the way to go, they get a little messier, you will be using global variables because those are actually the module attributes are your global variables at that point, but it lets you get away with a lot of things, like not having to check that you only have one, so really I suggest people, if you need a singleton build a module, so much nicer, but I don't suggest it for everything because you really do need multiple scenes a lot of the time, and then this last one that I mentioned is that scene management up here, this isn't really scenes, this is kind of a different pattern on how to use scenes, so instead of putting loops into the scenes themselves, you have a master object that will run your game loop for you, and all you have to do is set up an API between that object and your scene objects, so instead of having a bunch of loops, you have discrete functions in the scene that do various things, like rendering, updating the variables, things like this, and then your engine, this management object, will call them in the correct order, so this is a, again, it's kind of more complex, but this is a really, really good design pattern because loops are hard to test, if anyone has ever tried testing something with an infinite loop in it, you know what I'm talking about, it is not a fun experience, so the more you can pull loops out of your game so that you have procedural code, you'll be much better off, so now we're going to talk about frameworks, so the big thing that you're gonna notice when you start exploring frameworks, every single game framework on the planet is extremely opinionated, there is not a general game engine out there, Unity's pretty close, but if you've ever spent time looking at Unity code and playing Unity games, you start to see similarities because there are things that Unity does well, and then there are things Unity doesn't do well, so games tend to have a feel because of their engine, and frameworks are the same way, some things are easier, some things are harder, in this case, starting with PyGame. I have read a lot of books on PyGame, as I said, I have given the same talk for about three years about this library, the problem with PyGame when you try to learn it is a lot of people teach PyGame because it's easy to teach the tools, but they teach it in different code styles, I see a lot of people who would normally teach their class in C++, we'll just use PyGame and use all of the same C++ patterns and apply them to Python, the problem is, again, PyGame's opinionated and it doesn't like that pattern, in general, there's three major classes that you wanna look for when you go looking at PyGame, the sprite classes are the important one, all of your game objects should be sprites, the reason for this is because there's a lot of tools in PyGame that expect you to already be using sprites with sprite groups, so if you need a new game object, you make a new sprite, sprites have a handful of attributes that they need to have, specifically, they need an image which is going to be a surface like I mentioned earlier, they also will need a rect, which a rect is an access-aligned bounding box, thankfully, PyGame provides a really, really, really nice one, their rect class is got so much going on in it, it is a very useful thing, if you're going to be using PyGame, use the rect class because it will be your favorite part, it gives you some, has collision built into it, so checking collisions between two rects is very simple, it has some convenience functions, you can move rectangles around by signing to a corner, to the top middle, to the center of the rectangle, whichever one you decide, so it's just a very convenient object. The other thing a sprite needs is an update function, and this is a part that a lot of people I've taught have had trouble with because that update function is actually intended to be used as a callback, it's not that way for any specific reason, it's the fact that when you start using sprite groups, sprite groups call the update function, so you put your behaviors into your update function, and then you put your sprites into sprite groups, and there's a handful of each of these, they give you different features, some of them are just rendering bases, but there's a lot of neat tools there, you'll definitely have to explore it if you want to get into PyGame, but groups, so groups are container classes, they hold sprites, nice and easy, right? The two important functions on your sprite groups are going to be the update function, which anything you pass to the sprite groups update function gets passed down to your sprites update functions, so instead of having to put an update for every single individual sprite, or writing a loop to go over a list of them, you can literally just call update on the group, and it does it all for you, so that's step one, and then again, this is why this is opinionated, and you should know this pattern. The other one is the draw function, instead of having to worry about how to draw something to the screen, the sprite group knows that you're going to hand it the display, and that it needs to take every single image that needs to be drawn, and put it in its position on the screen. Unfortunately, that pattern makes the multiple grid system a lot harder, mostly because screen space and world space are equalized in that. There are ways around it, it takes a handful of tricks, mostly you're going to have an internal rect and an external one, and the external one is for rendering, the internal one is for positioning. So that's another one, and the other thing PyGame, PyGame is really opinionated about is the clock. So in order to enforce frame rate in PyGame, there is a clock class. You call tick on it through every single loop, and that does a lot of the management for you, so that you have things like anything that the window itself needs will happen there, you don't have to handle it. That function also returns milliseconds since the last tick, so this is the place where you get time in PyGame. You can also lock your frame rate by passing it a value. You pass it 35, it'll run at about 35 frames per second. Pass it 60, it'll try to hit 60. It can go lower, but it will never go higher. So that's PyGame, those are the things to look for, and again, check out the rect, it is a wonderful, wonderful class. I don't even like implementing my own anymore because it's just so nice. PySDL2, so PySDL2 is actually the library I've been using for rendering lately. The actual SDL bindings are really nice. They're not named quite pythonically, but mostly because the developers were just trying to do a pass through directly into the C. So you have to work with some C types and some other stuff, it's a little ugly, but it gives you a lot more control than something like PyAm. But I'm not really actually talking about the SDL2 library, I'm talking about the PySDL2 extensions. That's where they put all their game tools. With PySDL2, their expectation is your game objects are going to just be collections. You're going to build all your game objects using composition, and so you have attributes that have classes. The trick here, and this is actually part of the reason I don't use PySDL tools extensions, is that the classes cannot inherit from each other and they cannot inherit from a common parent. Because if they do, they overwrite each other in the systems. So that's a thing to keep out for, and it's actually the opinion that made me go, I'm not really into this one. It's still kind of nice, it's very interesting code, it's all written in pure python, so you can sit there and look through all of this. But so that's how you build components. So if they need a position, you have to put position equals a position, because, and that's how it updates. I just don't like it. I just hate their opinions.