 We got our next talk up. Piper Thunstrum is going to tell us about how to make games with Python. Let's give a round of applause for Piper. All right. So specifically, we're going to talk about making games with PPB, which most of you probably haven't heard of yet. So I'm Piper Thunstrum. I'm a software engineer at a company called GLG. I mostly do web development and database programming. I'm also a community organizer in the New York City Python area, Python community. And I'm also the author of PPB, which is, again, why you probably haven't heard of it yet. If you want to follow me or tweet about this, at PA Thunstrum and at PursuitPyBear. So what is PPB? Oh, this did not work on this screen. OK, so PPB, we are an open source idiomatic Python event driven education focused game library. Lots and lots of words to apply to one resource. We're going to kind of take them in order. I don't think I have to explain open source to this room, but I do to others, so that's why it's there. When I say idiomatic Python, what I mean is the code you write with PPB looks and feels like Python. By event driven, I mean that you write your code as responses to events and not as a game loop like you're probably used to if you've done game programming. And by education focused, I mean that primarily when I need to make a decision on what of three different engineering options do we need to do, I go and talk to a bunch of teachers. So that's what PPB is. This is the team. I'm Piper at the top. I'm PA Thunstrum on GitHub. I'm the API author and basically the manager of the project. Jamie is my co-maintainer. Jamie is the actual one who is actually the one who wrote the event system and has done a lot of great work and is currently trying to get an animation framework over the line. And Nico has done a lot of work on making our numbers stuff all work better. Clearly not the person to talk about it. So let's, wow, that is the wrong file to open. This is why you don't live code, folks. All right, so let's actually talk about making games. When I'm talking about this talk, I'm not going to really cover these basics in a lot of detail. This is just a very quick primer. If you want more, I've given a talk on making games the actual concepts. You can find them on YouTube under my name. So all games at their core have an event loop. We actually call this a couple of different things, depending on who's talking about it. Sometimes they're called render loops, sometimes they're game loops, sometimes they're simulation loops. They all kind of tell you what's going on under the hood. We want to respond to events. Update the simulation, and then render, usually in that order. But it doesn't actually matter that much. So in PPP, though, you don't have to write this loop anymore. We make it as easy as possible to get your first loop going. So there we go. We have a window open, two lines of code. If anyone's ever played with the other libraries, this is a little less than you have to do there. So this is just, again, the basic everything needs a loop. Here's how we handle it. But let's talk about events. Events are the things that a lot of people have a little trouble wrapping their minds around. So what are events anyway? Events are things like mouse motion, the window trying to close, the update event in our case. In general, we also can use this for message passing so that different objects can talk to each other. In PPP, we have built the API so that it's fairly simple to write new event handlers. So here, we're going to respond to key presses. So we have a bunch of constants defining all of the various keys, which is what you saw pop up there. And when I press the space bar, we're going to just increase the size of the object. So we do this. Every time I press space, it grows. So that's how simple it is to actually write your event handlers. We have these for mouse motion. We have them for there's an update event and a couple of other features. This is basically where all of your actual game logic is going to live, is inside your game objects, inside event handlers. So let's talk about rendering. I'm sure you've noticed that we already do a lot of rendering for you. But we're going to demo some other stuff instead. So when we're talking graphics, as far as PPP is concerned, we do graphics. It's a 2D context. We're using actual pixel arrays under the hood. But mostly, you don't actually have to think about this. We would rather you worry about the metadata and not the actual process of rendering. So as you've seen a couple of times already, we get a square for free. Doesn't take much effort. That's just the base right there. But you can also do things like add images. We'll do this. All right, so we run this. Let me uncomment. Here we go. All right, so now we have a ship on screen. That's all it took. Other features you can do just using metadata in the same way. You've kind of already seen this, but we'll do it again. And you can also manipulate the rotation. So now we have a bigger one and a reversed one. And then additionally, we've made all of these features possible so we can do all the things with basically no extra code. So rendering is nice and easy. You can manipulate all of those values as much as you want. So let's move on to the actual architecture of how PPP works, and then we'll get into the actual fun stuff. So we actually start at the top of our tree. This line of code is ugly, and there's a reason we hide it behind PPV run. So this is basically how you'd set up your own custom engine. Normally, inside the context manager, we would call gameengine.run. But I'm actually trying to show you some things, so we're not doing that here. Inside the engine, though, there is a set of subsystems. Subsystems are actually how we manage adding new features to the game engine. Primarily, they were designed this way so that it may be really easy for the developers to do this. But it also adds an API so that if you want to add manipulations of how the engine works, you can add them to this list. And then once we start the engine, because it kind of does some lazy work, once you start it, you also have a, whoa, that is the wrong file. All right, so if you look down at the bottom here, there is a list of scenes. Right now, there's only one, because we haven't done anything with them. We keep them in a list, but we're using it as a stack, so you can push, pop, and replace. And to assist with that, we actually have this gameengine.current scene, which gives us whatever's on top of the stack. Nice and easy. The neat thing about our scenes, though, is they're actually containers, so they're iterable. Can't remove that. So we do this, and as you can see, we now have a object that has two different objects inside of it. This is just a list, because it's nicer to print. So the engine uses events to manage them, and I'll cover that in detail, since that's one of the advanced features. And then sprites, which are the things inside, are basically just data bags, as I just demonstrated, that you can attach event handlers to. In this file, I've done that up here, just so that the sprite actually responds to the update event in a moment. All right, so the engine is actually also how we publish events. Under the hood, you can send up a signal. So if you saw that signal function in the parameters a bit ago, it's just a function on the engine. We can print the events just to show what's there. All right, so there is a DQ underneath. Deck, whatever, I can never remember how to pronounce it. And then if we call publish, it should, it did not go through. I have to figure that out later. In general, though, when you do GE.publish, every object is actually going to receive that event. And it works when you're actually running the engine. I don't know why it's not working here. All right, so that is, that's the boring stuff. This is the background information you need to be able to understand what's going on. So let's actually talk about making the actual games themselves. Let's close all this down. So we've talked about system events. I kind of covered the kinds of things that are in them. But as I mentioned, what about manipulating them ourselves? That signal function we have here. What we're gonna do is have this ship, which currently does this, nope, wrong file. So this is what you've got so far. What we're going to actually do is make it so that one of those two ships actually sends another update event every time. So signal. So here's that actual update event. And we're going to use the existing ones, time delta, before I actually blow this up because I have done it multiple times already. All right, so if we run this, you saw about how fast it was going. They should go about twice as fast. So that's one way you can do it. You can signal any of the existing events by doing this. This is less useful for actual making games, but it's good if you need to do things like faking player input. So you can make your own classes, just using data classes actually. So here we have a data class on time dilation. Basically the goal here is we're going to, instead of responding to update events, we're going to have the time dilation ship change how often it responds. So we put our code here. Self.accumulator plus equals self.speed. Wow, it's greater than or equal to one. So we can just raise our event. And this needed a time delta as well, so we want to keep it the same. And then down here you have on time dilation is where we actually do the movement. So if I run this now, it'll break. Wonderful, I think it's this that is causing the weird interaction. So this should, it's not doing the thing. This is why you don't like code. Oh, yes, I did this multiple times while practicing and reminding myself not to do it. There we go, now it should just work. So right now it's actually moving the same speed because down here where we've instantiated it, we've made speed one, but let's make speed, whoa, 13, it would go very fast. And there, we've made it faster. It's running on the same update speed as everything else, but it's not changing its speed, it's actually updating more times. So the other cool thing you can actually do is extending events, you could do, let's see, right, I have a leader. So I need to comment this out because I don't want two engines running at once. Mostly they won't run at once. So we write a little function. Up the file we have a global called leader that gets set during the setup. And we call ge.run and up here, we're going to just print. I missed a step here. So here we go, we have to ge.register. We are going to respond to the update event and then our callback is add leader. So now, despite it not being defined normally, you'll see that every time there's an update event we've been getting output of what's on that leader. So this is one way you can make objects available to other things is by registering one of these callbacks to add a new object to each event that you care about. So that's events. What about scenes? I mentioned changing scenes. This is really just how you manage the state machine that is the stack. So in this example, we have a splash screen. I am going to warn, this is going to flash a little bit for anyone who might have problems with that. So to start with, we just signal a start scene with our new game screen and this fun bug. This isn't actually going to do anything, but it will not try to instantiate the scene without it. We're working on that. So if we run this now, we should have, it'll go from one to another. So that was the start scene. In the middle of that, what you're going to get is the splash screen here will actually receive a on pause event just to demo this. So we run this and so when the color changed, the splash was paused. Every scene when it gets started actually receives an on scene started which you'll see the function of in just a moment. So when we're done with the game screen we're actually going to move to this game over screen and we have replaced scene for that. Same idea, we just pass it our new scene and the quarks. So there's that. So this is going to last for about a second and then it's going to switch to the game over screen. When it stops, it's actually going to print the runtime. So you can actually see some of the other features here. All right, so we do this, we get this, then this, then this, we'll close this. And as you can see, when the first scene paused we have that, when the game started, we saved that start time and then when it stopped we actually printed the actual runtime which is about a second. And then the last thing we need to do is be able to get back to that initial scene that's still on the stack. That one's even easier. We just need to tell it to stop the scene. This one doesn't have any examples but it should turn back to that initial purple color when we're done. Oh, it helps to actually spell my libraries correctly. So that's basically all you need to know to start manipulating scenes. When you start working with scenes just consider them a piece of the game from levels to splash screens to scoring screens to main menus, things like that. And so you just use that state machine to move between them. Next tool you need to know how to use, sprites. So you've seen sprites being used through all of these examples that are like the core thing that puts things on the screen. This is actually going to be about how to make those sprites interact with each other. So the first thing we can actually do is we can add their Python classes. We can pass other sprites as parameters. So in this sample, we're going to... Here's our leader, scene.add our leader. So here's this, just the same thing here. Seriously. All right. So we've done this. So this is going to put three objects on screen. And then if we look at the code on update, the leader is going to basically move nowhere because it doesn't have a target. Oh wait, no, right. It's going to move towards the center of the screen. And then the other ones are going to try to follow the leader, which is basically what we get. So that was just using parameters. You just pass things around like you would any other Python code. But you can also use scene.get. So what we're actually going to do now is we're going to add a player. And just so I can demo some stuff, that's going to crash. You want a list of hashables and not a string. So we're going to have this player. If I run this, as you can see, the player is going to follow the mouse. So that's done. And so here, instead of going to this, we need an update.scene. So just like I mentioned earlier where you can extend the event system, the engine does it's itself so that the current scene is always available on every single event you see. So in update.scene.get. So the first thing we can do is look by kind. And this is just going to find everything of that type. And target is going to be player.position. So what this should do is, as it moves around, the leader is going to start following the player. So another simple way to do this, in addition, you don't have to use kind. There's also by tag, which this should work exactly the same way. Oh, I should change that to the actual thing we're looking for. So you can use that tag system to also grab objects. So if you have multiple different types under the same tag, you'll get all of them. And then the last thing you can do is actually just use the messaging system. I don't actually have a sample ready for this, but you've kind of seen this all in order. So what we'll do is, we need data classes. All right, so we've all seen this. So what we're going to do in here, this will actually signal target moved, and it will be that player.position. And then down here, oh wait no, if self.leader, yep. This is a little backwards. If the leader here is not a Boolean, it's the actual leader itself. So I have to do all my logic in reverse. So if we're not the leader, we're actually going to care more about this target move. So this will be self.target equals event.position. And then up here, okay. So now instead of following the leader itself, it's going to go where the leader tells it to. Here we go. Okay, well, like I said, I didn't prep this one. I'm not sure what's wrong here. It's not callable. Where do I try to call a, okay. So we're going to move on. But that is basically how it would work. If I could figure out where I'm trying to do that. Oh, wow, that's right here. Hey, there we go. And now they're all mostly chasing the player. So yeah, there's the various ways you can actually put sprites together to do things together. Three different systems. You'll use them for different things. Generally, it depends on what information you have at the time on which one you're going to want to pick. So the last step, let's talk about subsystems. So I mentioned subsystems is how we actually add features to the engine. So I am actually going to build an achievement system right here in front of you. Remarkably, it's more simple than you'd think. So this is what it looks like. So we just have a very loose game here. All right. So in order to get this started, the first thing we're actually going to need is to start building, let me delete that. One minute. Okay, I might not be able to get this done. So the basic idea is you would make your, you do this, all subsystems have to take args and quarks because they kind of receive, they're going to receive all of the quarks for all of the subsystems. That's just how things instantiate. 10, I'm not going to get to show this one running, but basically you would write an event that is the progress. When you get that, self.count plus equals one, self.goal, if goal is greater than count or reverse that. All right, raise an event and then a sprite can be created. Would you see somewhere? No, I didn't write that part. Okay. Yeah, you would just raise an event and then can create a sprite in response to that event on the scene. And you'd have a working achievement system. So I think that's it.