Pygame Platformer Tutorial - Full Course

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
welcome to my Pie game platformer tutorial where I will be covering the basic Supply game and quickly ramping up into creating a full platformer game where you're a ninja who can run Dash jump and wall jump around while fighting enemies and beating levels if you don't know who I am I am the fluffy potato I've been working with pie game for over a decade I've released two Steam games with it won multiple game jams and just won the graphics category of the last luteum dare with pie game this tutorial is for anyone who has a decent understanding of python I expect you to understand the basics including lists dictionaries and a bit of object-oriented programming if you're not comfortable with object-oriented programming you can check out my old pie game tutorial Series where I did the whole thing without it whether you're brand new to pie game or you just know the basics and you want to learn more this tutorial is for you we'll start with the basics and work our way up to some Advanced topics like level editing visual effects systems Ai and more as you follow this tutorial there will be several breakpoints for which I've uploaded the project files to a resource page for there will be a link to this page on screen and in the description this means that if you make a mistake you'll have a working reference to figure out what went wrong with additionally all of the resources Code art sounds music etc for this tutorial are released under the public domain you can use them for whatever you want and even make something you'll sell with them before we get into pie game I'd like to give you some tips on getting the most out of this tutorial the overwhelming majority of people will learn significantly better through experience and experimentation than by just listening it's important to be coding along with me in some sense during the tutorial it doesn't have to be the exact same thing but making something will help you both understand the topics and remember them it would be a shame if you sat through seven hours of a tutorial and then forgot all of it the next day also it's important to not just copy what I'm doing if you're following along it's important to periodically pause the video and try changing the code I write to do various things to ensure you understand what I'm doing and how it might be used for other purposes tutorials are most valuable of a learning experience when you learn to apply them to a broader use case than the specific topic being covered now let's get a bit into pie game so what is pi game well in technical terms it's the python wrapper for sdl sdl is a media library widely used with C plus plus and C to make games and other applications in other words pygame is the adaptation of the sdl to python if you're curious for more details I have a video dedicated to the more technical overview of how pygame works and its implications titled packing's performance what you need to know the important thing to understand is that Pi game is actually considered a pretty low level way to make games there is no such thing as an animation a camera or even a player we have to cut all these things ourselves and I'll be showing you how in this tutorial python gives us user input and we have to create a window and generate every frame of the game primarily by just telling it where to put specific images on the screen this approach has upsides and downsides it's an incredible learning experience that teaches you things that are very useful in the software engineering field in my case I found that lots of my experience with pygame translated very well into being able to work on rockets and flight simulators as a software engineer additionally working at this level gives you a high degree of control over how things work and once you've developed the basics you can reuse things you've already made to enable you to develop games rather quickly my performance in game jams can attest to the fact that you can compete with people using game engines when you're using pygame the downside is that the initial learning curve is steep and the initial progress can be slower than using a game engine so if all that sounds good to you it's time to get started by installing pygame to install pygame you need to use Python's package manager pip to install just the package pie game funny enough there's a recent Forge in pie game because of some open source drama stuff I have another video about that if you want to look into it so there's actually two versions there's the original one which is just Pi game and then there's another one which is pi game CE I've been using CE lately and at the moment I think that's a better option I'll keep the pinned comment update dated with what I recommended for this tutorial series though I will be using CE at the moment there's not too much of a difference between them all of the stuff that I'm covering in this tutorial will work in both versions of Pi game so to install it like I said you need to use pip the way that you would normally do it is just type pip install an Empire game or Pi game CE although the problem is is that sometimes it's pip sometimes it's pip 3 sometimes you don't have that bound at all and you have to do python pip but then sometimes your python is not under Python and it could be under Python 3 and uh funny enough in my case it's just under Pi but actually if you're doing the python thing in the front you have to do a dash M that's the python module uh and you're done calling the PIP module to and telling it to install pycam so like I said it would be Pi python python 3. if you're on Linux normally it's going to be Python 3 I think I would assume it might be the same on Max but for Windows it's all over the place so just try them all something to check is to make sure that you're not using python 2. so if you just type python you can see your version and you don't want this to be a two it needs to be a three so in my case I'm using pi so I'm going to do PI Dash M hip install pygame CE all right so it's just installed with that I can just run the Shell by just typing pi and then I can do import Pi game and then you can see it imported if you get an error on that import then it's not installed correctly it's also possible to get an error when you're installing it typically you're just gonna have to Google it if you get an error while you install it all sorts of things could have happened and usually it's because you did something weird with your setup but for most people it should install fine and then if you get the error on the import it just means it didn't install correctly if Pi Python and Python 3 I don't actually have this one if none of these start the shell for you you're going to have to find your Python and install so that you can actually use that there's a million things that can go wrong in this part because there's a ton of different ways you could have python installed so I can't go too into depth here a lot of this stuff is kind of just general installing packages with python type stuff so if you run into issues you can just Google it anyways with pi game installed it's time to go ahead and create our very first window all right so I'm just in a folder that just has the assets in it for the project I'll get into those later and there'll be a link to those assets in the description so that you can follow along but for this first step in making the window you don't need those to begin with so let's go ahead here and just create a new file and then we'll call it game dot Pi so the very first thing you're going to have to do is just import Pi game and then after that the next step is to do a pygame DOT init this will start up Pi game after we initialize Pi game we can create the screen or the window or whatever you want to call it really so we can just name our variable screen we can call it window I like to use screen I don't know where I got this from I think I found it in some example code like 10 years ago and I just kept using it ever since but we'll set that to pygame dot display and then we'll dot set mode so this is a bit of a misleading function this is actually the function that creates the window kind of for some reason is called set mode there there are technical reasons for that but it's a little bit confusing the next argument is going through the resolution and we pass that as a tuple so I'll say 640 by 480. that's the resolution of our window in pixels so just this function call here will create our window and then this is kind of the handle for a window so that we can draw onto it the next thing we'll need is a clock uh the use of this is for force on the game to run at 60fps because normally if you run code it'll just run as fast as possible but in games you might want to restrict that because you're running a lot of stuff in a loop every frame is its own iteration in the game Loop so if you rent it as fast as possible you would like max out your CPU and just your fans would Gober and stuff like that so that's not ideal so you a lot of times you don't want to run it at the maximum frame rate another thing worth noting is that uh normally you would use something called Delta time which is the difference between the time in the frames in order to just kind of change how fast things are moving and stuff like that to adjust for frame rate we're not going to get into that in this tutorial I'm going to work it with a fixed frame rate because it's a lot easier mathematically so that's another reason to try to set up something so that we can restrict the frame rate to 60 FPS so for the clock uh as you can see tab 9 suggesting here which is pygame dot time the clock so that will instantiate a clock object and then we can use that for a couple things the next step is to create the game Loop so the idea is that as I mentioned in a game each frame is an iteration in a loop and in that Loop some games will have multiple Loops running at the same time like maybe one for rendering and one for physics and stuff like that but in our case we're going to do it all in one Loop and every Loop you'll have to update everything and then you'll have to put it onto the screen and then you'll finally update the screen at the end so that the user can see it and then you go to the next frame so the idea is that game is running a continuous loop and you're just constantly working with changing data and the main thing that affects that data is the input that the player gives which you also pick up in that Loop so to do this we'll just do a while true so that's an infinite Loop and then in here we can do a pygame DOT display.update this will update the display so we can draw onto the screen but until we actually call this function to update the display we won't see anything changing by default the screen is just going to be black so if I don't call this function you're just going to get a block screen and after this we'll want to do clock Dot tick 60 and that's for 60 FPS so this will force the the this Loop to run at 60 FPS so this function in itself is effectively just a dynamic sleep it'll sleep for however long it needs to to maintain that 60 FPS there's other stuff the clock can do but we won't need any of that for this tutorial all right so the next step is to get the input if you were to run the code right now a window would pop up and it would work but we don't want that because in sdl you have full control over the input handling if you don't ask for the input windows will think you're not taking input and if Windows thinks you're not taking input as an application it doesn't matter if you're updating the screen it will think that the application has stopped responding and it'll do that thing where it Grays out and gives you the spinny thing on your cursor and then we'll ask the user if you wanted to shut it down so to prevent that we have to get all of our events and then process them in some sort of way so we can do a for event in pygame.event.get so pygame.event.get is what gets the input and this is where it kind of interacts with Windows and gets that user input so that the thing doesn't freeze and then in here we can do something with our input it can be whatever we want an event is just like it could be a button press it could be someone clicking the mouse it could be someone even moving the mouse I think there's some touch type events and other weird things that can happen there's events for like resized on the window there's all sorts of stuff so the way this is structured is that the event will have a type attribute um that specifies what type of event it is so we can do if event DOT type and it's suggesting it here is pygame.quip so the pygame.quit type just means that you click the X on the window so believe it or not we actually have to code the part where you click X on the window and it closes so to do that we do a pygame DOT quit and we can do a sys.exit sys.exit will just kind of out exit the application for that we need to import this up here highgame.quit will just close Pi game so you need both of them anyways we should have our window now here we go this is our window we can drag it around and I can click X and it will close and that's all that is so far uh one more thing that we might want to do if you notice the window name is pi game window we might not want that so let's go ahead and change the window caption we can do pygame.display.set caption and we'll say ninja game and that's the name of our game I guess and if I run it again it now says ninja game you can also change this icon here in the top left but I won't really get into that in this tutorial because it's not super important anyways it's time to give you a bit of a tour of some of the assets that I have prepared for this tutorial I will link them in the description it'll just be a zip file you can download just make sure you have it set up the way I do where it's just a folder and then you drop the data folder inside that other folder and then the game script is right next to the assets folder so inside of data we have a few things we have this music file we have sound effects for a few different types of sounds we have Maps I'll show you how you can make your own and then also do the level editor and everything but I do have these pre-made because they can take a little bit of time to make if you look in these at the moment it's uh Json if you're not familiar with that format and it's just a mess of data you can try to read it if you want it's not a good idea to try to edit this by hand that would not be a fun time that's what level editors are for anyways if we go to into images this is where most of the stuff is we have things like a background a gun a projectile and then we have other stuff we have a tiles folder with a bunch of random stuff in it like I think this is a tree and that's a crate okay that's uh flowers the the tree is I think this one yeah here's the tree we also have our tiles right here so this is like the different grass tiles I'll show you how to auto tile these as well so that you can just play some tiles and they'll form with their edges automatically and everything and here we also have like different types of particles just a bunch of images and then we have our different entities we have a player with a bunch of different animations and like the enemy and this bunch of anime frames for that animation and like I said we'll get into animations later and then finally there's the clouds but we'll get into most of that stuff later for the time being the main focus is going to be to show you the basics of using pi games so we can get started with the project in order to make a game that's easy to work with in terms of scope a lot of times it's good to use object-oriented programming so that you can like pass objects around and just get a lot of the other convenience of this of object-oriented programming I probably won't lean all the way into objector into programming for this tutorial but I will use a lot of that functionality so the very first thing you're going to want to do which is a common thing is to make your game its own object so let's do class game deaf init self and then in here we might want to do do a few different things so we can take all of the stuff and indent it so that it goes under in it and then we can do self.screen so that's an attribute of our object and then the other part of this is going to be the Run function so this is how the game runs so you can initialize it and run it separately if you want so we indent that in in our Loops now in its own function and the only thing is we have to do aself.blog.tick and then finally we have to actually initialize our game and run it so down here we can do game and then dot run so since this will return the object the dot run will just be called on the initialize object so if we run it it should be the same look at the window and everything and this is an easier form to work with games in alright so let's get started with actually putting an image on the screen to do that we use Pi gamespygame.mh.load function so we can do let's say self Dot image equals Pi game Diamond Style load and then you just give it the path as a string for the argument and then it'll be able to load it it can do PNG files jpegs and a couple other types but usually you want to use PNG because it's lossless so let's do data images clouds cloud1.png so we're loading one of the cloud images so with this image loaded nothing's changed yet we actually have to put it on the screen to do that we use something called the blit function do a screen dot bullet and we can do self.image and we can put it wherever let's say 100 100 actually this has to be self but actually let's do 100 200 so you can see the differences in the dimensions all right so if we run this you now see a cloud here if you notice I put it at 100x and 200y so the way the coordinate system works is that the top left is zero zero this is actually quite different from how you normally have it in math in math it would be either the center zero zero or it would be like the bottom left of zero zero if you don't want to have negative numbers and actually if you get into computer Graphics where you're writing shaders it wouldn't be that way but for pi game and other non-gpu based rendering techniques the top left is often zero zero and that's I believe has something to do with how when we read text we start it in the top left and just kind of in the order of elements so say you were to make a website and just place a bunch of HTML elements it would go from the top left so yeah we have 100 pixels from the left side of the screen and 200 down from the top so yeah as you can see we have our nice little Cloud there now that we have our Cloud it'll be nice if we can move our Cloud to do that I'm going to add a couple variables here so let's say self.image position equals 160 260 and then we can use that position as the input over here for our bullet function and something interesting about bullet is that the idea is that it's just kind of a memory copy you're copying some section of memory onto another surface and blit is just a terminology used for that and you'll notice that I said surface there so in pi game a surface is basically just an image so the window itself has a surface which is the main one you render onto that's the screen that's a special type of surface but most surfaces are kind of like this image one where it's just an image in memory it's not doesn't necessarily represent the screen or something like that so if you wanted you could do it the other way around and you could actually put the screen onto the image because they're both surfaces but we don't have a reason to do that you can put any surface onto another surface and then at a given location so you're just merging together different images if you were to actually BLT the screen onto our Cloud image it would erase the cloud and replace it with all black because well the screen is black by default one interest same way I've heard to think of this is that you're just kind of making a collage of different images that you're putting onto the screen and you can put images onto your images and do whatever you want all right so with this image position attribute implemented let's go ahead and get the movement artworking so let's add a movement variable software movement equals false false and let's say we want to move our Cloud up and down so we go back to our event Loop here remember we get all the events with event.get and our event has a type so the type for a button press is if event DOT type is highgame.keydown that means that you pressed some kind of key down to find out what type of key was pressed for that event we can do if event key is pi game Dot and we'll use K up so that is the up key and we'll set self dot movement zero equals true if you press the up key if event dot key is pi game.k down self. movement one equals true so something interesting to note is that these events are fired when you press or release the keys they don't necessarily mean that you're like holding it down in that moment so you're not going to be getting this event every single frame while you're holding it down so we have to have something to keep track of whether or not it's being held on our end there are functions the pi game has so checking if something's being held but I like to use my own systems for this because it's easier to hook on some extra stuff if you want to change something in the future alright so aside from the key down event there's also the key up event which is the same thing except the key was lifted up instead of being pressed down so we want to switch our movement variables to false so the way we're doing this is we have these two balloons here that we're updating based on whether or not the key is held down so if the first one is true that means we're holding down the up key and if the second one is true that means we're holding down the down key so with that we can use that to modify our position here so we can do self dot image position one plus equals self dot movement zero actually we should do self dot movement one minus self-dot movement zero I'm using an interesting trick here so balloons Tech technically can be converted to integers implicitly and that's what I'm doing here so a true will get converted to a one and a false will get converted to a zero so if we hold down both we get this adds up to zero if we hold down just down it'll add up to one because of this one if we hold down just up it'll add up to negative one if we do neither it adds up to zero so we're able to just kind of calculate how much the position should be changed based on these two balloons if we wanted we can change the speed by doing this and like adding up five which actually I'll leave that for now if I run this and I press up and down we can see the cloud is moving but we have a weird effect it seems to be leaving a trail of some kind and the issue here is that if you recall correctly the screen is just as I mentioned a surface so we're putting the cloud onto the screen but we're doing that every frame at a different location if if the cloud moves so we're rendering the cloud on top of the previous render which already had the cloud rendered and you do that a bunch of times and you end up with a trail what you actually have to do is you have to clear the screen after each frame usually you'll be generating a whole image from blank screen essentially and you do have to clear yourself so to clear it I'm going to do a screen dot fill and we want a nice Sky color so we'll do two what we'll have 14 219 248 so dot fill will take a color in RGB so this is the red value this is the green value and this is the blue value so it's a very blue and green color it'll fill the screen with that color so anything that was there previously from the last frame will be just completely replaced to buy a solid color that we just gave it so if we run this we now see that we have a cloud if we move it up and down we don't get a trail anymore but now you see there's this black box around it if you look into our assets and you go to the clouds here and you open up this image you can see that it actually does have that black background to it there's no transparency automatically in it so what you normally do is you add a color key there are ways to just use the transparency built into the image but it's easier usually to have like a background color that you just replace so in our case we're going to want to add our color key here we'll do self.image.set color T and then we'll say zero zero zero so zero zero zero is the color for pure black and if you notice before in the image I showed you the background was pure black so we set the card to zero zero zero and that color will be replaced with transparency whenever that image is rendered or rather that surface is rendered so the color key allows you to just say that one specific color should be transparent and now if I run this you can see there's no black background to our Cloud image and I can move it up and down just as you'd expect all right before I get into the next part which is going to be Collision so something else I want to share to you about input so for this tutorial I will be used using a slightly unpopular but what I find to be kind of accessible keyboard layout where we use the arrow keys for movement and then we use x and then if we needed another one C but we went for this tutorial for extra like abilities and stuff so the idea is that many keyboard layouts will be quite different so specifically if you look at wasd if you go to a different keyboard layout they're not going to be next to each other anymore so using the arrow keys for movement is actually quite Universal for example it works in quantity which is what I'm using it works in quartz and it works in azurdi layouts so lots of I think mainly Europeans are able to play games that use layouts where you have the arrow keys and the other thing is that for the abilities like I mentioned you use x and C A Lot the reason for that is because those two buttons are actually next to each other in all three of those formats I just listed I think the major format where they're not going to be in next to each other is Dvorak but I don't think there's much you can do about it at that point there's a reason why people a lot of people use Excellency I definitely get a lot of complaints for not having wasd in my games if you want you can add it yourself for example these Keys become like just KW if you want to use that in the pi game documentation there's a whole list of all the keys so that you can just bind it to whatever you want and one thing you can do is you can actually just support both so you can have it working with wsd or the arrow keys simultaneously and that that's a common solution as well so with that out of the way let's get into collisions so when I say collisions I'm talking about Collision detection I'm not talking about there's a wall you run into it and it stops you I'm talking about there's an area you run into it and you know you've collided with that area so that type of collision detection is the very first step of getting physics implemented and it's quite simple to do in pi game and this is one of the few I guess physics related things that pyram actually supports which is just basic uh rectangle collisions so here's what we're going to do we're going to have just kind of a basic area that we can collide with we'll just call it self dot Collision area equals pygame.rect and then we can do let's say 50 50 and let's make it like 300 wide and 50 tall and then we'll also want a rectangle for our Cloud image so this is image R equals pygame direct and then we can do image position zero image position one and we do image.getwith image.get all right and this is actually going to be a self on all of these because this is an attribute all right so there's actually a shortcut here which I won't be using in the rest of the tutorial but I use it when I'm writing killer myself which is usually spot operator you can actually do pygame.rect and just Splat self.image position and then self dot images I get size and that will just spread out the arguments but I won't be using that here because it's uh something you have to learn that a lot of people don't actually know about python if you do know about it feel free to use it it's very convenient but if not that's also fine all right so the pygame.rect function creates a rectangle which kind of just makes sense based on the name and that rectangle has a top left position which is defined by the first two parameters and then the second two are the width and the height of the rectangle and remember the coordinate system is from the top left and it's just right is positive X down is positive y so there's a couple different things you can do with these rectangles the basic functions are things like drawing that rectangle ones on the screen you can draw it as like a box or you can draw it as in a filled in rectangle you can also do the equation detection with them we'll be doing both here so right now we have this one rectangle which we don't want to move that we're just using to test our equations and we have our image rectangle which is created every single frame based on the position of our image so the position is coming from that position thing right here and then the size of the rectangle is being pulled from the size of the image which you can get with get width and get height so that will create a rectangle that perfectly matches the image that we're rendering here all right so now we can do our Collision test we can do if image r dot Collide erect self dot Collision area that means that in this Frame our two Recs are overlapping in some way and what I'll do for that is I'll draw the rectangle draw that rect we'll draw it onto the screen and let's give it a blue color a bright ish blue color but not the same one as our background all right and then we have to pass in the rect that we want to draw so we want to draw our equation area rect with the color that's like mostly blue and onto the screen those are the three arguments of hiking to draw direct and I'm going to copy this I'm going to say else and then this is if we're not colliding we use a different color which is going to be darker all right so if we run the game you see we have that rectangle up top we can move our Cloud up and down and if our Cloud touches it it turns to a brighter blue so you can tell if our cloud is touching that box with that code we just wrote and this is as I mentioned what we will use to do are Tau physics and all sorts of other collisions later all right so something interesting you may notice here is that the cloud is showing up behind our rectangle this is probably not ideal for what we're doing here you would expect to see probably that the cloud is on top of the area we're colliding with uh just from a visual standpoint so the way that layering Works in pygame is that it's purely based on the render order remember you're just kind of copying an image onto another image so if you happen to copy one image onto another image before you do a different image onto that same location that second one will cover the first one so layering is done entirely based on the order that you render stuff onto the surface if we want the cloud to appear on top of our Collision area we just have to take this code and paste it under the rendering for the Collision area that way this is rendered on top all right if we run it again we can move our Cloud up and we can see it's actually on top so this can create some problems if you have a specific order you want to like update things and render them in uh so you might have to do a little bit of problem solving to figure out how you want to implement your rendering orders into your update sequence and stuff like that it's very common to separate your updates and renders for different objects like if you have a class for your player or something you might want a function for updating it and a function for rendering it so you could do it separately and then your update code doesn't have to be reordered if you want to reorder your rendering now that we have the basic Stone we can get into the crazy stuff that is actually making a game the first thing we'll probably want is a player so I'm going to do this in the object oriented way from the beginning which hopefully keeps us from having to rework things later so the way we're going to do this is I'm going to create a new folder and call it scripts and this is where everything that isn't the main game script will go so in here I will create an entities script and this will be responsible for a couple of things so let's just start with a basic entity class I'm going to call it a physics entity because later on here it will be capable of handling the physics so let's define the initialization function let's take the game as a parameter and that way anything that's in the game is accessible through the entity this is a bit of a trick you can use to deal with scope issues for games the weird thing about games compared to other types of programming problems I guess is that in games it's very common for elements to interact with a ton of other elements some people handle these interactions through intermediary systems but that adds more code and in some cases more complexity it can help to keep things organized if you do it that way but if you're working by yourself interacting with other things directly it's usually a decent way to go use hatch kind of keep track of things and uh if you lose track of things it might get a little bit tricky to debug stuff but overall this is a quick and easy to understand solution for a scope issue so aside from the reference to the overall game so they can access everything in the game we also want to take the entity type we won't really be using this yet but we will use it later we also want to take a position to spawn the entity in and then we also want a size for the NC so let's do self.game equals game and self.e type equals eat actually just to Type e type there we go the reason why I don't want to use just type here is because that's a built-in function but using it as an attribute is fine that doesn't override it all right self.position equals position and actually something interesting to do here is to actually just call the list function on that it'll convert any iterable into a list and the reasoning for that is two things first of all if you did pass it a list you don't want to have a reference to that list let's say you spawned like three entities uh in the same location and you passed a list in so all of those entities could be and it's a bit of a more complex pie that's on topic all those entities entities could be referencing that same list that represents that position and if one updates it all of those NCS will have their position changed so you want to make sure that each entity has its own list for its position the other thing is that if you pass on a tuple you can't change individual values in the Tuple you have to just update the whole thing so it's easier to work with lists for a position because you're usually changing the position another thing you could be doing is converting those locations into Pi games Vector twos uh I won't get into that but there is a lot of useful functionality there if you want to use that so let's do self.size equal size and then we'll make an update function that just gets called every frame death update self and let's take in a movement variable we'll update these arguments later and add in a tile map for collisions but for now we just want to do movement something else we're going to one is a velocity so I can do it yourself to have velocity uh we don't want that uh equals zero zero so if you didn't know the derivative of position is velocity and the derivative of velocities acceleration so the relationships are similar velocity is just used to represent the rate of change in the position so if your velocity is five in the X you'll move like five in position in X every frame and then acceleration which we will also be using will be the rate of change in velocity you'll be updating the velocity of reframe a certain amount based on the acceleration and fortunately for us our acceleration is going to be constant it will just be the force of gravity a lot of people get confused as to how they're supposed to implement like good jumping mechanics but if you actually just use the physics formulas for how these things are supposed to work not all of them but the basic ones for like position velocity and acceleration you can get some pretty good results in higher level physics systems a lot of times they will use physical Concepts like force and momentum and inertia and all sorts of stuff like that but we don't have to get into here once you get to that level of physics a lot of things actually start to feel a little bit clunky if you're not very careful about how you deal with momentum and stuff so I actually like to work at this kind of simplified physics level so you get the benefits of nice smooth movement but it doesn't feel clunky anyways moving on from here we want to generate how much of a movement we want to make for the frame so frame movement equals movement zero plus self dot velocity zero movement one plus velocity one so what we're doing here is we're creating just a vector that represents how much the entry should be moved in this Frame based on how much we want to force it to move in this particular frame plus however much there already is in velocity so you want kind of multiple controls on how things move which we'll get into later but for example the gravity will be applying an acceleration to the velocity and that velocity will pass through as well to the position but for example for moving right and left you want that to be instantaneous usually or at least that's what I like it to be so for that we use two different inputs for movement so in order to actually move it we can just do self-doubt position zero plus equals frame movement zero typically when I have a list of two values it represent something dimensionally is going to be the first one's X and the second one is y so position and velocity doing this is just updating our X position based on the frame movement and then we can do the same thing for y it's important that these are separate uh we'll get into this with the physics later but you actually do the movement steps in two stages you do one for each Dimension you can actually do this in three dimensions if you want but also get to that later anyways we have our movement working so now we can do our rendering self.render and then we want to take our destination surface and let's just say surf.blit and we need an image I'm just going to write something in and we'll fill it out later so self Dot game.assets and let's just say player self-doubosition all right so we have our physics entity here let's go over to game and import it we also do have to fill out this assets thing so we can go over here we can do import script entities actually it's from scripture entities import just that object that's all we want from there and we can just Define our player self.player equals physics entity and then just pass in the game and call it player and let's spawn it at 50 50. and give it a size of 8 by 15. all right so we have all this other stuff going on here that we don't need anymore so there's this image loading stuff for the cloud and all that glitch and stuff we don't need that anymore that was just demonstrational we'll use a lot of those ideas here in a moment but we don't need that for actual game here so in here we want to do a couple things so we want to do self.player dot update and actually hold on there's the movement stuff back here which we should probably leave well we want this movement variable because that's still taking our input but for the rest of the stuff we can do self.player.update and then we can do the whole movement self. movement one minus self. movement zero and then for the second part we can just throw in zero we don't want any changes on the y-axis because if you're in a platformer you run right and left not up and down so the other part of this is to change these up and down keys to right and left so we can do right left actually I think it's the other way around right uh yeah yeah that's left right right so just copy these down and that should do that new self.player.render onto self.screen so the player's rendering the player is updating uh and moving the only thing is we need to get the Assets in so for this I'm going to start creating some utility code so let's call this utils so in here we're going to want to create just a basic function we'll create more functions built on this later for just loading images So Def load image and give it a path and we can say our base image path is data slash images all of our images should be under that folder so we can do image equals Pi game damage.load and should be the base image path plus this extra thing that we're giving it and this is where we will want to do something new we want to use dot convert on the image this converts the internal representation of this image in pi game it makes it more efficient for rendering a lot of new users forget to do this or don't know about it and it costs them a lot in performance so it's important to do this uh this almost no reason not to it so I just do it by default for just about everything all right so let's do image.set color key and then if you remember all of our backgrounds are black so black will become transparent and then we just want to return our image so we can take this function and use it to start loading stuff so let's go back over here and we can do from scripts.utils import and we want our load image function so in here we can finally Define that one image that we needed for the player which is self.assets equals and then we can do player and then we can do a load image on entities slash pointer.png so if you remember there is that base images path so that'll go data images and then we have entities and we go to player.png which is this one right here it's a bit small but yeah so if you're a bit lost on how I'm accessing everything from everywhere I I recommend taking a moment to read through everything and make sure you understand how everything's connected one of the big things that's important to do when you're learning stuff like that is is to make sure you fully understand things and the one of the best ways to do that is to actually play around with things and see how things work when you start changing stuff so before moving on assuming this actually works I'm double checking that uh I have a new mistake here this is pygame.image.load and that's why I put that Clause that it needs to work anyways so this works the player can move right and left by just pressing right and left and we have our character on the screen it's represented as an object which is nice so this is all great yeah as I was saying it's really good to mess around with things and get a full understanding of how this stuff works by changing stuff and seeing how it interacts if you just copy everything I'm doing and don't play around with it too much you're going to get a bit lost I do recommend if you do end up breaking something and you're not sure where you went wrong you can go back to the last available like reference code that I have on the assets page for this tutorial so you can always have some baseline that's working while following this anyways so now that we have movement and our player the next thing is to deal with the fact that we have some pixel art here and it's really small we can barely see it so this is where we use a interesting technique there are multiple ways to do this pie game has some built-in stuff for scaling but you get a little bit more control if you do it yourself and by do-it-yourself I mean what I'm about to get into here it's a bit slower performance wise but it's pretty much a non-issue at this resolution so we can do self.d display equals pygame dot surface and this is another conventional name that I just started using at some point I think I got this I don't know if I came up with display or what I don't know where that came from but for me screen's always been kind of the the windows surface and then displays the one that I actually render onto all right so we have these two surfaces if you notice display is half the resolution of the screen and also I use a new function here which is the pygame.surface function highgame.surface generates an empty surface so like an empty image it's like you loaded an image of this Dimension using pygame.imential load except it's all black by default so just imagine a black image 320 by 240 and that's what Pokemon surface does this is useful for just creating stuff in memory that you might need for some kind of purpose in my case the display is going to be a second surface that we use for rendering so the idea is to render onto the smaller display and then scale it up to the screen so that you render on a small resolution and you scale it up which creates the pixel art effect so let's render everything onto this display to do that we just Swap all this display and then the last step right here because like actually I can show you what happens you just get a black screen because now that we're running around the display instead of the screen you get nothing so we have to render onto the screen so screen Dot blit and we want to split the display onto the screen so if you do this we now get this so it's just shrunk so the next step is to scale in so this is also something new we'll do pygame.transform.scale and the first argument is the surface you want to scale and the second one is the new size so what we'll do is self.screen dot get size so we're scaling this surface to the size of this one you could pass in like a couple of any size you want but it's convenient to just use the get size on the screen here so the return value of this will be the scale display and we can just throw it on the screen and that's what we're looking for all right so now you can see the pixel art has gotten a bit bigger and it actually looks like pixel art all right now this is where things start to get crazy we're gonna get into tiles and physics well we've already done a little bit of physics in the sense that we've implemented some code for velocity we're not using it yet but it is there so we'll want to create a new tile map script telemap.pi is what we'll call it and in here we'll let's create a new class we'll call it tile map def init and then we'll want to take like a tile size we'll say 16 and that 16 is a default value we'll say our tile size is equal to the tile size sofa tile map equals that and self.off grid tiles equals this all right so what we're doing here is we want two systems of tiles so we have the tile map which is where every single tile is on a grid so it's just a square grid of a bunch of tiles and then the off-grid tiles are things that we placed all over the place that might not line up with the grid so the idea is that you can only handle physics with this grid here it's more convenient to work with you could add a physics here if you wanted but if you keep stuff on a grid it's easier to optimize it so we'll have most of our tiles on this Grid in here and so there's a reason why this one's a dictionary and this one's a list though so we're going to do something where we map each tile based on its location if you've done any of this stuff before you may have seen like an example of a map where you do say there's a little Hill or something so if you have a little Hill you're mapping maybe you would do something like this where you have three rows in a list and your ones represent the ground so you get a little Hill of the ones right here and and there's a slight issue with this approach let's say you wanted two islands that are far apart you have to fill in the space between those islands with air so if if a lot of your world is air you're going to have a lot of wasted space in your setup here and you also have to go through all of the work of creating that space between the tiles as well so the approach that I like to use instead instead of just throwing things into a list because you have to you can't have two elements at different locations without having all the elements between the technique I like to use is to create something where you have let's say a tile at the location zero zero and we say it's like grass or something like that and maybe a tile at zero one called dirt and the advantage here is that you could have a tile at 9999 and that could also be grass and you don't have to fill in all that space between so this is really convenient to work with it's still also very efficient to look up tiles based on location we'll be using a slightly different strategy here you can use tuples like this as your location for your tiles but I will be writing these as string so I'll use like zero zero or zero one and stuff like that and that's because of the way the files will get saved I'll get to that in I don't know maybe an hour but it's a lot easier for this use case to do it as strings because the format we're going to save these in doesn't support tuples in my own framework I added a system where I could convert tuples into something else so it can work with tuples because they're they are easier to work with in terms of the actual code but I don't want to mess with the export stuff all right so let's throw in some basic tiles for us to work with we can do 4i in range 10 and we'll create a couple different tiles so self.tile map ring of three plus I I plus I will do 10 equals type and we're going to get it a little bit more complex with our trials here type grass variant one position three plus I 10. so there's a couple things to know here first of all our tile will now be represented as this dictionary so that way our tile can have multiple attributes you could implement this as an object if you wanted and you can do like a tile like that uh but I'm not going to go through all that effort so we're just going to use the dictionary this dictionary will have the type the variant So within graphs there's a few different types of grass tiles depending on like what directions are air and stuff like that and then I'm also storing the position in here and that's because this position as a string is not easily usable because you would have to split it by the semicolon convert it to an integer and stuff like that so leaving one in here this actually adds a couple of integers is easier to work with and actually faster so that's useful to leave there we'll be using that later and then this is just concatenating the dynamic location based on this iteration with a fixed position of 10. so this is going to be the positions of 3 through 12 on the X and then 10 on the Y so this will be a horizontal line of grass tiles all right so let's copy this and we'll do it again but we went a horizontal line now so for that we can delete this there's semicolon on the other side and do cluster I plus 5 and then instead of grass we'll take Stone and then this position needs to be updated the position here should match the one here so we do 10 5 plus I or rather I plus five and then we can actually this one's inconsistent so we do five plus I over here all right so we now have our tiles but we need a way to render them in order to render our tiles we'll need something to render so we'll have to go forward to our utils and create a function for loading multiple Tiles at once because if you look over here in our data and we go to images tiles let's say graphs there's a bunch of images here we don't want to type all of those out we want that to load automatically in reality you could create a function to just load every single image in that directory automatically which is what I normally do in my games but we'll just be handling a single folder for now and then we'll just load all those individual filters all right so we can create a def load images function give it a path do images equals that for image name and and then we'll have to import a new library here we'll do import OS so this is all we're going to do here is do OS at Lister we'll take the base image path plus path and then I'll pass here for now so OS that Lister will take a path and give you all of the files that are in that path so specifically here if we ask for say images tiles and we go graphs it'll give us the zero through eight inch so this is a way to easily dynamically load images so let's go ahead and say images Depend and then we can actually use our other function here load image and we give it the path plus slash and then Plus image name so remember this is the path to the folder but not including the base image path that's why we don't have to add it again here because this also includes it so yeah this is passive folder we add a slash because we want to get some inside that folder and then we do the image name which is given by this function here all right so since this will return the loaded image this images list will become a list of all of our loaded images so we can just return that and now we can use this to start loading some stuff so let's go back over here into the game and in our assets we'll once load a couple things so we have several tile sets in here or not several but a few let's do Decor that's one of them we'll do load images tile slash Decor you could do this in a for Loop if you wanted um actually we need a uh to import that up here also from utils but yeah we can do this as a for Loop if you want it but since there's only four of them I'm just going to copy paste so we have grass we have large decor and we have stone all right so let's take the grass right over here largely throw it over here Stone throw it over here all right so if I do that and then if I just do print self.assets we can actually see all of our acids if I run the game all right so if you look in here we have our assets so you can see we have decor and it's got a list of a bunch of assets and these are just how pygame represents these surfaces if you print them out so it lists all the surfaces we have and all of our different tablet sets like glass right there all right so now that we have those acids we can start rendering our tile Mount to do that we do diff render self surf so that's our destination and actually something else we want to take up here is probably the game so we can do self.game that way we can pull our assets so if you remember correctly I mentioned that we have these tiles and these tiles these ones are on the grid these are the only ones I filled out for now uh I'll get into these when I do the level editor later but I'm going to write the renderer for both right here so let's start with the stuff on grid which is what we have working right now we have four location in self.tile map so when you iterate over a dictionary you'll get all of the keys so that'll be these values so we'll get like three and three ten for example uh but to get the actual tile we'll have to look it up in the dictionary based on the key so that'll be self.tile map location and that'll give us this part all right so from here we can render it so we can do surf.blit and we'll do self.game.assets and then this is where things get a bit tricky we do tile type so if you remember Crosley over here we gave these things names so in the stick dictionary we have I'll Bluetooth print here in this dictionary we have grass and then it's a list so if we go over here remember we have grass so we want to say we're looking for those images of the grass category and variant will be the index of the image we want to use from there so from here we can do another indexing on this assets thing so this gets a little bit confusing you can like print it out at this state if you want um see what you're looking at but from here we can do tile variant to index based on this and get these very specific image that we're looking for from here we can go ahead and handle the position for the rendering so that's just going to be the position of the tile which we have up here already and then something you have to do here is you have to multiply by the tile size because these coordinates are in terms of the grid so the grid has tiles of a specific size and to get this in terms of pixels because that's how rendering works we'll have to convert it using the title size so self.tile size and then we do tile position one by itself the tile size all right so that is a very long line of code but it should cover all that we need for that rendering something else that I just remembered is that this might not work on Linux this function right here behaves differently on different operating systems I believe on Linux the order of the files you get is different from Windows on Windows it's alphabetical and it might be alphabetical and Linux and just in a different way I forgot but if you go over here where depending on the assumption that it'll load zero as the first one and eight is the last one so that might not be the case so what you have to do is if you just run this three sorted it'll sort it for you and then that makes it consistent between all operating systems uh something else you have to be careful about is that since we're depending on the file names here and it's alphabetical 10 actually comes before eight because it'll look at one zero and it'll throw it either before or after the image number one because that's how it sorted alphabetically so the way you deal with that is you pad the front with zero so if you look at the entities here and say go to the idle animation you can see I padded the front with zeros that way 10 is no longer but four eight so that that's how you can keep that consistent this is abusing a little bit of a trick to keep our images in order here anyways so back to the tile Mount the last step for rendering is to handle the off guard titles these ones are going to be the same tile data but they're not going to be organized the same way so for tile in self.off Grid tiles so this will be for example we could actually just dump this one in here and say that's an example of what we could have for the alpha tiles so it's just we have that same type of tile object in there it's just a list instead the one thing to note that's going to be different is that this position will be interpreted as pixels instead of a grid position and that's because well the whole point of this is to be able to put things off of the grid whereas the tile map is supposed to be on the grid so the coordinate system is going to be a bit different so we don't have to do that multiplication with the tile size so we just do surf.blit and then we can grab the same thing right here and then it'll just be the tile position since that's already in pixels for this set of tiles all right so we have our tile map so let's go ahead and load it in for that we can just do from scripts dot tile map import tile map and then we can go over here and say our tile map is equal to that and then just for the sake of it we'll say that child size is 16 even though that's already the default just because of for clarity you could change this if you want so next step is to render it well in our case we want the tile map to be rendered behind the player so we'll do self.tile map dot render and then we want self.display is where it'll go and something else I just thought about speaking of layering is that we probably want our off-grid tiles to be behind the grid tiles because in our use case the things that we're going to be placing off of the grid are mostly decorations and the things that are going to be on the grid are mostly going to be tiles you can like run into and physically collide with so because of that the decorations are normally something that go in the back so you usually want to render these first if you're using this technique so we can go over here and throw them right there so that they get rendered first and that means that these uh tiles you collide with are in front you don't want to be walking and you see like some sort of decoration and blocks the view of a tile and you just run into something for unknown reason so that that's should be good enough for now let's run it and see what we get uh we got an error because I for forgot to pass in the game so we do self that'll pass in the game reference so it has access to the assets and there we go we have our tile map and we can see our tiles which look nice so now let's make it so that the player can fall because right now we can only move right and left so for that we go over here and since later on we'll create a player entity that will inherit from this physics entity but because gravity will be universally applied I'll apply it to this physics entity object instead of creating the player one so for now the player can still be a physics entity all right so let's go ahead and apply our gravity so to do that as I mentioned you apply acceleration by modifying velocity so our velocity is going to be self.velocity and the y-axis of the Velocity is the second value so right here normally you could just do plus equal say 0.5 or something but I'm going to use a different trick because there's this idea of terminal velocity in physics so in physics something will have a maximum velocity you can reach while it's falling and that's called terminal velocity you don't want to just follow long distance and just accelerate to an Infinity because you keep falling so for that you usually want to cap how fast you can fall after a certain point you want it to kind of ease into that fall but you also want a cap so for that we can use the Min function which is built-in python function it'll take the lower of two Val or however many values you give it let's do five that'll be our maximum velocity downwards remember since this isn't the normal coordinate system from math we have positive Y is down not up so that might be a bit misleading but that's how it works so this 5 represents a maximum velocity of five downwards you can go more than five upwards which would be like negative 10 or something so that doesn't affect this so we take five and then we do self.velocity one plus 0.1 so right here we're modifying the velocity and and then if this modified velocity is less than five it'll take this part so that's just effectively doing a self-doubt velocity one plus equals 0.1 if that's the case but in the event that this number is smaller it'll take this one instead so that it can't go over five I'll use this technique a lot later on but I figured I'd explain it now since it's a very useful technique all right so we have our velocity being updated I'm actually going to drop this below the movement and then now if we run the game look at that our player Falls but he falls through the floor so now is the time for physics this can be a bit tricky if you don't know the actual technique for doing these types of collisions I remember I spent like a year or so on game development actually I think maybe a couple years before I heard about this technique and I actually just found it in some example code somewhere I don't even remember where I got it from but trying to write your own stuff for this from scratch is a bit tricky if you don't happen to know the uh technique for having consist and questions all right so in order to handle collisions efficiently what we'll want is a way to look up piles so for physics in the case of the player since it's not very large you just kind of want the tiles that are nearby the player and then you can run the collisions from there you don't want to look up all of the tiles and test them all for collision with the player because we know the player's position and we can look up the tiles based on position so there's no reason to try to test all those collisions at once if we can just look up the like nine tiles are on the player if the player were bigger and could collide with a larger area you'd want to look up more tiles so keep that in mind if you're doing your own stuff here but in our case we just need the nine neighboring tiles so we can get some pretty good physics so let's do neighbor offsets equals negative one zero these are going to be the offsets from a given tile to get the nine tiles around and including it so negative one negative one zero negative one one negative one one zero zero zero negative one one zero one one all right so these are just all of the combinations or all the permutations of negative one through one actually this would be offset it's not offset so now we can write an interesting function we can do def tiles around self position so you pass in a pixel position so remember the grid tiles are not in pixels so we'll have to do a bit of a trick here so we'll have to convert this pixel position into a grid position to do that we can do tile location equals int position zero self.tile size so if you don't know what those double slash is it's integer division so it'll chop off the remainder so that you don't get for example if you divide three by two you don't get 1.5 you would get one and you might be wondering if we're doing integer duration already why do we need this n here and that's because if one of these numbers is a float it may round it to an integer but it'll still have the dot zero on the end and uh that can cause some problems with the way we'll do indexing in a moment so we saw it's converted to an integer and you might be wondering okay so if we're already converting into an integer why can't we just chop off that extra slash into normal Division and then convert it to an integer well the problem there is that integer truncates it does not do proper integer conversion the same way that double slash does so for example they handle negative numbers differently let me pull up a terminal so you can see this so let's say you have 3 divided by two you get one uh let's say you have int 3 divided by two you also get one so that's expected but let's say you have negative three divided by two you get negative two you do int negative three divided by two you get negative one they have different results uh and that's because they handle negative numbers differently so int will pretty much just chop off like if you did negative three divided by two it'll pretty much just chop off that point five which is not a consistent Behavior because if you think about the number zero through actually negative one everything between not including negative one and one so let's say 0.9 that will go to zero negative 0.9 that will also go to zero so that's an important distinction and this is why you need to be very careful about converting things to integers with dealing with computer Graphics this is making the space of zero almost the size or sorry it's the same space between like one and three in terms of pixels I guess you could say that the two pixels bordering zero get brought into one or something like that it causes some issues when you're dealing with this stuff so especially when you're dealing with grips you have to be very careful about how your stuff gets rounded anyways back to what I was doing so this will just this mess of code will convert the pixel position into a grid position so that's just the x-axis so we have to do the y-axis now and now we have both we have a nice style location and then we can do a for Loop so four offset in neighbor offsets this is the thing I defined earlier we can now generate all of the tiles that are around this pixel position given so for this we can do check location is and then we compose a string because that's how we did our keys here string of title location zero and then we do plus offset zero so we're adding all of these numbers to whatever this base location is so that'll give the nine tiles in that area so we do that and then we add our semicolon because that's what's what's our location and then we can do it again and we'll throw in that which is the y axis all right now that we have our string we can do if check location is in self.tile map that just checks to make sure that the there is a tile that happens to actually be there because remember in a tile map we can have a lot of empty space and that's just denoted as they're not being anything in the dictionary for that location all right if that location exists in the tile map we can do actually I'll throw a password here right now and then we'll go up here and we'll do tiles equals this this is the tiles we're returning so we can do tiles that append and then we can just grab the tile data by doing self.tile map and then check the location all right so now we can return the tiles and this will give us all the tiles around that location so I'm just gonna run this function so you can see the output here so let's just do print self.player actually in itself.tilemap.tiles around and then we can do self.player.position so that'll give us some interesting output here you see that so right when we pass to the ground it started outputting this data here so when we were in the air just gives us this and we're near the tiles gives us this so this doesn't mean we're touching those tiles it just means we're near them and this is where we can actually do the collisions because that's a little bit slower than looking them up all right for the collisions we gotta go back to titlemap and there's something else we got to do because we don't want to quad with all of the tiles we're going to be able to put stuff in the world that we can see but we don't necessarily have physics for so you can just like walk by a tree or something so the next thing we have to do is add a function that'll filter the nearby tiles for things that have physics enabled so let's do physics Rex around so we'll do another sap in here to make the collisions easier which is converting all these tiles to have physics into pygame.rect if you remember that's what we did the collisions on like I don't know an hour ago so we can do Rex equals this this is the rectangles we're returning and then we can use this function here to get all the nearby tiles so we can do four tile in himself Dot and then we do that and get rid of that self and now we're iterating over all the nearby tiles if there are none this whole Loop gets skipped to begin with so we'll just return this empty list of rigs so if tile type and this is where we're going to get into something interesting uh we need to add a list of things we want physics on so physics tiles equals and I'll say grass and stone so if you don't know even though curly braces are normally for dictionaries if you don't do the like key value pair thing this is what's called a set a set is used for a collection of values usually order is not important but the big thing is that no duplicates are allowed and actually looking up to see if a value is in a set is more efficient than looking up a value to see if it's in a list for example so this is actually slower than doing this if you're looking to see if a value is in there so for something that's being run a lot which is checking tile types uh it's good to have a set there and this is one of the techniques you can use to optimize things and don't forget that we also have this code right here which is also already a huge optimization you can have as many tiles as you want and the collisions will still run fast because it just looks up the nearby tiles if you don't know looking up a value in a dictionary is also very fast you can have as many values as you want and it still roughly takes the same amount of time to look up a value as long as you know the key all right so we want to check if our tile type is in the physics tile so if it's in physics titles that's very English then we know that this tile that we we have near us is a tile that we can collide with so because of that we now want to add something to our Rex list so we just do a DOT append and up here we're going to have to import Pi games so we can actually Trade It erect we do pygame dot rect so a new rectangle and remember Rex are just kind of a thing in memory unless you decide to draw them but you can run questions on them you can draw them you can do lots of stuff with them so we'll create it with the X position being the tiles position on the x-axis and you have to multiply it by the tile size to get the position in pixels and then tile position on the y-axis get the pixel position and then we also need these towel size because this will be the size of the tile in width and height and since they're Square it's going to be the same for both of them all right so that will add the new rectangle so we can return these rects and if I go over here and if I take this back and then do physics wrecks around instead of returning those tiles it should return a bunch of wrecks when I pass through all right so you see these These are the wrecks that I'm nearby so the final step here is to run the actual Collision detection and handle the collisions so that the player can't pass through for that part we'll go over to our entities and we'll have to do a new Step here we're going to throw in the tile map so tile map will become an argument and we can go back to game and in our update function right here we can do self.tile map so that that gets passed incorrectly all right so now that we have access to our tile map in here we can do our Collision detection if you remember from before in order to do collisions you have to have two rectangles so the thing you're colliding with needs a rectangle and the thing that's doing the colliding needs a rectangle so let's give the physics entity erect so to do that we want one generated dynamically because we don't want to go through the effort of updating it all the time so we will create a function for generating it let's do return pygame.rect and we'll do self.position zero self-doubt position one so that's the top left and that's something important to note is that we're using the position as the top left of the entity if you remember that's how surfaces are bloated onto each other it's by top left and that's also how we're going to handle the physics here so this position on the entity you might think it's the center but it's actually the top left of our entity all right so now that we need our size which we already have self.s size zero self.size one and that will be our ret and the reason we want the function is because we're changing this position all the time and we want to be able to get a fresh cracked based on that new position whenever you need it all right so now we with that wrecked we can finally do our collisions so let's put this in half here we'll do entity rect equals self.rect and then four erect in and this is where the magic is Tau map dot physics Rex around self.position we want all those nearby tiles if entity rect duck collide rect with one of those tile rectangles this means that we've had a collision so now the next step is to handle this closure so this is where the magic of splitting up our movement into two axes comes into play you don't want to do them both at once I have a whole video about this it's pretty old but it's still really good video I think explaining in depth how this technique works but the idea is that if you handle one axis at a time say you move on just the x-axis if you collide with a rect because you know the players erect and the thing you collided with is erect and you're you know what your movement was you can now resolve that collision by adjusting the player's position based on what was collided with so in this case let's say you're moving right you quieted with a tile so this is where this thing comes into play we could say if frame movement zero is greater than zero that means you're moving right let's say you're moving right you quieted with a tile so there's a really simple solution for how you handle this you make the right edge of the entity snap to the left edge of the tile so that way they touch and then they get pushed back straight to the edge so we just do entity rect dot right equals Rex dot left and we can do if frame movement zero is less than zero entity wrecked dot left equals right the right because let's say you're moving left because remember Negative X is left if we're moving left and we Collide we want to set the left side of our entity to the right side of the tile and the last thing you have to be careful of here is that we're modifying this Rex position we're not modifying the player's position so we have to go and update the player's position now based on this logic we did here so if you wanted to do the actual math yourself it gets more complicated that's what we abstracted out to the rects so we can do self.position 0 equals entity rect.x so you might be wondering if you can move around a rect like this then why don't we use the Rex to represent the position of the entity so there's a slight issue with Rex if I import Pi game here I can show you you could do pygame.rect so let's say you have uh zero zero and say 10 by 15.5 Rex only works with integers so you can't have sub pixel movements with Rex so depending on how fast something is moving you might have problems with movement depending on whether or not you're abstracting for a wreck or not there are very few cases where it's okay to just have your position represented as purely in an integer when it renders because we're using pixels and everything it gets converted into an integer but for example if you move 0.5 pixels here I can show you let's say R equals pygen direct you can do R to X plus equals one R to X is now one or you can just print it out and now let's say this r dot x minus equals 0.5 that works fine so r dot X plus equals 0.5 now you see the problem we just added 0.5 and it didn't move so it was added two more times it still didn't move and this is where the problem is so if you're using pi game CE which is that choice I mentioned at the beginning there is something called an affect which allows the Rex to be float so you can still do collisions with them and handle positions but since I'm trying to make this tutorial work for both versions of Pi game I have to create my own position variable here that's why we do all of this it's a bit tedious but that's how it is so if we were actually just purely doing a pi game CE tutorial I could make this a couple steps simpler and take out the position thing but uh I'm not doing that so it's a bit more complicated all right so we've done the quotients for the x-axis and now we do the y-axis and this is the whole point of separating out the movement here because you handle the Collision twice per frame so that you can handle the collisions based on whether or not that Collision was from moving right and left or up and down because it gets a lot more tricky if you try to do both of them at once all right so let's just copy this over and we do the same thing the only difference is this is now the y-axis so this now becomes the bottom and top and top and bottom so if you're curious about what these attributes actually return this is just a single integer representing the x or y-axis depending on whether or not you're doing top bottom or right and left so it'll be like a position on the x-axis or a position on the y-axis all right so now with those two collisions done we have our physics so let's go ahead and run this and see if it works and look at that we're standing on the ground and I can move around and I can run into this wall and I can't pass through it so now there's a slight problem you see I dropped off very quickly there I I didn't slowly accelerate into a fall like it should so the other thing about track of is the fact that our velocity here reached five but there's nothing to ever bring it back down normally when you hit the ground your velocity should be updated to zero it may not be able to move through the tiles because of our physics but that velocity is still sitting there at five so let's add something to keep track of what types of collisions we've had so I'll go up here and add a self.collisions dictionary and we'll do up false down false right false left false so this is a little nice trick you can use keep track of which directions you've had a collision and we can use it in here and in other locations to see what directions an entity just collided with this will be useful for things like wall jumping and stuff later so for our collisions here we just have to go into here and when we actually resolve these collisions we can update our Collision so self.collisions right equals true and then this one is going to be left and this one will be down and the last one is up all right so we have our four directions and something important to note is that we're resetting it every frame every single time this updates function is called this will be reset so it'll be one frame you get where it says true after a collision but if you're just continuously quieting like you're holding right and you're constantly running into the wall it'll always be true but if you're running into a wall and you lift your finger off the right key uh it will no longer stay true because you're just standing next to the wall not actively running into it all right so from here we can do if self dot collisions down or self.collisions up this is where we want to reset our velocity so self.velocity one equals zero so you notice I did it on down or up and that's because if you run into the ground it should stop you if you are going up and you hit the roof it should stop you you could also do the x-axis too but we're not using that velocity yet we're only using the Y velocity for now so now I can run around here and if I fall off the edge it slowly accelerates so let's go through here and add one more thing it would be nice if we could jump and this is where the magic of this whole physics-based system comes in so we can do if event dot key is pygame.k up we can do pig or self.player.velocity one equals negative three so all we have to do is override that Vertical Velocity and set it to a negative number so that the velocity is pointing upwards and suddenly the player is just moving upwards and then the gravity will pull it back down so it's a nice smooth jump all right so if we run this code and if I press up you can see I go up and down so it's a nice Arc and it's very responsive as well this is I think the best way to do jumping there are some people that try to like model it as some sort of formula or something but overall I think this just works really well and I this is a technique that I always use all right so now that we have jumping we can test a couple other things so right now we have infinite jumps later we'll work on making that limited but for now we have infinity jumps the last thing we need to test is to make sure that if I hit my head it stops me properly so if I go up here you can see it I instantaneously bounce off the roof I don't run into it and stick so that should be pretty much everything for the physics if you see I can just run around and this works pretty much exactly as you'd expect which is great so we have a game where the player can jump and run around and run into stuff but the next thing that you would often see in Platformers is a camera that can move around so let's get into that the idea of a camera when it comes to lower level computer Graphics is really Just an Illusion instead of moving a camera around you're really just moving everything else let's say you wanted to move the camera right if you moved everything in the world to the left you would see the same thing so this idea of a camera isn't really real you can abstract your code that does move things around into like a camera object and that's sort of what we'll be doing and that's how you can sort of get something that's easy to work with so you can move the camera around all right so to do this I use this idea of a scroll variable you can think of this as like the camera's location so self dot scroll equals zero zero so let's start the camera at the position zero zero the next thing we'll want to do is actually make it to this camera position gets applied to apply this camera position we need to start adding offset variables everything that renders so right here let's do offset equals scroll and this is how much we're going to move what we're rendering because as I mentioned the idea of moving a camera is just moving everything else so we'll add this offset for rendering based on where the camera is and then we can add this actually should be self.scroll we can add this to all of our rendering stuff which is just I believe these two things so our tile mapping our player now let's go over to tile map and go into render we can say offset equals and give it a default value in case we don't provide it but we did so whatever but over here we're going to want to apply this offset so we do minus offset zero tile position 1 minus offset one you'll notice I broke up this Tuple that was here or it was originally just the position but I had to break it up into its elements so that I could adjust it if we were using packing this Vector too is you could just add it or something something else to note is that I'm actually using some subtraction here instead of adding the offset which is a bit more intuitive I'm subtracting it because when you actually are working with the camera if you remember I said you move the camera to the right everything on the screen effectively moves to the left so if you want to deal with the camera and World coordinates the offset should be applied negatively all right so now we have to do that over here so we can go minus offset zero minus offset one okay now that we have offsets in here let's go over to the player and in here take the offset parameter and then we also have to Output it here offset zero laptop position one minus offset one okay now that we have that if we run the game it shouldn't crash and we see the exact same thing the next step is to make it so that we actually are moving our camera to do that we can do something as simple as self.scroll0 plus equals one and then we can run the game again and you can see the cameras moving to the right so now let's see something where the camera focuses on the player we're going to use a interesting technique here we don't want the camera to just move linearly to the player you want it to kind of slow down as it gets closer to the player so it's a more smooth motion and it should speed up the further away the player is from where the the camera is so let's do scroll zero plus equals what's up that scroll and then we can do self.player Dot rect Dot Center X and then this is where things get a little bit tricky minus self.display.getwith divided by two so this camera position is really the top left so if you were to actually put the camera directly onto the player's location the player would end up in the top left of the screen and we don't want that we want the party be in the center of the screen so we have to do is we have to subtract part of the screen size so that the camera is positioned in a way where the center of what you can see is on the player so that's what this is for and then the next step of this formula is to subtract self.scroll zero so if you go and do the math on this we're taking where we want the camera to be we're subtracting what we have and then this whole thing is being added to the scroll so what we're doing here is just finding how far away the camera is from where we want it to be and then we're adding it to the scroll so if you think about it this is just a glorified way of setting the camera to where we want it to be but there's a reason why I wrote this as plus equals and we have this in parentheses here so there's this fun trick you can do where if you just divide the increment because this is how far off we are if we divide by 30 it now will take 1 30th of whatever distance there is remaining to the center and apply that instead of the whole thing so the further the player is the faster it will move and as it gets closer it'll slow down because that distance shrinks all right so let's take this and do this for the y-axis as well so it would go Center Y and get height and let's go one and there's this one Minor Detail we'll have to deal with here I I think I might get to demonstrate what the issue is you might be able to see you might not it's the difference of a pixel so let's run this and see what happens so oh yes you can see it so look at the player I'm like jittering around a bit so if I jump especially you can see it's jittering with the floor the player was bouncing up and down and that's because the player's position is a float it's uh not a whole number and because the scroll is also not a whole number when you round things so that you can get the pixel locations of everything though those fractions can add up and make things inconsistent depending on the sub pixel movement of the camera and we don't want that we want the sub pixel positions to not really come through in the placement of entities because we want things to be nice and smooth for a pixel art game if you're not doing pixel art maybe this wouldn't be as much of an issue but for pixel art especially you should deal with that jittery issue so to fix that we just take a variable called render scroll and we can assign it to the integer of self.scroll 0 and itself does scroll one so it just takes whatever the skull is and converts these two numbers to integers if you remember before I mentioned there was a an issue with using the int on Pixel locations that's not as much of an issue with the camera because if the camera is off by one pixel it's not a huge issue some things off by one tile or depending on how things are moving if it's off by one pixel it could be an issue but for the camera it's not an issue so we don't have to worry about trying to deal with the weird way the end behaves anyways so now that we have this integer version of the scroll position we can use that instead of self.scroll and because now the scroll won't have its own decimal component the only thing that will have a decimal component is the player so you won't get any of that subpixel movement showing through as you show the camera because you have to have two elements with sub pixel parts to their coordinates for that to work or for that to cause issues anyways so if we run this again that Jitter should be gone now you'll notice that the camera gets a little bit choppy as it gets closer to its Target there's not much you can do about that because the camera's position is in pixels and everything's rendered in pixels so since our pixels are larger since this is a pixel art game if we want to keep things consistent it gets a little bit choppy now there are some games where they'll have like pixel art artwork but everything moves around with sub pixel movements and that's something you could use to get the movement of the camera to be a bit smoother but I like to stick to Pure pixels I'm a bit of a Puritan when it comes to my pixel art so that's just how I like it anyways our sky is looking a bit flat so let's add a background there if you remember we have our background.png here which is a nice Sky background with some patterns on it and let's go ahead and load that in so we can do background load image background.png and now instead of filling the screen with this color we can do a DOT blit and we can take our self.asset background and we can put that at zero zero so since this background is the size of the screen that pretty much does a job of wiping the screen for every frame before we were filling it with one solid color now the screen basically gets reset to whatever this background is because this will draw over everything that was there before now if we run the game again we now have our background but our sky is still looking a little bit empty so why don't we add some clouds so this is where things are going to start getting a little bit hectic let's go ahead and go into our scripts here and create a new script called clouds and let's import random because I know we'll be using that let's start by creating our Cloud class so class Cloud we need an initialization function so a cloud will have a position and then it will have some kind of variant but in our case we'll just pass the image that we're using and then a cloud should have a speed that it's moving at and then a depth for how deep it is into the sky I guess all right so we can do self.position equals list position if you remember we want to take the list of that position so we do with tuples and copying and all that and then we can take the image we don't want to copy this image because it's we're not modifying this image in the cloud so we can have them all pointing to the same Source image so that we don't waste memory all right so for the speed that will be the speed and then one last one is the depth which is the depth all right so when we update our cloud we just have to do one thing the cloud just has to move a little bit so let's move the cloud based on its speed and then let's create a render function so self let's say our destination surface and this will need an offset too because remember now we have we're working with the camera so we need to start taking offsets and pretty much everything we render unless it's like the user interface which we won't have for this project all right so we need to calculate a render position we'll do self.position 0 minus offset Zero by self.dep so instead of just applying this offset straight up we're going to use a trick where we multiply it by the depth that means that if the depth is like 0.5 and the camera moves like five pixels to the right this Cloud will only move 2.5 pixels to the left whereas all the train in the front is moving five pixels to the left and that will create a nice Parallax effect and this is probably the easiest way to implement that is just multiplying your camera for this we'll want all our depths to be less than or equal to one in reality we're actually going to go even lower than that so that they move slowly in the background all right so self-top position one minus offset one by self.death and then that should cover our render position so now we need to actually render it so surf.blit that's our destination surface soft.image that's the image for the cloud and then we do the render position zero and this is where some other magic comes in if you notice here our position just keeps on incrementing we don't have any code for removing our clouds or adding new ones I'm going to use a trick where you just create like a set amount of clouds and they just loop around and to do anything that loops with computer Graphics usually you want to be using the modular operator because when you divide something by another number and take the remainder you're effectively just creating a loop of numbers so for example if your modulo is 10 and you have your number is five you'll get five as an output if your modulus 10 and your number is 12 you'll get 2 as an output so it can increment up all the way to nine but once it hits 10 it'll go back to zero which is really nice for making things Loop we have to do some offset stuff because of the way the the images are positioned so let's go ahead and do modulo surf.getwith so that's the size of the display and we'll actually want to add some width here because you don't want the cloud to be looping as soon as it hits the edge you need to have the extra space for it to go off the screen so you need extra space for looping based on however big our cloud is otherwise you'll see it teleport before it's actually fully off screen or the other thing you could end up seeing is that it suddenly appears on the left side of the screen instead of rolling onto that position all right so for this we'll add self.image.getwith and then we'll want to subtract the width again and that will just handle the way that images are placed on the top left and we have to do some adjustments for the edge of the screen and for the wrapping so now we have to do the Y component so we can just copy this whole thing go to the right paste it we can say render position one get height get height get height all right so now that's our mess of a rendering function done now we need to create our clouds class which is going to store all of our clouds so self cloud images and then we want account for how many clouds spawn so self.clouds equals an empty list that's where we will store all of our clouds and then for I in range count we'll want to spawn a bunch of clouds so self.clouds the append and then we take the cloud object and instantiate it and then this will pass in like a random position and our random image speed depth everything so this is going to get a little bit messy we can do random.random by 99999 you might be thinking that's way too many pixels that's going to wait off the screen but remember We're looping it so it doesn't matter how high that number is it'll loop back to somewhere on the screen all right so now we can do random.random by nine nine nine nine nine again for the y-axis and then we can get a random choice of our Cloud image and the next step is to get our speed which is random that random by 0.05 plus 0.05 so that's starting with this number and going up to 0.1 so that's really slow the idea is that well clouds are usually slow moving at least in Platformers and now we can do our depth So Random dot random by 0.6 plus 0.2 so we want a minimum multiplication for the camera's position to be 0.2 so that will be the camera is moving the foreground five times faster than the background all going all the way up to 0.6 plus 0.2 which is 0.8 so that's 25 faster for the foreground than the closest Cloud all right so with that spawning stuff down there's not much left to do we can do self.clouds.sort key equals Lambda x x dot depth so this is a bit of some Advanced code I would say what we're doing here is we're sorting all of our clouds here and the when you pass the key function for the sort function it determines how you sort things which usually you use for objects in our case our clouds are not naturally sortable python wouldn't really know what to do with them so we have to tell it how to sort it so what we're saying is just take the object and then sort it by its depth so by doing that the clouds that are closest to the camera are going to be pushed to the front for our rendering you don't want the stuff that's moving slow in the far background to appear in front of the faster moving stuff in the front so you got to get your layering right with this and sort will automatically take care of that for us all right so just a couple more things we need our update function for the clouds and that's as simple as for cloud in self.clouds cloud.update and then we need our render function so self serve offset equal zero zero and then for cloud in cell of the clouds cloud.render surf you just pass the arguments along all right so there are our clouds so the last step here is to import them and actually use them in the game so we do from scripts.clouds import clouds and then we can go over to here and say self.clouds equals cloud and we just pass it the self.assets cloud so those are going to be the images for the clouds which we need to Define here so we can do clouds load images and this will be under data oh no not data what is it it's going to be data images clouds so we're looking for just clouds I believe and that should load those two images as a list and then now with our clouds here we can go ahead and say how many we want and then we'll just say countries equals 16 for clarity and we can go down here into our update function and we want to make sure that our clouds render before the title so we go and do that right here so self.clouds.update that'll just move all of them and then we need our render function for the clouds and we want to render onto the display and give it the offset of the camera all right so now if we run the game behold we have our clouds and look at how they're moving slowly which is nice and you can see if you look carefully they're actually looping around and you can tell that and if I move fast enough you can see that there's a bit of a parallax effect going on so some clouds are clearly in the front and some of them are in the back and it move they all move slower than the ground in the front which creates a really nice effect so that's pretty much it for Parallax it's a really simple concept but it looks really nice as well while we may have implemented a nice tile system already typically if you're working in some larger environments you will want to optimize this a bit currently if you look at our setup here we render every single tile that we have onto the screen we don't want to do that it's much faster if we can efficiently determine which tiles should be on the screen and then only show those tiles so what are the conveniences of the way we had our tile set set up is that it's in a dictionary which means we can look up the values if we know where the camera is we can calculate all the possible positions that could be on screen for the Tau map so that would be these positions right here where we're defining them as like the string of the coordinates separated by semicolon so the idea is to figure out what the essentially top left tile of the screen should be and then calculate all those strings going down to the bottom right and everything in between and then we look up those locations and if there is a child then we render it so doing this because dictionaries if you have the key to find a value because they have a runtime that's above one if you don't know what that means that's not a huge deal but it just means that no matter how much data you have it's roughly the same amount of time to look up the value which means you could have millions of tiles and still look up tiles very efficiently so we're going to need two for Loops here one for the x-axis and one for the y-axis so I'm just going to comment this out for now because we're going to replace it so we want four X in range and then we'll take offset zero self.tile size offset zero plus surf dot get width and then divided by itself dot tile size and then we'll add a plus one here so what we're doing here which is we're taking the range so that's all the numbers in between and then we're dividing the offset here of the camera by the tile side so that will find what the top left tiles X position would be because remember the offset here is in pixels and our title system is in the tile coordinates which is just the normal coordinates divided by the tile size and then we can take our offset zero so same thing and then add the display width and by that we find the right edge of the screen and then we divided by the tile size again and that gives us the tile coordinates of the right Edge but there's an off by one error because of the way the coordinates work where you need to add one here to get everything so that's just adding one extra tile on the right side that we need to check all right so now we can just do the same thing for the y-axis actually so that would be offset one because that's the y-axis and then instead of get width it's get height and then everything else stays the same now from here we can calculate or actually we can change that so y we can calculate our location string so string X Plus semicolon string Y and then we can do if location in self.tile map and then we can take our tile so tile equals self.tile map location all right so now that we have our tile we can render our tile in fact actually I just noticed there's a mistake here the this off-grid tile stuff is what we want to keep it's the stuff below that we're replacing oops so I'm going to move this back up over here and then it's this that we're replacing down here so we want to get rid of this and this line right here can be reused to render our tile so we can just drop it right here and then let's just get rid of this now so we're using the same code where we just take the tile so that we'll take the position and the tile type and stuff to get like the image and where it should go so that we can bullet it and that's all coming from the same thing because this is the equivalent of what we had before so now that we have that we should be able to run it and things should be the same at least they should look the same so yeah you can see we have our player jumping around and everything and the map works as expected so you can't really see a difference here but if you were dealing with very large worlds you would notice a very big performance difference and actually even with medium worlds if you're actually logging it you would be able to tell something interesting to note though is that in this particular example this is actually going to be slower than what code we had before and that's because there's a lot of air around and we have to look up the title locations even if it is air so that takes some extra calculations where before we just iterated over everything but as soon as the number of tiles will not necessarily as soon as because there's a bit more overhead and doing all these calculations here but roughly around the time when the number of tiles in the world is greater than the number of tile coordinates that would be capable of appearing on screen that's roughly around when this will start to benefit you and that will happen in a lot of cases so you might think okay this is great but we also have this for Loop right here where we're going over every single tile well this one's a bit more complicated to fix because this one well right now we even implemented it out as a list um there's other ways you can structure your data for this stuff to make it efficient so if you have off-grid tiles you're going to have to implement some extra system here to optimize the rendering but that only needs to be done for very large worlds because typically if you have these off-grid tiles and remember we actually don't have any yet but we will have them later but if you have off-grid tiles usually you don't have that many of them because they're usually just kind of some decorations here and there most of your tiles are normally going to be on the grid unless you're doing some really fancy stuff so you it's you can get away with a lot more when it comes to the off-grid tiles before you have to start optimizing them which is why for this project I'm not going to optimize them for many level based things where you're making your levels by hand and it's not like an infinite World you usually don't end up having to optimize your off-grade tiles but if you do do an infinite World where there is automatically generated off-grid tiles you're going to need to implement a system for this to optimize it and that system could be a set of quads so you separate your off grid tiles into quads and then each quad will be a list of all the tiles in it and it's sort of like the tiles we have now except instead of mapping a tile to location you would mount a list of off-grid tiles to that location and then your quads would probably be bigger than your actual tile size so you would maybe have quads that are four times larger in each Dimension than your tiles so that would be like uh 16 tiles could fit in one of those quads but that is more of an advanced topic and it gets even more complicated if you want to deal with stuff that's moving around because then you have to switch which quad your off guard towels are stored in but overall it's the same principle it's just you have to deal with all these different edge cases and handle things slightly differently so that's something you could do in your own time if you're interested in that type of thing all right so that's pretty much it for optimizing your tile map this is one of the biggest optimizations you can do for games like this so I thought it was important to show you how you can do it here if you're curious about how to optimize things more in general it's very common to see solutions to most game development related optimization problems at least at this level you'll see that most of them involve just using dictionaries because they have really good lookup times and you can throw as much data in there as you want so a lot of these problems are just the issue of how can you organize your data in such a way that the basic operations that you're doing don't take as much performance and dictionaries just end up being the solution to that most of the time or at least part of the solution sometimes you're dealing with stuff like caching where you'll want to pre-generate something and then you store it inside a dictionary so you can look it up later and then use that but even then you're also still using dictionaries so it's funny all right so now that we have our tile system optimized I think it's time to get into something that a lot of people would think that Pi game has supported by default but it actually doesn't and that is animations this is to me one of the big things that differentiates something like Pi game from a game engine is that there's no built-in animation support if you want to animate something you have to write your own animation system and that's one of the things among others so some of the other things that differentiate it would be the fact you activate your own physics or your own tile system and those are just kind of know some of the basic things that people expect in game engines although there is no formal definition for game engine so because of all those things that I just mentioned though High games about as far as you can get from a game engine so we do have to keep going down this path of doing things ourselves which is nice on some level because you get a lot more control over it and then another way it's nice is that it gives you more experience dealing with lower level problems in game engines it's more like what system that's already made can I use to solve this problem so lots of people will have to Google very specific problems and find very specific Solutions whereas when you're working at this level every solution is more General so for some people and this was the situation for me when I first got started I actually found it much easier to make all these Solutions myself than to try to figure out how to use all these pre-made things that come with engines the other side effect is that it's a much more General problem solving skill that you're developing by solving these types of problems so typically you'll find that if you're trying to do work as a software engineer you'll get a lot better experience working on this type of level than if you were to try to use a game engine like Unity or something and then once you have these systems already made you can start writing your own Frameworks and stuff to get to a point where you can actually work faster on certain types of projects than people using engines which is really cool all right so let's get into animations the first thing we'll need is an actual animation class so we need some way to represent an animation as an object so to do that in my utils script here I'm going to create a animation class So Def in it this is basic stuff so we want to take in a list of images that we want to animate so we'll do images and then we also want to say image duration so this is basically how many frames we want each image in this animation to show for in the last argument I'm going to add is Loop so Loop equals true so by default we want our animation to Loop so let's just bind all these parameters so self.images equals images self.loop equals Loop self dot image duration equals whoa image stir and then self.done oops self.done equals false that will be set to true if you're not looping and it reaches the end and then we have self.frame equals zero and now we'll just keep track of where we are in our animation so this is one of the most primitive types of Animation setups that you can make we're restricting ourselves to giving each frame the same duration across the animation if we don't want to do that we could set up a system where each frame has an assigned duration you go and do all the math and try to figure out when to move to the next frame and stuff like that and you can add all sorts of other rules to animation if you actually look at some of my games you can find the animation code and it gets a lot more complicated than this but this is a very simple one so the idea is to just take all of your images and then just render your images based on your frame and then that frame increases over time and then we also Loop it and that's enough to get us a nice animation all right so one of the first things we'll want to do here is we want to be able to copy our animation because if you notice we're taking in our images and we have all these or specifically these two values that are specific to an individual animation so the way I'm going to set this up and this is some people might say this is bad code some people might say it's good code um for this use case I think it's pretty good because it's a simple solution but the way this is going to work is that we create an animation for each animation in the files and we have it somewhere that's easy to reach and then every time something wants to use that animation it will copy its own instance of the animation so something important to note is that the way python works is that if you have a list and you assigned something else to that same list or even an object it's just any object in general that's not like a primitive value uh it will be a reference to it instead of actually copying it so let me demonstrate here let's do def copy self return animation so that's just making another one of itself and we pass it self.images self.image durations our duration it's of that Loop so I'm passing in the images that it already has the same duration in the loop so it's all these same values so it'll generate pretty much an exact copy of itself but like I was saying it because we're passing in images here it's not actually making a copy of the images you're just getting the images this the same list of images so if you modify it in one of them it will affect both so that's something to be careful about but in our case that's actually to a benefit because we can copy this as much as we want and then all those images don't take extra memory because they're all looking at the same thing all right so with that copy done we now need a way to render our animation so let's do def image self and instead of actually rendering it to a surface and like having a render function I'm just going to make it so that you can get the current image of the animation because that's a little bit more flexible because we can use that image however we like instead of being in a situation where you have to give it a surface to render to so for this function we'll just return solve.images and we'll do int self.frame divided by itself.image duration so we're dividing our frame by how long each image is supposed to show for so frame here could I guess be interpreted to me in a couple different things so it could be frame of the animation or it could be frame of the game so in this situation we're going to be using it as frame of the game uh I think in in some of my projects I use it the other way around but but in this case we'll increment our frame value every single frame of the game and then every single time it crosses this threshold here this whole value since it's truncated to an integer will go up by one and that will be the index used to select which image we show so this will just give us whatever image we should have for the frame that we are on now we can do def update self and we need to actually increment our frame here so if self.loop we want to do it differently if we have a loop versus if we don't so this is not as simple as self.frame plus equals one because you'll run into a problem where uh let's say our frame goes past the end of the animation you'll get an index error on our images list because you're trying to access an image past the end of the animation so we have to make sure that this Loops properly and then also for if the loop is not enabled it needs to stop at the end so here's how this is going to work we'll do self.frame plus one so that's the same thing as plus equals one but I'm going to add an extra step here in this calculation so we're going to do modulo so that's the remainder of self dot image duration by Len Self images so this will Force are framed to loop around once it reaches the end because remember I mentioned before that pretty much anytime you want to Loop anything usually you're going to end up using a modulo because the remainder is effectively creating a loop all right so like I said it goes to whatever the maximum frame is for animation which is calculated here and then it just loops around and then we'll also want to do if we don't have a loop so that'll be else and we do self.frame equals Min self.frame plus one that's the same plus one thing we have a Min here this time so we don't want it to go past the end of the animation so Min takes in two values and then Returns the lower of the two so that'll be that we Define what our end of Animation is right here and if for some reason this value is greater it will return whatever the end is so self dot image duration and we did this before by landself.images now the one thing we have to be careful about is just being off by one because technically let's think about it like this so let's say you have an immigration of one and you have three images so you say okay our last image is number three and this math will give us a maximum value of number three but lists are indexed starting from zero so the index 3 does not exist because of that you have to do -1 and you might be thinking well wait we didn't have to do that up here and that's because of how module it works if you take the remainder of let's say three divided by three you get zero so if our end here let's say our last frame is frame 3 for some reason then it'll go 0 1 2 and then loop back to zero because you can't have a remainder that's equal to whatever you're dividing by so the remainder is actually naturally deal with that issue we just have to account for it here because this is a different system all right and actually if you're curious about why array start at zero that's actually one of the reasons why it's convenient at least in game development there's a whole load of reasons why it's better for them to start at zero but this is one of the ones where it's convenient is just being able to do this here all right so we do if self.frame is greater than or equal to self.image duration by self or little extra lens of the images minus one so this is just doing that comparison with this value right here and you could do is equal to I don't like to do that because this is a broader condition if we made a mistake and like maybe we went back and changed this for some reason this will not just suddenly break because of that so I I prefer to when possible just do the broader condition um if it makes sense so yeah we're just saying if the frame is greater than this we can do self.done equals true although technically if it's greater I will get an indexed error down here but just to be safe so this will set done to True when it's actually done and the frame can't go past the end which is just what we want all right so we have our animation class so now we need to be able to load our animation so for that we go over to game and we need to find our animations so if you go over here into Data we have images we have entities we have player this is a lot of files but then here we have all of our player animations we have like idle and then this is like 21 images uh we've got jump which is a single frame so you might think of an animation as multiple images but in this case we're going to generalize it so that the player is described as having a state that is an animation so even if when we're jumping we don't want to show multiple frames we just load the animation of that one frame that we want to show and we say okay we're on this animation and that's how we'll Implement that later so we also have run seven frames slide zero frames wall slide zero was actually one frame uh not zero but we have all those animations we need to load them in so you could write something fancy to load all these in automatically but for this it's easier a little quicker just for the scope if you're creating a bigger game I would actually write something to load this better but for this tutorial I'm going to do it by hand so we'll do player slash idle equals and then we'll actually have to go up here and in utils we'll want animation we'll do animation of load images remember the first parameter for animation is the set of images we want in animation of so we can just pass in what images we're loading here we can do entities slash player slash idle and then we'll set our image duration to six because I mean it's an idle animation we want the animation to probably go a little bit slower because it's just kind of that the player is inactive and just kind of bouncing around or whatever all right so we have our idle animation uh now we need our run animation and then this is just the same thing you grab this Swap all those and then I'm going to set the image duration to four because it's a running animation so you're running it makes sense for it to cycle a bit faster a lot of this depends on how you actually Drew everything uh it's not a hard rule it's just a number that makes sense for whatever your assets are and in my case these are the numbers that make sense you can play around with it if you don't like the the durations for all the remaining ones I'll use the default of five because most of these don't even have multiple frames so we have jump uh slide and wall slide all right so we should have our animations loaded here we can do print self.assets and we can see what we have all right so if we look down here here's our stuff here and you look we have like player jump and here's our animation object when run and idle and stuff and now the last step here is to actually use these animations to render something so for that we need to go over to our entities here and we need to add a few new things so we'll do self.action equals blank string self.nm offset equals uh and then this is going to be a bit of a hack and animations because say the object that you're animating will have varying Dimensions usually the way you do this is you add padding to the edges of your images so that you have space to animate the changing sizes of your uh whatever object you're animating so for example if I go over here and I go entities player run let's look at zero it's hard to see here right here look at there's all the space to the sides and then if you look at I don't know maybe oh here's a good one see look now this foot's sticking out a lot so we need some of that space on the side but if you remember this player image does not have that padding and if you actually go back to let me close all this if you go back where is it to our game here our players dimensions are smaller than those images we have so a tree trick you can use is to render your images with an offset to account for that padding so that our animations overflow outside of what would normally be the hitbox for the player normally you would have changed this offset for each entity in our case I think just setting it to negative three negative three should cover everything this is the the value that the player needs we can inherit from this and override it later if we wanted to use something different but this is just the number that we're adding to wherever we intend to render the image which will just offset where it's going to be rendered all right so we also want self.flip and that's because the player should be able to look right or look left we only have our animations facing in One Direction so we need to be able to just flip our images which I'll show you later self.set action idle so this is the function we're going to have to do right here we need to be able to set which animation we're currently using so to do that we need to create our function here we'll call our desk set action self action and these are just the string names of our action so if you remember the player has the idle run slide wall side jump animations so it's just the the string of those names and then it will find the animation for that so if action does not equal self to action so we only want to do this if the action has changed from what we already have saved self.action equals action so that's just updating it and then we update our animation here equals self dot asset self.e type so that's the type of the entity so to look for player slash and then self to actions it'll look for player slash run for example and then we want to copy that that's remember our animations up here are in our assets and they're these animation objects which we had the copy function for I'm just calling that copy function which is creating a new instance of that animation and this is why we need this case here to prevent it from being called if you set the action multiple times because it's more convenient I have found to be able to just say this is the current action each frame then to specifically determine when it should be changing and then what we do in here is just say check if it has actually changed because of this function call or if it should change because of this function call and if what we're setting it to is different from what we had then that's when we want to grab the new animation because the way it's going to work is we're going to say every single frame while we're running we're going to say set action run and if this were called every single frame then if you remember over back here we set frame equal to zero and this will happen whenever you copy it as well so it would be permanently stuck at the zeroth frame so this animation can only be created when you switch to the animation all right so now we need to be able to actually render our animation so we go down to the render function here and remember we had this hack here where we just used the player we don't want to use that anymore so what we'll do instead and I'll comment this out for now we'll do surf.blit pygame.transform.flip self.animation dot image self.flip folks so this is just handling the flip part so we have the flip attribute here that we created up here we'll be updating this in the update function uh and then we're using pygon transform that flip so this is just first of all taking the output of whatever animation we have this is where we actually get the current frame of our animation and this is why I mentioned that I think it's better in our use case to just take the image of the animation rather than creating a render function and that's because we might want to do something like this where we flip our image before we render it so we flip our image and this is the x-axis weapon this is a y-axis flip so we don't want to like flip it top over bottom that there's no use for that in Platformers for the most part like for entities you just like facing right or facing left you don't face up and down some NCS you might do that but uh for most of them this will be sufficient all right so this is this function here will output the flipped image that's appropriate and then we can work on our position here so for our rendering position we'll do self.position zero minus offset zero so that's the camera offset but then we also have that other offset which is the animation offset so self diam offset zero and then we do the same thing for the y-axis which is a software position one minus offset one plus self data animation offset one so that's a bit of a long line but there's all these different offsets to account for but they mean different things and they have different uses which is important all right so let's go ahead and delete this line and that's our new render function there the next thing is we'll need to be able to update our animation and that's simple we just go over here and this is the update function right here we do self die animation the update all right there's just one more step that we could do right here in this object and that will be the flip so we'll do if movement zero is greater than zero self.flip equals false if if movement zero is less than zero self.flip equals true so if you remember our assets are facing right it would be the inverse if they were facing the other way around where our assets were facing right so if you're moving right which is what this case is we set foot to false because we don't want to flip our image because it's already facing right but if you're facing left or while you're moving left our images are facing right so we have to flip it and that's why we say flip equals true all right so that should be everything for this class here I'm actually going to create a new one and this is where we get into a bit of the fancier stuff we're going to create a player class so we do class player and we will inherit from the physics entity and we'll do def init self and I'll take a reference to the assets because it'll need that position size and we'll do super dot init so this is just calling the the init function from the physics entity actually wait we don't want to reference the assets we need to reference to the game and also the mistake over here that I made uh and the reason this is happening is because I um in my reference code I didn't do the full option oriented stuff the same way I'm doing it here but that's not super important anyways uh so the mistake here is this needs to be self.game.assets so we're here this is dot assets all right so anytime there's dot assets actually that's the only one so that that should fix it but yeah so we pass in to the initialization function for the physics entity the pass in game position size actually uh and there's one extra thing it's game position size but then we have our entity type and because we're dealing with our player here we know that the type is going to be player and actually wait I know some another mistake here so that should be soft.type but anyways so we're passing in our type that's used here to look up our animations and we know that that's going to be the player for our player so we just pass it in here we don't have to put it in as an argument there we could if we wanted and there are use cases for that although this is fine for now so now we need an update function for a player so tile map movement equals zero zero and we want to use the default update function tile map movement equals movement movement and then this is where we get into some of the magic so we want an extra attribute for a player here we'll do self.error time equals zero and this will just keep track of how long we've been in the air so self.air time plus equals one and the objective here the reason why we're inheriting and adding our own code here is because each entity will not necessarily need to entity but different types of entities will have different animation logic so we're writing the animation Logic for our player and that's why we separate it with inheritance here all right so we do if self.collisions down so if the air time equals zero and then if self to air time is greater than four we'll do self.set action jump so if you're in the air for a while you want to show the jump animation all right and then LF movement is zero does not equal zero solve that set action run so if the x-axis of our movement is not equal to zero that means we're moving horizontally which means we're running so but if you think about it if you're in the air you probably want to override the run because you don't want the player to be showing an animation of running in the air you probably want to override run with jump that needs to be higher priority so by doing if and then LF here we're saying uh this one right here is the highest priority and then if this is not the case then we can move on to this one and say okay are we running uh and then if not if we're not jumping and we're not running we're probably idle self.set action idle all right so we have three animations uh those other ones are for extra things we'll get to that later but we have our three animations in momentum so the last step here is to actually use our player class so we're going to go over to game here and we're going to take from our entities and go player and we will create a player we want to do player and remove that and now we're instantiating a player which is inheriting from the physics entity instead all right so if we run our game now I still have that print statement so you can see there's an idle animation it'll be just standing there and if I run around you can see there's a running animation if I jump you get the jumping animation or the jumping frame so that's how you do animation it's a pretty simple concept but there's a lot of random stages to the implementation animation is all about just showing different images at different times so it's fundamentally not that complicated it's just it's nice to have different levels of abstraction to make things easier to work with because in the future if you want to add another entity you pretty much just have to load in the assets and you say well create another object like this and then you write the custom logic right here and you're done and also I've read the extra thing I didn't really point out was that you can face right you can face left and that's based on where you're moving which is nice all right let me just remove this last print statement here and we're done with animations woohoo with our animations done and our tile system optimized you might be thinking that our world here is looking a little bit empty with these two I guess slabs or whatever of tiles so it's time that we start to fill out our world but placing tiles by hand or by just coding up the individual tile placements it's pretty tedious so oftentimes it's a lot better to use low level editor for some games you'll write a map generator or something like that so there's all dynamically generated but for most games you're going to need to touch a level editor at some point there are plenty of good level editors out there but a lot of them are lacking in flexibility in certain areas so I prefer to write my own and additionally there's a lot of random things that are going to come up if we do write our own editor which we will be that are unique Topics in themselves that are useful to know about so for example by default this game I'm working on won't have any Mouse input so I'm working on a level editor will make it so you can learn about that here in this tutorial so when you think about a new I guess project one of the important things to think about are inputs and outputs so in a level editor you're inputting your like keyboard and mouse input to create a level and then the output is some sort of file that represents your map it's also worth noting that if you want to be able to edit a previously made map you also need to be able to load that map back in so that means that one of the core features that our level editor is going to be dependent on is the ability to be able to save and load map files so you might be wondering how do we save our Maps one primitive technique which I think I alluded to earlier is to just have a grid of like numbers for example where you could just do zero for error and one for ground or something like that that's pretty much the most primitive you can get what we'll be using is something called Json it's a file format for storing objects that basically fit into the format for how python stores dictionaries and lists and values and strings and stuff like that so it can't store things like objects or classes but if we encode it into things that can understand and that's like the dictionaries lists and stuff like that then we're able to store it and then load it back in so one of the major differences between pythons dictionaries and lists and Json is that Jason has no support for tuples and all of the keys for dictionaries Must Be Strings and if you remember back here I think I mentioned that for example for this location thing it would have been easier if we could have just taken the Tuple of X and Y because you can actually use that as a key for dictionary but for saving files you can't have that as your key because it will be interpreted as a list and then that can't be easily converted into a string and then you run all sorts of other problems so we have to sort like a string like this in higher level use cases so what I actually use is I created it my own function for saving and loading stuff like this where it's built on on Json but then I added support for tuples uh that's a bit of an extreme case but that's a direction you could take this in if you wanted to do that anyway so for what we're we'll be using here this format's fine just the string semicolon string if you're wondering where Jason comes from Json actually stands for JavaScript object notation it was originally made for JavaScript in dealing with JavaScript objects so it's naturally meant to represent objects so it works well with tile Maps if you work with actual level editors you'll find that you can actually export to Json usually either that or it will have its own custom file type and by the way while I will be making a Level Editor to show you some useful features with pi game don't feel like you have to be using your own level editor after this tutorial you could go out and get a normal level editor and then you can just write a way to interpret that in into a tile map so you would take this top map object here and you'd probably have like a load function you take whatever that level editor is outputting and you figure out how to convert it into what you need or you can build your tile map system around whatever the level editor provides but in my case I can make things work very nicely if I just write my own so to get started with our level editor we'll probably make another script here and I'm just going to go new file and I'll call it editor dot pi and now I can go over to game I'm just going to copy this whole thing and then I can go back to editor paste this in instead of calling it game I'm going to call it editor I'll go down here call this editor and then let's trim down what we're actually using here so we won't need load image we will need load images and we will need we we will need a Time map but we won't need like players or entities because this is for tiles we don't need the clouds obviously and then let's add a constant here called render scale which will determine just how much we're multiplying the size of each pixel so let's change the name here to editor and then let's go over here and let's trim down all these assets we don't need most of these we just need the ones we're making our world with uh so for this here I'm going to take this movement thing and we will be using this surprisingly because you'll want to be able to move your camera around so we'll do false false false instead of moving right and left you're going to be able to move the whole camera instead of a player in all four directions that's what we need four values here all right so let's remove some of this stuff we do need our tile map in fact because we're making a level editor so we can keep that we'll also want to keep the scroll because well we're going to be having a camera that we move around still so we need to keep that and let's go and trim down this stuff over here so we don't need a whole lot here we can just delete all that and change the background to Black and then there's a couple things right here we can use so let's remove these and let's say if event.key is pi game.k up self. movement two equals true and then we can go over here and say k down and say movement 3 equals true so now we can copy all this go over here paste and then switch these truths to falses and then this will automatically update our movement thing here so we can start moving around with that but we don't have anything to see yet so there's nothing here so let's just run this real quick and make sure that it works oh we actually don't all right it's not supposed to be bullet that's supposed to be film all right so we have our editor it's just completely blank though because well we don't have anything to show yet so the first thing we're going to need is the ability to select our tiles so normally I would create an interface if you can click the tile you want to use but because I'm trying to keep this simple I'm going to make it so you just press buttons to change which ones you're using so let's say self.tile list equals list assets so or self to assets this will convert our assets into just the list of these values because if you run list on a dictionary it just gives you the keys so it's giving me Decor grass logic or stone Etc so that'll give us the four values there and then let's do tile group or self.tile group equals zero self dot tile variant equals zero so with these two variables here we'll be saying which group are we using so we're using graphs Decor Etc and then the tile variant will be which tile in that group where we are we using so we'll have different buttons for changing between the group and the variance and that way we can rotate through our different tiles in a more efficient manner all right so let's go ahead and write something for showing which tile we have selected so in our for Loop here we will need to calculate what our current tile is so let's do current tile image equals self.assets we'll do self.tile list self.tile group so this is going to be one of the more messy indexing situations again and then self.tile variant and we'll want to copy it because we'll want to show it with an alpha so current tile image that's set Alpha 100 so what that'll do is it'll set it so that this image is partially transparent if you set it to zero it will be fully transparent if you set 255 it'll be fully opaque so we want it to be a fair bit transparent because we want to be able to see what's behind it this is supposed to just be a HUD thing so we can see what we're doing so we can do cell dot display dot blit and then we'll say current tile image and then we'll put f55 all right so if we run the editor now you can see that we have a tile up here so that's just our grass decor because coincidentally Decor is the first value of our dictionary here so with that we can now see which tile we have selected and if you're confused about this right here you can kind of just dissect it you can print out the different stages of it so you can like take this part here you can print that out you can take this part you can print it out and you can take this whole thing and print it out and see what's going on but what we're doing is we're taking from our list of tiles we're using the index that is our group so this will be an integer that represents the index and then we're selecting from our assets that group and then we're grabbing this tile variant which is another index all right so now we need to be able to switch which type we have selected and this is where I'm going to introduce something new so we have if event.type is pygame dot mouse button down if event dot button is One clicking equals true so we're doing a couple of new things here so first of all Mouse button down that's a new event type so remember we've been using key down and key up Mouse button down is when you press down on a mouse button or it's not necessarily that because it's more like when you activate a mouse button because it also can trigger on your scroll wheel which we'll get to later actually uh sorry for this cooking thing we'll need self.clicking goes through and then we can or false rather and then we'll put it through here there we go but so Mouse button one that's your left click so we'll set click and get true there now you might be thinking well if left click is mouse button one is right click Mouse button two in fact it's not you're looking for three and we'll set self.right clicking equal is true and then alter that up here you can go false all right so if right clicking is three what is two well you could look up in the pygen documentation to see but for two it's actually when you press down on your mouse wheel so there's a couple more that we're actually going to be using here uh we need to be using four and five and four is when you scroll your mouse wheel up and five is down I I think it might be the other way around I always get those mixed up but I think four is up so let's see if event dot button is for let's say tile variant self.tile variant actually let's do group here tile group equals self.tile group minus one and this is using that module trick I was talking about earlier where you can get something to Loop by using modulo and we're going to subtract our tile group here and we will be modding it by length of tile list so or self.tile list so when you reach the end of that list it'll just loop around to zero and then if you go less than zero it'll go to the end of the list so that that's convenient that way we're forcing our index to be in the bounds of what groups we can even select so now we can take this and then also go do five which I believe to be is scrolling down so we do plus one to scroll down and then mod it by the same amount and that will also Loop so now we can select which tile group we're using by using our Mouse buttons here so if I run the editor and I scroll up you can see the titles are changing in the top left so these are different groups so we have a graphs our Decor our Stone and our other decor so we can now scroll between groups we need to add something so we can scroll between variants so to do that let's go ahead and add self shift I'm going to use kind of a key combination for this and I'll say if event dot T is k l shift uh note that I'm not doing right shift here uh shift equals true you could add right shift to if you wanted but I'm not worrying about that right now so now this shift variable will toggle based on whether or not we're pressing shift so we can do over here is we can say if shift or self.shift we do this otherwise we do that and then what we can do here is we can swap the tile group with the variant so we say tile variant so if you're pressing shift we'll scroll through the variant and if we're pressing or not pressing shift it'll scroll through the group so we do this but the one thing we do have to change here is that the thing we're modding by can't be the title list again it needs to be the number of variants that we have instead of the number of groups that we have so to get that it's kind of based on this thing up here so we just do the length of self.assets self.tile list self.tile group and then that will give us the number of variants all right so we can take these and if we run it again I can scroll as I did before and go through the different variants but if I go to like say grass here and I press shift and start scrolling down you'll see all the or sorry the this this scrolling without shift goes to the different groups and then if I press shift it'll go through the different variants within the group uh something I know just honestly forgot though is that you want to make sure that if you change the group you set the variant to zero otherwise you could run into some index errors so we'll want to do zero here and it makes sense if you change which group you're looking at it makes sense to go to the first item in that group so if I go to graphs here go to a variant down here and then I go to the next thing it'll go to the very first variant of that group all right so we now have our tile selection working so now is the fun part we need to be able to place stuff so if you notice I already wrote some stuff for clicking we do need to do a couple things to finish this off we need if event.type is pygame.mouse button up and we can do if event button is one self.clicking me equals false if event dot button is three self dot right clicking equals false so now these clicking variables will be updated based on whatever our Mouse state is so let's go over here and this is where we need to start rendering our tiles and doing all that magic so let's start by rendering our tile map which we can do right over here we can say self.tilemap.render self.display and then we can give it our offset which uh we need to set up this render scroll thing again if you remember we need to convert our scroll here into an integer thing so we can do render scroll equals the int of scroll or self.scroll zero and then int self. scroll one all right so we have this render scroll thing which is the truncated version of our scroll we throw it in as our offset there and our title map can render you can run this and actually in fact I forgot about something we have this code right here that automatically populates our tile map which is fine so we can see that there is something there now but we want to be able to place tiles so to do that let's go down to here and let's say Mouse position equals pygame.mouse.getposition so this is a new thing as well so this will give you the pixel coordinates of your mouse with respect to the window so if your mouse is in the top left of the window it'll give you zero zero so what you can do with this is you can use this to figure out where your tiles should go when you you're clicking something you do need to be careful about is that we're actually scaling our image up here so that where our pixels are actually two pixels now so we actually have to scale down our Mouse position to get the correct coordinates so we can do M position equals and position zero divided by render scale and that's why I wrote this constant beforehand and then we do and position one divided by random or scale so that's what that constant or up here is for that's so that we can divide right here and get the correct Mouse coordinates all right so with that we can say tile position equals so if this is going to be what our Mouse's position is in terms of the tile coordinates so we've done this before but with other things so we want to say int of the mouse position plus the scroll and now we can divide by the tile Map size and then actually these need to be self again then we can do mouse position one plus self.scroll1 divided by self.tilemap.tile size and that will give us the coordinates of our Mouse in terms of the tile system so with this we can say if self.clicking self.tilemap dot tile map and then this is where we're assigning our new tile here we can go string of towel position zero plus and this is the weird strength and we have to do a towel position one equals type and we'll want to say self.tile list and then self.tile group so this is converting our index selection into the string name for the group and then we need to take our variant which will be the self dot tile variant fortunately for us this one is just an integer um the way that we store our tiles so that makes things easier and then for the position we can do the tile position all right so with that we should be able to place tiles now so here I've got my grass decor thing and if I switch over to actual grass then I can place that by left clicking and I can place rocks and stone and I can change the variance so that's all nice and all but we need to be able to delete tiles as well so for that we can do if self dot right clicking we'll do tile location equals and this is just a string lookup so don't confuse it with this I use position and location interchangeably but uh they do mean separate things here not because of the name of the the variables but I'm using them for something different so we do top position zero and then string tile position one if tile location in tile map.tile map or self.telmap.hama that means that this location that we're hovering exists or the place that we're right clicking exists and if it does exist then we can do Del self.tilemap tile location so with that I can run the editor and I can oh it crashes and I need one more tile map there because we need the tile map attribute of the tile map object so uh now if I right click we can delete all these tiles are already there and swallow over at it since we can now Place tiles let's go ahead and remove this code here that adds in tiles by default all right so with that removed we can go back to our editor here and if we run it our pre-placed tiles are no longer there so there's a couple extra things we need to able to do so first of all it would be nice if we could see kind of roughly where the next tile would be placed so to do that we can say right here self.display.blit and then we'll do current tile image and we want to show it at the tile position 0 by tile map.tile size or self again minus self.scroll0 self.tile position one by self.tilemap.tile size minus self dot scroll one all right so what that's doing is it's taking the or actually that doesn't need to be self it's taking the top position we just calculated here and then converting it back into pixel coordinates by multiplying it by the tile size and then we're adjusting that position based on the camera for rendering because if you remember we added scroll right here so we need to remove it right here because we're going to show an overlay of where the next level is going to go all right so if you're confused about why we're going through all this extra effort instead of using the mouse position to show this indicator it's because we want it to be aligned to the grid and the part that does the lining is right here on this division that will force it to be aligned and then when we scale it back up into pixels and then it will be snapped to the grid which is convenient all right so with this if we run this we can see now we can see where our next tile is going which is very nice so if I go right here I can go down into a tree and you can see look at that I can see where my trees are going before I even place them so now we we did all the stuff with scroll but it would be nice if we could move our camera so to do that we just used some of the simple tricks we had before we can say self dot scroll 0 plus equals self dot movement one minus self. movement zero and we'll multiply by two just to make it move faster and then we'll do three and two for here for the y-axis if you remember this is if you're holding down right minus if you're holding down left and this is down and up so all this adds up to figure out how much you should be moving your camera by here and then we just multiply it by two so that will move our camera base on our keyboard input here so if I do you can actually see it without me even placing anything because the Grid's moving around um I can move around and things are moving which is nice so if I draw these things I can move around and look at things which is convenient all right so the next thing I'll want to do here is probably it would be nice if instead of using these we could use ws and D because we're drawing with our Mouse so let's do k a because that's left k d k w k s and then we'll have k a again k d k w k s so now we can move around with w a s and D which is much nicer all right so now that we can move our camera around and we can place tiles the next thing is to be able to place things that are not on the grid because if you remember from the beginning of the tutorial from what I showed you for what we're making there were a lot of tiles that were not on the grid and we did all that code that I mentioned that we weren't using quite yet for rendering tiles that aren't on the grid so let's add a variable here let's call it self.on grid equals true and then let's go over to here and let's say if event.key is pygame dot k g and then that will be not self.on grid so what we're doing here is we're making it so that this variable here represents whether or not we want to be drawing tiles on the grid or not and then if you press a g it'll switch it so if you just set something or a Boolean to the not of itself that just flips It Whatever It Is so we can now start writing some rules for how things will be placed depending on whether or not we're on the grid so the first thing we've got to do is update our indicator because our indicator assumes we're snapping to the grid which only happens if you're rendering on the grid so if self.on grid we do this but if we're not on the grid we do and this is the simpler version that you might have thought of at first self.display.blit and then we take the current title image and we just throw in the mouse position and then we don't have to do all this crazy math to figure out what the snap grid position should be all right so if we run this now I could just play some stuff and then let's pull out the tree I guess so you can see the tree snapping around if I press G it becomes smooth but it's still placing on the grid we need to make it so that we can place off the grid so for that we need to modify this code right here so we do if clicking and in fact we need to do if clicking and self.on grid we do this now we don't want to place a tile every single frame off grid it would be just because we're clicking because there's no grid value that we're over writing instead it would just place 64 tiles per second or not 64 60 tiles per second running at 60 FPS down here so we don't want that we want it to be because off-grid stuff is not snapped to anything and we're not overriding anything we want it to be able to just place a tile when we click and just press down that button we don't want it to be placing non-stop so you would click for each tile you want to place it's just the easier way to do things so for that we don't need an extra variable or something to keep track of that input we can just go right here in here to the clicking thing per Mouse down on one and we can write it here for placing so we can say if not self.on grid we can say self.tilemap dot off-grid tiles and that that's that list we had before the append and we'll say type is tile list and this is pretty much the same stuff from before so self.tile list self.tile group and then we want the variant which we know is just the self.tile variant and then the position will be just the mouse position zero plus the scroll self does scroll zero Mouse position one plus self.scroll one and if you're wondering why we're adding the scroll to our Mouse here and we did a similar thing with placing stuff on the grid as well the reason is that from the camera's perspective the top left is zero zero but in the world's perspective let's say that your camera's 100 pixels to the right and you place it at the coordinate 0 0 in your window well the coordinates in the world for what zero zero on your window is becomes whatever the coordinates for the camera were so we actually have to add the coordinates of the camera to convert from the display space so where you're clicking on the window back into the world space so if you remember from the tile rendering it's actually the other way around where you have to convert the world space into Windows Space by subtracting the scroll so it's just doing the same thing and just flipping it so we should now be able to place our tiles that are off-grid so if I press G I can left click and I can play stuff that's not on the grid so if I draw these right here you can see those flowers were not aligned which is nice but now we can remove all of those flowers that are on the grid but we can't remove the ones that are off the grid so we need to be able to add that too and for that this is where things get a little bit messy this is the unoptimized way to do it but for level editor it's probably not going to be a huge issue if you're lagging a little bit when using a level editor it's not a huge huge deal uh you should be more concerned if your players lagging from playing the game so we can do some bad code in here so we'll say for a tile in self.tilemap.off Grid tiles.copy and the reason we're copying here is because we're going to delete if this tile is touching our Mouse so we don't want to mess up our iteration so we just take a copy of the list so that we don't get the reference so we can calculate our image for our tile here we can do tile image equals assets self dot tile type tile variant so this is like when we're rendering the tiles so we need this image to be able to compute what the bounding box or the hitbox of this off-grid tile should be because the trees are a lot bigger than the normal tiles so we want to take the image to figure out how big of a hitbox it should have for deletion so we can now calculate that hitbox we can do tile R equals Pi game dot erect and we'll say tile position 0 minus scroll or self dot scroll zero and remember this is because we're converting our tiles or World space into display space because we're about to try to collide it with the mouse so we can do tile position one and you could actually do this the other way around where instead of subtracting the scroll here you could actually add it to the mouse's position to do these collisions but we can do it either way and I'm just choosing to do it this way so we'll get the width of our image and that will be the width of our rectangle here tile image that get height I'm going to do it for the height and now we have all of our four parameters for our rect and we can do if tile r dot Collide point I don't believe I've shown you this yet so with Rex you can quad with other wrecks or you can collide with a point so instead of passing an erect now we could just pass in a point and it'll tell us if it's colliding so in this case we'll just throw in our mouths position and say is this tile colliding with our Mouse so we can say if it is well we can do self.tilemap.tile or action off grid tiles that remove and then we want to remove this current tile so with that we should be able to remove our off-grade towels now so here's some on grid ones and then let's play some off-grid ones so let's do that so we can remove the on grid ones and the off-grid ones now which is nice so that's pretty much the basic functionality for a level editor the next step is the thing I was talking about at the beginning and that's the loading and saving part so for this we'll actually have to go back to our tile map code here and we have to add some new functions so we need a load function and a save function let's do save first because you can't load anything until we've actually saved something that can load so let's do save we'll take self and we'll take a path to save to and then we just do f equals open path W so this is just normal file handling in Python if you haven't done it before I'm going to import a new library up here we're going to do import Json so this is the format I was talking about before so fortunately in Python there's a module that makes this really easy to work with so all we have to do and this is super simple Jason dot dump and then we want to throw in a couple things so we'll say the tile map and this is like a dictionary remember so we need to split up our information here so we have a tile map which is all this tile data on the grid so we'll do self.tile map and then we'll just tile the size which is the size of the the grid and then off grid so this is all the off-grid tiles so self.off grid tiles and then this dump function as you can actually see here um vs guy is telling me it takes the object we want to dump and then the file pointer so that will be just F so we told python open this file and then we're telling it to dump this object right here into this file and it will convert it into Json for us now if we do F Dot close it'll save it so if I go over to my editor here and let's say create a new key binding let's do event.key is pygame dot k and I really like to use o for this because I think o is output we are already using S for moving the camera down and I don't want to do like control s because then I have to go add the control key so I like o it's also far off to the right so you're not going to accidentally press it so self.tilemap.save and let's just call it map.json remember the parameter we give it is the path that we're saving to so now if I run the editor and let's just place a basic setup here so let's just Place some tiles do this so we place some tiles and let's do some off-grid ones too so what let's find our tree and we'll place it off grid look at that got some trees and then we can go and place some of this uh these flowers here so if I press o you can see it over here on the left that I had a file pop up and this is the file we just saved so this is kind of hard to read like this uh actually don't know how to turn on the the wrapping on vs code presumably there is a way to make this wrap but we have all this data here so we have tile map remember that's where we dumped our towels and you can see this is the key this is the type the grass variant position or variant position and it just looks like it's just a python dictionary and list like I always mentioning so it's pretty it's almost one to one like I said it gets rid of the tuples so these were tuples for us before I believe but they've been converted into the list but aside from that it's pretty much one to one and you can see over here and this is the off-grid stuff you can see I play stuff at half pixels that could cause problems I don't I think we should be fine because of the way we're converting our scroll to integers though so you might want to be careful with that and like truncate your positions for the off-road stuff if you're doing certain types of operations it's just something to think about if you start running to issues with like collisions or something or jitteriness in rendering in our case I'm pretty sure it's fun all right so now that we've saved our map it would be nice if we could load it I won't create a low key by name because if you create a low T binding then the expectation is that you can browse files and select something I actually have that in my level editors that I actually use but it's not super important you could implement it with kinter and that's how I do it but I I don't really want to get into that because that's not Pi game that's something else so you would hook that up with pi game to do the file loading but for our case we'll just assume that we're loading from a certain path so the way we'll do this is we'll go over to here and we'll say self.tilemap dot load and we'll have to fill this function later and we'll say map.json so if that file exists it'll load it so the next thing we'll have to do is say try except file not found error pass so we only want to load the map if it exists and in our case it does exist but we need to create this load function now so here's the other end of the nice stuff with Json def load self path and this is why I put all that effort into making sure that the data structure was easy to fit into this form because this makes the saving loading so much easier we can do f equals open path and instead of writing we're going to read now and then Jason dot load and you just pass in the file pointer and then we can say our map data is equal to that and then we just close our file oops now with our map data we can start assigning things we do self.tile map equals map data and this will come in basically as you see it right here so it'll be like a dictionary with tile map and everything so we just do towel map equals map data power map and then we just go through here and we do tile size and then we do off-grid and then that will be off-grid tiles and now if I run the tile map or not the tile map the editor we see our thing is loaded about again that we saved before if I delete this file here and run it again we get an empty thing to work with so that's convenient but you might be thinking okay we have this nice level editor and everything but it's really tedious to take something like this and deal with all the different tile types you have to think about so you have to go through it here and switch your variants and do all that stuff and then you go like this or and just keep going like that and you have to go through all these different tiles and eventually you get most of what you're looking for but uh it's very tedious to go through here and do this all properly so instead of doing that I'm going to implement something called Auto tiling where it can take all of the tiles that we have and figure out what variant each tile should become so this is just an algorithm that we apply to all of our tiles where it looks at all the neighbors and figures out what it should be another feature that I would normally add but I don't want to get into because it doesn't really benefit you much to learn about it here and it's a bit tedious to do flood filling that's something that I have in all my little level editors but fortunately in our case we won't have to deal with too large of areas we're mostly doing with floating islands as you'll see in the gape later so it's flood filling is not super important Auto tallying however is fun facts my first steam game super potato bro I made without Auto tiling I actually did have to go through all the different variants by hand and place them where they should go it's super tedious all right so let's go over to tile map here and that's actually something interesting to note is that my game super potato bro was I guess technologically inferior and in many aspects to this tutorial so what you're learning from this tutorial is past what a decently performing game on Steam had although that was in 2018 so five years ago so in here on our tile map we'll want to create our Auto tell function so we don't have any parameters for it we just call it a function and it should Auto tile everything so let's just do def Auto tile self and just iterate through all fertiles we don't want to do the off-grid stuff because I mean how can you figure out the neighbors for an off-grid tile but for the stuff on the grid you can look at the actual proper neighbors so we do four location in self.tile map tile equals self.tell map location so that's just the location string and then we're using that to get the value and then we can get neighbors equals set so what we're going to do here is we're going to look through the different neighbors so let's do four shift in these are different offsets we're looking at so one zero that's one tile to the right negative one zero that's one tile to the left uh we'll do zero negative one that's the top above and the tile below uh so for that we can now create a location that we're looking up so we can do check location equals the string of the original tiles position on the x-axis plus shift so what neighboring tile we're looking at and then we can do plus the string of that same tile again and we just do the y-axis plus shift right this should be shift zero and shift one all right with that we now have our check location we're going to do if check location in self.tile map and then we can now do stuff here because right when this pass is reached that means that that neighbor exists so it's time that we go and Define what exactly we want to do with auto tiling oh wait we actually have neighbor offsets here but we we're using a different set of neighbors uh because this one is for the tile equations where you need the diagonals as well as just the actual Edge bordering ones we don't need the diagonals for auto telling you could do auto tiling the diagonals and add extra cases for that but our tile set doesn't do diagonals so we don't have to do that anyways so we want to specify which tiles we want to Auto tell so we'll say Auto tile types equals and we'll do graphs in stone funny enough it's the exact same thing as the physics cells but if you want to separate them you could all right and the other thing we need is an auto tile map so this is going to be the rules that we use for auto towing so this is going to be a little bit funky uh we want to say that if we have certain neighbors then we should have a certain variant of the title set being used so let's say for example Tau variant zero if you remember that's the top left tile in the tile set because it was the very first one selected in the editor when you go to that group so it's the top left one so the top left should be placed if there's a tile to your right and a tile below so for example if you want that um that would be the tuples of one zero and zero one so that would be to your right and Below you so if these are your neighbors then we would want to say use tile zero so that's how this Auto telling thing works you could add extra rules as well so you can add uh like I mentioned the diagonals you could add that to your offsets down here and you can add rules over here so it's like if you have certain diagonals you follow these specific rules so you can go crazy with this but we just need some simple stuff now something you have to be careful about is that the way we're going to be doing this is we'll take the list here of these two things and because you can't use the list as a key well you could do a Tuggle but the problem with using a tuple like this is that what if for example your neighbors are these two but it comes in in a different order if you do this then that's not a value in the this map uh so well this order will depend on what order you go through your neighbors right here but the hacky way to make sure that you get the order correct is to just run a sort on it so it'll come out in the same order every time and that way you can actually look up the neighbors properly so what we'll do here is we'll take this as a list and then we'll run sorted on the list so that way this list will be the same order no matter if you write it as this or if you write it like that so it will convert multiple different Arrangements down into the same key but you can't use a list as a key so we have to make this into a tuple so that's a bit of a hack but it works so there's a few or several other rules we have to do here so we have to do we'll go one at a time so I'll just paste a bunch actually I won't paste them all because it's easier to do them as neighbors sorry so we'll do this one so that means you have a tile on your right below and to the left this is tile number one if you want to look through our images here you can see how these are arranged so we have grass and you can go at top left middle top right right bottom right bottom bottom left left and then middle so that's how that goes and we just have to go through and write down all the rules here so I'm gonna go through this really quickly uh you can figure out just by looking at these numbers what they should be but it's not super important and it won't be learning anything new so I'm just gonna do this real quick all right so I wrote all these rules here and this should be the rules for mapping out our Auto tiling and we can go back down to here and finish our Auto telling function all right so if the location is in our tile map then let's say if self.tile map check location type is tile type so we want to make sure that the neighboring tile is the same type as the tile itself or rather the same group or whatever so we we don't worry about if it's a different variant but if it's a different type we don't want to be Auto telling with it on the border so like if you have a piece of grass and stone next to each other you don't want them to kind of join in on the middle it doesn't make much sense visually they should have a border with each other all right so if it meets those conditions we can say neighbors dot add and then we'll add in shift so that's the neighbor rule that we had and then we can convert this neighbors equals Tuple sorted and that's the thing we're doing before so it comes out in the same order as that set we had before and then now we can say if tile type in Auto tile types and Neighbors in Auto tile map then we have a match and we can do the variant equals the variant for that rule which is auto tile map and nut type that's going to be neighbor and the neighbors all right so that's what we need for auto telling so now we just have to go back to editor and bind it to a key so let's just go ahead and I'll have to use the key t for this event.key is pygame dot KT so we do self that's how map.autotile and then if we run the editor hopefully this works first try we just place all these tiles right here and then if I press T it looks like it works so converted all of those massive massive tiles into what we needed so something to note is that if you for example do this if you remember we don't have a rule or even a tile in our tile set that can represent this so you have to be careful about your shapes if I press T here there's no rule that meets that requirement but if I add in a second strip there it does it exist as a rule so you have to be careful about what types of things you're drawing with unless you actually add those extra tile types but we don't have them our tile sets are just the nine tiles that form this shape so we can't do anything where it's like one tile sticking out like that or something so in fact even just a tile by itself we can't do that either so you have to be careful about those things that's just something you have to do in your level editor if you want to do a more World Generation thing and then Auto tell that and you end up with stray tiles then you'll probably want to update your tile set to support that and then you'll change your auto titling rules but for our needs this does what we need we can create something like this and then the graph shows up nicely all right so I'll make an example level here I actually have pre-made Maps so that's what these are you can look in here and oh why does vs code show this one properly but not the others that's funny so this one you can see look look at all this data that's that's a map but I'm going to demonstrate how you can do it first and then I'll use those pre-made ones because uh you're not going to learn a whole lot from me just sitting here using the level editor for an hour so what let's just make a basic one just to show you we can go create this we'll do that and actually we do need this now that I think about it because there's extra stuff in the tile map that I haven't introduced yet so we'll have to get to that later but that that's for the enemies and stuff we'll get to that in a bit so we can just create a bit basic map we can do oh I don't want that so I can like place the different types of flowers as well and then I can go over to the stone and we can make that and we'll say that and I can auto title those and then let's put in some off-grid tiles you can put in some bushes and some trees and then I believe there's where's the crate oh it's over here isn't it yeah here's the crate so I'll put down some crates as well and let's put down one more rock so that rock will go there and then if I save that I can do that and then I can go back to game and for now I'll actually load the map from here instead of using the maps folder that has those actual Maps because uh we're not ready for that yet uh so let's do self.tilemap.load and we'll just say map.json so that'll load whatever we just exported so if we run the game we now actually have a nice area to run around it and it has trees and rocks and flowers and different types of decorations so we have these nice little boxes and these islands to run around a whole lot nicer than those two slabs of land that we had before so the game's starting to really come together here and while that was quite of a detour to do level editing there's lots of useful Concepts that you're able to learn from that which is why I decided to cover that but also now that you have your own level editor made you can add your own custom features so it's fit your needs better than a standard level editor might but remember feel free to use a normal level editor if you wish as well just bear in mind what the requirements are for how you load different files and stuff like that into a game like this things are made a lot easier for us because I designed the game around how the level editor would work and then I had both of those in mind when I really did everything here so you can make things a lot easier for yourself by using something of your own but some of the pre-built stuff is pretty good as well so we've got two good looking trees right here and it would be really nice if they had some animated particles for leaves that would fall from them and move around it's about time for us to get into some actually nice looking visual effects for the game and to do this we'll be using particles as I've mentioned oftentimes particles refer to just still images that'll float around usually pretty small ones at that but in our case what we'll be doing is using animated particles where it's playing animation and then once the animation is complete they disappear so they can be used for more than just like tiny little leaves that are fly floating around and stuff we can use them for just playing animations if we want in a specific location so the first thing we'll need if we want to have leaves falling from our trees is to know where to spawn these particles from so to do this we need to figure out where our trees are and then kind of create a hitbox to spawn our leaves from and as always with pi game you're pretty much doing this all yourself so we'll have to create all the code for generating that area and figuring out which locations can spawn leaves based on the areas that we have and stuff like that so let's get into it so in order to get the position of our trees I think it's best to go to the tile map and we'll add a new function here that can be used for a lot of things so let's call our function extract self it'll take the argument ID Pairs and then keep equals false so what this function will do is it will take a list of ID pairs so this is if you remember our tiles are in a like tile type format along with a variance let's type in variant and those go together to uniquely identify the tile type so that's what I'm referring to when I say ID pairs more specifically an ID pair is one of those two combined now the ID pairs is a list of those and we'll check if a tile is in that list and if it is then we can return it so what this function will do is it will take a bunch of types of tiles that we're looking for and tell us where they are and give us information about them in our map and then if we want is what this keep variable is for we can have them removed from the map so we're not going to need that yet because we do want to leave the trees hanging around in the mountain but later when we do some other stuff we might want to get rid of things for example if it's an entity spawn location we want to might want to remove that from the map and then create an entity all right so to do this let's create a list and call it matches and then we can iterate over our tiles in our off code tiles let's do this one first because it's easier and then the reason we're copying here is because we might want to delete from this list if we're not keeping so we'll do if and we'll mix the Tuple here for the ID pair tile type tile variant in ID pairs that means that we've found one of the ID pairs that we're looking for then we'll do matches dot append and then we'll say tile.copy so just give it the tile information all right now if we're not keeping our tile then we want to remove it from our off-grid tiles so that's pretty much the logic for that just look for the match add it to the results remove it if we don't want to keep it and then we gotta do the same thing for the tile map so we'll go through the locations and we say tile equals self to tile map location and then we do the match so tell type tile variant in ID pairs matches stop and and then just another tile.copy and then this is a little bit different we'll have to do matches negative one position equals matches negative one position dot copy so what we're doing here is we're going to change this position attribute or not attribute it's actually a key value pair in the tile dictionary but we're changing the position for the tile that we're referencing because we want it to be in pixels if you remember the tile map is in tile coordinates for the grid not in pixel so we want to convert that so to do that we'll do matches negative one position zero multi apply and we do self ductile size all right and then we do that for the y-axis as well so that gives us the pixel coordinates for position the reason why we do copy here is the same as up here we want to have a copy of the tile we don't want to be working with the exact thing the same thing and then over here we copy again because copy is just surface level if there's a list nested inside of something it won't actually copy that it'll have the same reference so this is just making sure it's clean there's a function called Deep copy that we could have used that will do basically these two things in one line but I don't want to really get into that so basically we're just making a clean copy of the tile data because what would happen if we didn't is if we modified that tile data after getting the results it would modify the actual tile in the tile map when we don't want that and then like I said we're converting it to pixels so now we can do the if not keep we do dell self.tile map location and then we can return our matches so now that we have our function here we can grab this and we can go over to the game and then we can say print self.tilemap Dot and then we extract and then for our ID pairs we'll use a list with just a single pair of large Decor two so if we wanted to get another type of Decor we could just do like this and say one but this will work for now so we say larger core two and then we'll just match with this one tile and then it'll give us all those results so we want keep to be true because we don't want to remove the trees and then that will give us the results of all of our matches here so I run that and then we can look down here and we see type large Decor variant two and then here's the position we're looking for so it's got all this useful data and then from this we can figure out where we can spawn our leaves so I'm going to use a bit of a hack here there are better ways to do this that are more General but for the purpose of this project I'm going to keep it simple and just kind of hard code a hitbox based on the location because we only have one type of tile that can spawn leaves so let's do something where we'd have self.leaf spawners equals that and then we can do four tree and this will be the tiles that we were returning before we can do self.leafspawners.append and then for our hitbox we'll do a pi game direct because if it's a rectangle probably game.rect has a ton of useful functionality for that type of thing so for that we'll say four plus the trees position on the x-axis and then four plus the trees position on the y-axis 23 and 13. so you can go look at the dimensions of the tree image if you want this is just taking the position of the tile and just kind of looking for the area of the tree image then it makes sense to spawn leaves so we're actually offsetting it by four from the top left to the right and then same thing down from the top left because we don't want to start right in the top left because that's technically air so this is a just the rectangle makes sense for that image you can go look at it and just draw it out if you wanted um so we can now print out this list self.leaf spawners and remember the rect is X Y width height anyways so we can print out our least spawners and close again and we have our two Rex here representing our two trees since we have our rats where the leaves can spawn now we could theoretically easily pick locations to spawn the leaves but now we need to be able to actually have the particles that we're spawning so to do that let's go ahead and make a new script we'll call it particle dot pi and this is a pretty basic one so we'll do class particle def in it and then it needs access to the game we can do self.game equals game and then we want the particle type we went to position we want the velocity we'll default that to zero zero we want the frame that we wanted to start on all right so we can do self.type equals P type self.position equals list position and remember that was just making a copy all right so and then we're going to solve that velocity equals list velocity and then self.animation equals self game dot assets and then we'll take particle and we'll have to load this in in a moment and then we'll take the particle type and then we want a copy of that animation and then solve the animation.frame equals frame all right so that's the setup for our particle because the particle needs to be able to move around and it needs to have a velocity so we can configure how it moves uh it has the animation and that's really all you need well also the type obviously but that's really all you need uh now we can just do def update self self.position zero plus equals the velocity this is just the basic stuff so we do the y-axis too and then we need to do self.animation.update and we'll do kill equals false if self the animation dot not frame done kill equals true so our update function is going to do a couple things here we'll try and kill it needs to determine when the particle needs to disappear so that's what this kill variable is for as soon as this update function returns true that means that this particle needs to be removed so we return true once the animation has finished playing which was what we added before this is a nice little utility attribute that we have for that and we also have to update the animation in our update function and move the particle so it does a couple things if we wanted we could add more Behavior here as well but it should be enough for now and it's really important to remember the type of thing you're pulling from the assets here uh like for example if this were a list of images you'd probably have a problem but we will be adding this in as an animation like we did for the player stuff all right so now we need one more function for a particle we'll call it the render function it needs a destination surface and an offset and we'll take zero zero and we'll take image equals self.animation dot image and we just take that and we go surf.blit we render our image onto the destination surf at the position we have minus offset zero and then this is something actually we're doing I like to Center my particles based on the center of the image instead of the top left it just makes the behavior easier to work with and more aesthetically pleasing so let's do minus image dot get width and then we divide that by two now we can do a self.position one and just do important thing for the y-axis all right so now we have the rendering coordinates for the X and the y-axis and that should be sufficient for our rendering function all right so now we need to actually add our particle assets and for that we can go back to our assets thing right here and say particle slash leaf and that's the particle type we'll be using if you remember we said particle slash and then the type and we'll you'd be using Leaf here so we'll create an animation of load images and then we'll do entities particle leaf I believe uh let's check that so we go data images we have particles so this will be particles slash Leaf so we have two types in here we'll be using just leave for now and here there's 17 images if you look in here there's like different leaf images normally in my games I would have the ability to recolor these but right now we'll just be playing the animation and that's it so it'll play through those Leaf frames which will load here and then attach to the particle uh and then we'll have our particle as we went in so now that we have the particle object I think it's time that we add our particle like collection so we can say self. particles equals that list and if you remember we had that return kill thing in the particle code so in our Loop here we need to keep track of that and see if we need to remove it so we'll do four particle in particles dot copy and again we're doing the copy thing itself we're doing the copy thing because we will be removing during iteration so kill equals particle dot update so we're calling the particle to update and then we're just checking if we need to kill it so we can do if kill self.particles dot remove particle and that will handle that uh we also want to render our particle so particle.render and then let's do self.d display offset equals render scroll all right so we now have our collection of particles and it should remove them as soon as the animation is played through so now we just have to spawn our particles so for that let's look through our least spawner so for erect in self.ly spawners if random not random and this is going to be just a mess of numbers here uh four nine nine nine nine is less than rect dot width by rect dot height I'm going to go import random here so we don't have that uh import random and then we also want our particles so from scripts dot particle import particle so if you're unaware random.random is just a random number between zero and one it's a floating Point number so it could be like 0.25 or something and then we're just multiplying it by this and then checking it to see if it's less than the pixel area of our rectangle so this is a way where you could have the spawn rate for leaves be proportional to the amount of pixels that that hitbox covers so that if you have like a bigger tree it will spawn more leaves and then this number right here is just to control the rate at which they spawn because if you do this it will spawn 100 of the time like every single frame so by multiplying it by that big number it'll make it so it's not every frame anyways if we've decided that this random odd chance has passed then we can say our spawn position equals erect dot X Plus random.random by rec.width and rect.y plus random by a DOT random by Red Dot height and this is really one of the more interesting problems I think in this tutorial it's just the spawning leaves problem so there's a lot of random techniques you can pick up from here in different ways of solving problems of course this doesn't generalize to that many different things so there's just little things you could start thinking about here and there about how you would approach problems anyways so we're taking the x of our rect and then adding the width multiplied by a random number from zero to one times that so that will give of us any number that's within the bounds of the rect because 1 times the width plus the the left Position will be the right position and the zero is the left so anything in between is somewhere inside and it's uh linearly distributed so we can do self.particles.pend and this is where we spawner particles we do particle and we do self for the pass along the game we want the leaf particle and then we'll give it our position so that will be position that which we just calculated we'll do for velocity all the leaves will have the same velocity you could randomize this if you wanted if you want variance but I'm going to leave that negative 1.3 or negative 0.1 and 0.3 that will mean it's going slightly to the left but mostly down but also very slowly for the frame to start on we'll say a random.rand in if you don't know this is a random number within I think it's inclusive uh of the two numbers we give it so we'll give it 0 to 20. so that will select just a random frame for it to start on so that way we don't always start with the biggest leaves anyways so that will spawn our particles and then this will manage our particles so if we did everything right it should work now aha so you can see there are leaves falling now they're a little bit rare but as a visual effects they should be oh I do see we have a problem though so first of all our leaves are kind of decaying into the one pixel dot very quickly and then second of all the animation is looping and they're not disappearing so to do deal with that we need to go over to our animation here and if you remember we have a couple of options here for animation uh so first of all we need to make them Decay slower so we'll do image duration equals 20. so 20 frames for each frame of the animation it's confusing terminology but whatever and then we want to disable the looping so we'll do Loop equals false so we run that again and we have leaves that already came much slower it looks much nicer and we're looking at this one on the bottom left and the one it just disappeared so our leaves are disappearing that's good and they're decaying slowly that's good so looks very nice but there's one more effect I want to go ahead and add over here so if we go over here into the particle management we can add some extra rules for our particle we already have our update function but we could do something else if we wanted so we can say if particle.type is leaf then we can say particle dot position zero plus equals math DOT sign particle dot animation dot frame by 0.035 by 0 0.3 all right so we do need math that's the math library that comes with python and that's just giving the sine function to us so if you don't know what the sine function does just think of a sine wave so you know the curvy wave that goes up and down you can just look up a picture if you want and we're passing in just the animations frame so like the progress that we have in our animation so if you know what the sine function does it will give you a number between negative one and one and it will smoothly go between the two if you were to plot it out uh it would be a kind of a bit of a smooth curve so it has a more natural pattern than just moving to the right at one pixel per second and moving to the left at one pixel per second it will slow down as it gets to the edges so what we're doing here is we're applying this random number that could be negative one or one or multiplying it by 0.3 to decrease the force of it but we're applying that to the position and because it's a number between negative one and one it actually sums up to zero over time so what this is doing in the short term is making the particle move back and forth like right and left over time in just a smooth pattern so this last part here the point 0.035 thing is just to make sure that you don't go through the a whole kind of loop of the sine function too fast it's just to slow down how fast it goes left and right and if you aren't aware there's no limit to how big of a number you can put into the sine function it will always give you a number between negative one and one so we don't have to worry about how far along that is anyways if we were to run it again we could see that our particles will move right and left now so if you watch the sparkle closely it'll walks way back and forth right and left so this is a technique I use for a lot of things if you want natural animations for a variety of things the sine wave is a really good way to get a natural look you can layer it with some other stuff because uh technically it's a little bit too mathematically perfect but it's more natural than the robotic uh multiplying well adding negative one or one technique this is a lot smoother but that's just a useful thing to keep in your toolbox I recommend getting very comfortable with this function if you just play around with it you can get an idea of what it returns for different values and you can get used to using it for things of course it has tons of other applications in math as well if you've taken trigonometry but I won't get into that too much so if we go back over here to the input code and take a look at our up Arrow key we will see that we left it as you press up the velocity of the player is just set to a negative number so the effect of that is that if we run the game you can just press up as much as you want and jump as much as you want so normally in a game you wouldn't want this maybe if you're playing Kirby but for the most part you don't want to be able to just jump to Infinity so what we'll be doing is we'll be adding some restrictions on jumping and also while we're at it we'll add the wall slide effect so you can slide down a wall and then jump off of it alright so let's get started with the easier part which is the jumping aspect so what I'm going to do is I'm going to go right here in this key up part and just Swap this velocity change with player dot jump and then we'll fill out this jump function so that it handles the jump when the buttons pressed so we can just go over to a player here and we can add a new function we'll go def jump self we don't need any arguments all right password now and we need a couple of things added to the player to keep track of jumping so we need self-tight jumps equals one the way I'll be doing this is that the player will have one jump and a wall jump will consume that one jump but if you've run out of jumps and you're on a wall you can still wall jump so you can jump and then jump off of a wall but if you fall and jump off a wall you can't jump again that that's just my preference for how this is implemented here you could come up with all sorts of rules for how you want the jumping limits to work if you want you could just increase the maximum number of jumps to like three or something so you can jump in the air three times you can do all sorts of fun stuff and I recommend you do play around with that if you want to get a good understanding of how this works anyways so let's go over to this jump function we'll need to keep track of this jumps attribute to determine whether or not jumps can be made so let's do if self diet jumps self tight jumps minus equals one self.velocity one equals negative three and self dot error time equals five so we're doing a couple things here first or reducing the jump count so if our jump starts at 1 and we jump and reduce the number of jumps by one it'll go down to zero and this statement will fail the next time you try to jump because if jumps to zero that evaluates to false so you don't have any jumps left and you can't jump anyways but it'll also modify the velocity same as before negative three and then while we're at it we'll set the air time to five that's just to force it to automatically transition to the jump animation because once the air time is above four it'll transition otherwise you would have to wait like four frames so you get a little bit of the running or idle animation before it actually switches to jump in the air unless you have this line anyways the next part is to actually restore your jumps after you've hit the ground which is usually when what you want to do but I mean you could make a mechanic where you have to maybe collect something in order to restore your jumps and your jumps are a limited resource but in our case and and with most games you'll want to make it when you touch the ground you restore your jumps so we'll do right here when you collide with the floor going downwards you can do self.jumps equals one and if you wanted to have uh two jumps you could just swap these two numbers for two and that would make it so that you always have two gems after each touch with the ground all right so that's the basics of keeping track of your jumps if we go over here to the game and if I press up twice you'll see that I can only jump once if I press the key loud enough you might be able to hear it but yeah well now if we go back over to here we can go ahead and start working on the wall slide so to do this we'll add a attribute for not well wall slide and set it to false now this is where things get a little bit messy because not only do we have to do with the actual movement mechanics of dealing with a wall slide and the jumping resource management of a wall slide but we also have to deal with the animation aspect of the wall side so there's a lot of components going on here so this is going to be a bit more complicated than just the number of jumps that you have so let's start by saying every single frame will say the self-doubt wall slide equals false and the purpose of this is so that this variable will act as a single frame switch or like I don't know it a button where it fires off an event once you press it it sort of has a setup so once you set it to True during the frame it'll be true for the rest of that frame but then when it comes back to here on the next update it'll be false so this is a useful trick if you want to have something that just it looks that's sort of an event that fires off once so uh now we'll write the part where we'll determine if wall slides should be set to true so we'll do if self.collisions right or self.collisions left and self.air time is greater than four self.wall slide equals true so we're saying here is we if we hit the wall on either side and we're in the air and this is a threshold that we're using to determine if we should be showing the jump animation but if we're in the air and we're touching a wall then we should be sliding on the wall so since we're using this technique where we've basically set it to true and then switch it back to false every single frame then we're basically saying only for the frames where this holds true will this be true what you can also do is you could throw an else right here and say yourself that while slide equals false but this works as well now I can do self.velocity one equals the minimum of self.velocity one and 0.5 so what we're doing here is we're capping the downward velocity at 0.5 remember the Min function takes the lower of the two values so if this number is lower than 0.5 it'll take it if it's greater it'll take 0.5 so that's just a 0.5 cap effectively now let's do if self.collisions right so we need to break apart this statement here because while this is the statement that determines if you're sliding on a wall we need to break it down to determine which direction so that you can show the correct animation anyways so we can just check if it's right and if we know if it's not right then it must be left because that's how an overstatement works anyways we can do self.flip equals false and this is important for keeping our animation accurate and then we can do else self.flip equals true and remember the base images for the player are facing right which means that we want flip equals false when we're facing right and then flip equals two from facing left and anyways now we want to do self.set action wall slide and now the purpose of this wall slide variable because if you think about it we have most of the code here for handling the effects of the well size we're updating the animation we're handling the flip we're restricting the velocity so this variable right here is just for a couple of things it's used to prevent the animation set right here from being overridden by these ones so we can go if not self.wall slide because if you're sliding on the wall you don't want it to switch to whatever the normal animation would be and because of this restriction right here it would technically be either you're in the jumping animation or wall slide because this is the first case but we can just break it all out and say if we're wall sliding don't do any of the other animations we want to show the wall slide animation which is whatever it was previously set to and then once that becomes false which will be as soon as you're no longer touching the wool then it'll let it switch to the other animations again which should probably be jump alright so now that we have the animation side of things handled and the slowly sliding down the wall side of things handled we can go ahead and work on the jumping part of the wall slide so we wanted to make so you can jump off the wall so we'll do this by doing if self.wall slide and that's why we need this variable over here as well as an attribute we'll do pass and then this will become a lift self that jumps because we want to prioritize the wall slide first over the normal jumps and then we'll say if self.flip and self.last movement zero is less than zero so we're looking for is flip equals true which means you're facing left and your movement that you attempted to do is going to the left then self.velocity zero equals 3.5 so that's going to add a velocity um almost like an Impulse pushing you to the right which will push you away from the wall and that's the jumping away from the Wall Part so now the other part is the vertical component of the jump so self dot velocity one equals negative 2.5 and that will force you up same as the negative three here except in a wall jump you probably don't want to jump as high as a normal jump because you're also going to the side anyways we'll just help that error time equals five just to get the animation updated and we can do self.jumps equals Max zero self.jumps minus one so we want the jumps to be consumed if you have one remaining by the wall jump but like I said before you should still be able to wall jump if you don't have any jump left which is why there's no check for the number of jumps you have before it gets to the statement and then something you have to be careful about is because we're doing if self-die jumps and not if self die jumps is greater than zero then we have to make sure that the number of jumps can't go below zero and technically if you can wall jump when you have zero jumps left and you subtract one jump from there it would go to negative one so that's a bit of a tricky bug that you don't want to deal with so uh we Max these two values so that the minimum value is zero and that will effectively just only reduce your jump if you're you have them left so now we need to deal with the other direction so L if not self.flip and self.last movement zero is greater than zero and then we just kind of copy all this and there's better ways to do this but this is good enough for now um and you just swap that direction on the x-axis and that covers everything all right so that is our jump code the last thing I'm going to do and this is just for convenience if you want to add something on top of this is I'll add a return true if the jump actually occurs so that you could hook off some events from this jump function so you could go over here and say if self.player to jump so if the jump actually went through then you could do something else so normally my games I might add in a special animation for that but I think I'll leave this for now and you can go add that yourself if you want anyways let's go back over to our update function if you notice we modify our velocity here in two Dimensions so we have our code for the gravity that runs up here in the entity parent class for the player it will force the player to fall downwards and then cap it at five so that will make it so that if you're jumping up it'll pull you down and you've seen that already but if you notice we don't actually have any code for normalizing the x-axis so if you were to jump off of a wall and have your velocity set right here to like negative 3.5 it would stay at that number and you just constantly be moving left and we don't want that so to deal with that we'll we'll need to add some normalization to the horizontal velocity and that just means that we're going to be making it move towards zero so that it eventually gets to zero and you stop being forced in that direction similar to how gravity will pull you down you could think of this like air resistance that's sort of what it's like except technically we're exaggerating it for the sake of gameplay so we'll do self if self.velocity zero is greater than zero velocity zero equals the minimum of SEPTA velocity zero minus 0.1 and 0. so what we're saying I actually said this should be Max so what we're saying here is that we want to subtract 0.1 from the velocity every frame if it's greater than zero and then stop it from going below zero so you just want to stop at zero and then that will make it so that it brings it towards zero if it's greater than zero so we have to deal with the other side now which is if the velocity is negative so all that is is you just take this you copy it down here you switch to so plus and you switch the zoom in so that will make it so it'll take the two values and say which one is lower so as soon as this number crosses zero into the positive territory from these increases uh it will get capped at zero so that it can't actually go over so this will just pull the number linearly towards zero and that will give us the effect we're looking for so if we go ahead and run our game now we can run around we can jump and if we go over to wall we can slide down slowly out and we've got a crash here so it appears that I have not added the last movement attribute so that's just a simple concept uh the idea is that you take whatever the previous movement was and do something based on that so to add the last movement we can just go over to our physics entity and we can say self.last movement equals zero zero because remember this is what we're inheriting from is the player so it'll have access to everything in here and then we can go over here and then copy this down into this update function which takes the movement and then we can just drop it right here and say that the last movement equals the movement and now we should have this last movement attribute to work off of for our jump mechanic and something important to note is that this movement variable is keeping track of movement which is the input to the update not the actual movement that was executed upon so if for example you're running into a wall or to keep track of the intent to move towards that wall and not just put it as like zero on the x-axis because you hit the wall and that helps us keep track of the fact that we're actually trying to push up against that wall now if we run over to our game and we've run it and we jump into a wall and we try to wall jump it works properly so it balances this away jump up Bounce Off the Wall Works as intended so you can jump once and then you can jump off a wall or if I go up to here and then slide down the wall and jump that's our only jump so that's how we intended for it to work and it's working as intended so these two things will cover pretty much all that we were looking for in terms of movement at least in the jumping sliding space there's one more thing that we have left for movement let's go ahead and add The Dashing mechanic to the game this is not only a movement ability but it's also how you do the combat in this game the enemies will shoot at you while you as the player have to beat them by dashing through them so it serves multiple purposes for the effect we'll have to move the player suddenly and we'll also have to have some nice visual effect to go along with it so what I'll use is a burst of particles so let's start by loading in the particles that we want to use let's grab the particle Leaf let's copy that and we'll say particle and then for the administration we'll say six instead of 20 because this particle should not last nearly as long as the least so if you look in our particles list we do have this particle particle which is just four uh images and they're just circles so I'm going to go ahead and close that though so now that we have our particle animation loaded uh we'll want to add the key binding for actually using the dash so let's go down over to our keys here and we'll do if event.key is pygame dot k x and that will be a key that we use to dash we'll do player dot dash and or self.player.dash and similar to the jump thing that's something that we'll have to go ahead and add here so let's go over to the player and we'll go under the jump code and we'll say def Dash self and this will be a pretty short function we'll do if not self.dashing which is what something else will need let's go up here and add it self.dashing equals zero if not self.dashing so self and without dashing at zero we can do if self.flip so that's if you're facing left soft.dashing equals negative 60. and then we'll do else equals 60 and that's if you're going right so this dashing variable will keep track of how much we want to Dash and in which direction so uh 60 is how long we're going to dash four and then this negative is for the direction so you know how there's the idea that velocity is not only a speed but also a Direction that's what we're using here we're using the directional component to say which way we want to dash so that's just a nice convenience that we can use all right so now we have to actually do something with this dashing variable so we need to do something that says basically if it's not zero then we need to apply The Dashing effect so for that we can go up here into our update function we can just go right here I guess let's go in a lot of different places here so self-type dashing is greater than zero we'll do self-dashing equals Max zero self.dashing minus one and this should look familiar that's the same thing we're doing right here so this is just bringing it towards zero so we need to manage our dashing value so that it just goes down towards zero and that the effect can eventually end so the other side of that's going to be the plus one with Min and then now we can do some other stuff so what we'll say here is if the absolute of self.dashing is greater than 50 self dot velocity zero equals the absolute of self.dashing divided by itself dot dashing by eight so what we're doing here is we're saying if we're in the first 10 frames of the dash then we take this part which is actually just it's going to be one or negative one depending on which direction it is so if you just divide the absolute by its own value you'll get the direction of the scalar so in our case if it's a positive number you'll get one negative one will be received when you have a negative number and that will just be multiplied by eight so we're just modifying it based on the direction here all right so now we can do if um self.dashing is 51 self the velocity zero times equals 0.1 so what we're doing here is that at the end of these first 10 frames of our Dash we're going to severely cut down on our velocity and this remaining velocity will be quickly just diminished by this code right here which will reduce the velocity so the purpose of this is to just kind of cause a sudden stop to the dash if you saw it from earlier you dash suddenly and then you stop but there's a little bit of velocity left over and that's how we can achieve that so you might be wondering okay that's the first 10 frames what are the last 54. so this is also not only working as our code for applying the dash but it's also working as our cooldown code so when this number is less than 50 but still greater than zero if you remember in order to dash dashing has to go down to zero so this is actually working as a cooldown so that you can't Dash as much as you want so remember this will eventually bring it to zero but this is going to be 5 6 of a second before it goes completely to zero so you have that little bit of time of a cooldown where you can't Dash again so you can't just Dash as much as you want and basically fly around so that's how we manage that so I can run the game now and show you what this looks like so I can run around and if I press X I can Dash around is cool now it's time to add some visual effects to it this just handles the movement component and let's go over to entities here let's close this if you remember our physics entity up here has its own render function right here so I want to make it so that when we Dash we turn invisible and we just turn into kind of a stream of particles so what we'll do is we'll take that render function and overwrite it right here we'll say def render well not override it we're going to build on top of it we'll take our Surf and offset and we'll do if the absolute of self.dashing is less than or equal to 50. so remember this is accounting for both directions you're saying if we're not in the first 10 frames of a dash basically if the dash is on cooldown or if it's just completely off of cooldown and we're not doing it at all um then we can go ahead and do super so this is accessing the parent class and Dot render and we'll pass in the serve and offset equals offset so we're doing here is we're kind of gating the rendering function of this physics entity which we would normally be using behind just an extra layer in the player that checks to see if we're dashing or not and if if we are dashing then we don't want to actually render it so it'll skip this and it just won't show the player all right so that will make the player invisible so the next part is the particles that I talked about earlier on so for that there's two sets of particles I want to do so first I want a burst of particles when you first start and when you end your dash and then I want a stream of particles while you're dashing so to do that we can go ahead into our update function here and right here we can just add the code for particles we'll do we can do self.game dot particles to append and this is where we can add a particle if we want so for this we need to actually have particles to work with so let's go over to up to the top and we'll say from script stop particle import particle and while we're at it I'm also going to grab the math module because we're going to be doing some trigonometry here all right so let's go back now that we have our Imports done we can work on spawning our particles for this so like I said we want to stream of particles just as the player's dashing so for this we can do particle and then we'll pass in self.game and we'll say it's a type particle and we want to spawn it from self Dot rect and if we go up here that's this function here that generates the Rex for the player and we want to take the center of the rect so this will just be a tuple just the X and Y coordinates of the center of the player and that will be our spawn location and then we also need a actually let's let's split this up here so we'll say velocity equals or I'll say p velocity just to distinguish it it's a p velocity equals the math dot cosine of an angle times a speed and then we want the math.s sine of an angle times the speed and for this we'll go ahead and say angle equals random.random by math.pi by 2 and our speed will be random.random by 0.5 plus 0.5 and then now we can take this P velocity here and say velocity equals P velocity and then we'll also want frame equals random.rand in zero to seven all right so what we're doing here is we're taking the cosine of a random angle so if well first of all let me import random real quick so let's go import random we're taking a random angle this will be random dot random times mapped up pi times two if you know how radians work this is just the full circle of angles in radians a full circle is mapped up by times two and this random.random is just selecting a random angle within that full circle if for example you were to just take the times 10 or something you would get it unevenly distributed because there are certain sections of the circle that show up more and the number 0 to 10 in radians than others that's why you want the exact amount of math.pi times two technically if you were to just put in a really big number it would be almost the same um but this is the technically correct way to do it anyways so this will just get a random angle and it's like think of a circle just finding in a random Direction and then we'll take a random speed and this will be just a random number between 0.5 and 1 um because remember we rounded at random zero to one so you take that multiply by 0.5 and 0.5 and you get 0.5 to 1. all right so for p velocity we're taking the cosine of the angle times the speed and the sine of the angle times the speed uh the cosines for the x axis and Sines for the y-axis this is a little bit trigonometry so what this is doing is it's generating a velocity based on the angle and this is how you move something in a direction in any case pretty much at least in 2D and 3D it's a bit more complicated but in 2D you take the cosine of the angular you want to move and you multiply the by the amount you want to move for the x-axis and then for the y-axis it's the sign and if you've taken any trigonometry you would know about the whole opposite and adjacent sides of a triangle and their associations with the functions of cosine and sine so there's actually a math behind here if you've looked into trigonometry for how this all works but it can get a lot more complicated than it needs to be so you can actually just pretty much memorize this one statement right here or this one formula and this will get you through most of the trigonometry you need for games in fact I have a video about this topic as well if you want to get into it a bit but this will cover most of what you need the other half what you need is using arc tangent 2 to turn and x and y coordinate into an angle so it's just the conversions between angles and Cartesian coordinates that represent vectors anyways so you might be wondering why would I go through all the effort to do all this trigonometry to generate a random direction to create our particle velocity with instead of just picking in actually random velocity so I've seen this in a lot of games and I think I actually used to do this myself where you just pick a random number for the X and Y coordinates and the problem with that is the way it's distributed and scaled so for scaling you run into the problem where if you're going diagonal let's say you picked a random number from negative one to one for both of the axes let's say you go in to the one One Direction so Plus One X plus one y and that's your velocity so that's moving twice as fast if you think about it because it's going diagonal as if you were to go just straight right from getting one and zero as your velocity and the effect that that creates visually is if you spawn a ton of particles like that it will create a weird explosion in the shape of a square instead of a circle uh so that that's the problem with these uh scaling technically you could normalize those numbers using square roots and stuff in order to make it so that the scaling tract and it is the shape of a circle but then you still have problems with the way that it's distributed like the way the particles get randomly distributed so to do it correctly if you want to spread something out in a circle the best way to do it is to pick a random angle in polar coordinates and then convert that into Cartesian vectors for velocity so that's a bit of math stuff and it's really important to at least when you get into more advanced topics in game development if you want to make your stuff look natural and look good A lot of times you do want to get into the math to make things look a little bit better than what the Primitive solution might be and that's just another random tip I'm dropping for you here and actually I've just noticed a rather large mistake I've made here uh and this is just a side effect of how I'm making these tutorials this whole section here is actually for the burst and not for the Stream so if you remember I said I want to stream of particles and a burst of particles so technically this part right here is the Claus that's for the stream so it's just any time that you're in the middle of the actual movement of a dash do we would run this code so for that we'll actually want to take this and we'll want to create another one of it modified for the stream but since I just went and did all the the math and explained that for the circle I'm going to add the extra case for the circle here so we'll do so if um self that dashing in 60 50 for I in range 20. and this is why I was talking about when you do a bunch of particles we'll say if our dashing is in 60 50 this means if we're at the start or the end of our Dash we'll do this 20 times we'll create 20 particles in random directions with random speeds and that will be our burst all right so now we can grab this and go ahead and fix the stream stuff over here so that's going to be we just drop it right here and Boop and same thing except with some minor differences so P velocity will have to be different this time we'll do pivot velocity equals and we'll say the absolute of player dot dashing or the absolute of self.dashing divided by self.dashing by random.random by three so if you remember an absolute divided by itself is just giving you the direction One or negative one and then we just take the random number times three so it'll be just random number from zero to three in the direction that we're dashing so that way the stream of particles moves along with the movement a bit instead of just staying stationary and then we don't want any movement on the y-axis because uh U Dash on the x-axis so we don't want that to be moving around anyways so uh we'll keep everything else the same and that should cover that other type of particle so we have the stream particles and we have our dashing particles right or burst of particles right here so now if we go ahead and run the game again we can run around if we press X oh whoops we're not supposed to have a leaf so that's a fun mistake you can make we're supposed to swap this with particle wait a second versus game that one's supposed to be leaf this one's particle wait why is that one aha so if we go over to here when I copy this at the beginning I left that as loading from the leaf so we want that to be actually particle not leaf and that should fix our Dash so if we go back over here and you can run around and you dash and you get your you get your start you get your inverse and your line but you don't get the start one which is not intended so to fix that there's an important lesson to learn here and that is that the order for some of these things does actually matter so if you think about it right here we're setting it to 60 in our Dash and then say after Dash we get to our updated function at some point so it goes through this update function and we have our code here that adjusts our Dash basically timer thing so it'll go from 60 to 59 or negative 60 to negative 59. and by the time it gets down to here but to this code if it were set to 60 it would have been pulled away already so it'd be at 59. so this is one of those situations where you can either take this code and move it up like up here or you can leave it as is and then change these numbers because technically this 50 for the end of the dash is also wrong it would be 49 if it's right here so I'm going to do the easy thing and just copy this and move it over here I think it reads nicer to have the number 1650. as opposed to having to figure out why it's minus one in the future for some reason uh so we can now dash and we get both ends of our Dash which is great and we have our line between just as we wanted it and that should cover pretty much all of the movement stuff that we'll be doing at least for the player at this point we have most of the base of the game done in terms of the movement and the basic implementation so the standard things you would find in a game like animations particles physics Etc but it's time to turn our game into an actual game and to do that I think it would be fitting to have some enemies to fight and the objective would be to defeat all the enemies and then move on to the next level without getting shot all right so to start adding enemies we'll do something very similar to the least spawners thing we have here and we'll extract a different type of tile and use that to spawn things so I'll demonstrate how you can add your own I guess spawners for the player and enemies but then I'll quickly switch to using the pre-made levels I have because I don't want to sit here for an hour making levels all right so first of all let's extract some spawners and then I'll go ahead and add them to the level data itself so let's do four Spawner in just like the top look just like the least spawners will do self.tile map.extract and then we'll do spawners zero and spawners one those will be the tile IDs we're looking for and this time we don't want to use the whole keep thing because keep is used to well keep the tile in the tile map if we don't set keep to true it will actually extract and removed the things that we're asking for so we actually don't want these spawners to show up in our tile map we want to just get the location so we can put stuff there and that way it works as a spawner as intended so we'll do if spawner variant is zero this will be the player spawner we'll do self.player.position equals spawner position and then else we'll just print out the position all right and I'll say enemy and then we can fill that in later but for now let's actually update our map here so to do this we can go back over to our level editor just go ahead and run this and let's see if we actually have it in here no we don't so we do need to load in the spawners so if you look right here we have Decor grass large decor and Stone if you go over to tiles we have this extra one called spawners which have a picture of the player and a picture of an enemy so what we'll do here is we'll say spawners and then load images and then we'll do tile slash spawners and that will give us 20 for that now if we run this again we now have the player spawner we could put it off grid and we can drop one down there and then we can go ahead and add a couple enemies as well and I'll press o to save and let's go ahead and run the game all right so now spawn this in the right location and if you look down here it gives us the two locations of the enemies that we added so with that we can now actually spawn the enemies to do that we obviously need some enemies to spawn so similar to the player we're going to go over to entities and we're going to add a new entity all right so let's add class enemy and this will inherit from physics entity same way the player does and then we'll do definite game position size all that good stuff all right and then we can do super Dot init game Enemy position size all right so with the Enemy will use the custom enemy animations which are over here in entities enemy it has an idle animation and a run animation so let's go ahead and load those in so we can just do enemy slash Idol animation load images entities slash enemy slash Idol and then we'll say image duration equals six similar to The Player's idle animation and then we'll also have an enemy run animation we'll just swap these two Idols for run and then we'll set the duration to four send more to the player all right so now we can go back over here and here's the idea for the enemies they should be able to walk around but then not walk off the edge of an island they should be able to turn around and we just have a basic AI for just walking around kind of casually and then it should also shoot at the player if that's appropriate so that would be based on the location of the player so whether or not the shot will line up so the enemies will only be able to shoot horizontally sort of have to see if the players within range and at the right level on the y-axis but for the actual movement itself it's going to be kind of like a Koopa so if you've seen Koopas they just walk back and forth and then if they walk up to an edge they'll turn around so part of that pattern for AI is to keep track of whether or not it should be moving so normally there's like a attribute or something that will keep track of whether or not it is moving and for how much longer it should be moving and then you just randomly decide when it should be moving or you can have like a global timer where it's like if it's like you mod it by however many frames and then you say if it's in this section of that modulo then you're walking so it could be a consistent pattern but I would like the more random pattern so we'll just use Randomness in a timer so that timer will be self.walking and this will be similar to dashing in the sense there's a timer but this time we won't include Direction because we'll derive it from the flip of The Entity all right so the first step here is we'll do an if actually no wait def update self tile map movement equals zero zero uh and if you look at the player these are the same arguments we have here so if we want to do the physics movement stuff we'll need the super.update thing so we can drop that in right here so this is just calling the parent update function from physics entity which handles the physics as you know we pass in the tile map and the movement we want to do so we can modify the movement here to be whatever we want so before we actually move we should do some calculations to determine what we should be doing in this Frame so we'll do if self.walking we'll do a pass the pass for now and then we'll have an alif random.random is less than 0.01 self.walking equals random.rand in 30 to 120. so what we're doing here is we're saying if we're walking we have this space to do whatever we want which we'll fill in later and then otherwise if we're not walking basically we can check if this random number is less than 0.01 since random dot random is random number between zero and one that means that this is has a 100 and 100 chance of occurring which since we're running at 60fps that's one in every 1.67 seconds if we're not walking so that's how long the average delay is broken and then our walking will be set to this random number which is the number of frames we'll continue to walk for so that's random number between half a second to two seconds all right so from here for the walking we'll need to do a couple things we'll start by making it so that it can just walk in One Direction and that's it we'll do movement equals movement zero minus 0.5 if self-doubt flip else 0.5 movement so we're doing here is we're saying that we want to subtract 0.5 if we're flipped that that's facing left or otherwise we want to add 0.5 so the movement is just given a zero because that's what the keyword is then it'll be negative 0.5 or 0.5 depending on what the flip is and if we're walking and then it'll just keep whatever the wax movement was now the next step here is to take the walking thing and reduce the timer so walking equals Max zero self.walking you've seen this before minus one so that'll just cut it down to zero over time if we're walking so let's go ahead and actually spawn our enemies to do that we'll have to go ahead and import our enemies and then we'll need to go over to our spawn code right here and we'll say we want an enemy with the game you want to give it to spawner's position to place it and then we'll say it's 8 by 15. you can look at the image yourself and go compute what the dimension should be but I'm just writing it down here uh since it's not actually teaching you much all right so we also want a self dot enemies to add this to so we'll do self-di enemies depend and we'll add in the enemy so now with our list of enemies we need to render them so I would like them to be right after the tile map but also still before the player so we'll do four enemy in self dot enemies dot copy because we're going to be removing from that later we'll do enemy.update this is the same update coach from before and then we'll give it a default movement of zero zero and then we want to do enemy.render and we'll give itself that display and the offset will be the render scroll all right if we run this you can see that they're up there and they're walking around it should walk off the edge because we have well yep they can walk off the edge and that's what we want to fix all right so to do that let's go ahead and go back to our enemies here and we want to say if there's a solid tile in front of where you're walking to and Below meaning that you're about to walk onto a floor you can keep walking otherwise you need to turn around so to do that we need to add some case right here under if self-doubt walking but we also need some way to determine if there's a solid tile right right in front of it so to do that we'll go ahead and head back to our tile map and we'll add I think what's going to be the last function for this video so let's add a def solid check so this is just checking for a solid tile and we'll give it a position it will compute the tile location as the string of the ends of the position zero we've been through this before self.tile size plus semicolon plus string int position one it's also tile size and that will give us the tile location if you remember this is just converting our pixels into the coordinates of the grid system because we were looking up tiles that you can collide with which have to be on the grid all right so we can do if tile location in self.tile map if self.tile map tile location type in physics tiles return self.tile map tile location all right so we're looking to see if this location exists and then if this is a physics tile which we have defined up here so there's two types of physics tiles we have grass and stone those are the things you can actually collide with so we want to see if exists and if it's a physics style then we return that tile data in our case we're not going to actually use the tile data for anything we're just going to check if it exists because if it doesn't hit this line then it'll return none so we can just put this behind an if statement since none evaluates are false as a Boolean this will be either true or false depending on whether or not something was found so we can go ahead and use this function now we can head back over to our entities and right here we can add that special case I was talking about so we'll have if tile Mount remember we had it passed in right here which is convenient dot solid check and we'll do self.rect Dot Center X and this is where the mask gets a little bit funky we're using some magic numbers and stuff it's based on the movement so remember we have this negative five positive well negative 0.5 plus 0.5 depending on flip we're doing the same thing here except it's more extreme we're looking seven pixels to the left or to the right based on the flip and that's from the center and then the next step is to take the self.position 1 plus 23 so that will be below the player in the ground so this is just kind of scanning out in front of wherever you're facing and then into the ground and it's taking this location and looking to see if there is a tile there now we have this as an if statement and if it values true that means there's a solid tile and that we should be able to move otherwise this will do self.flip equals not self.flip so you just flip it around if there's not a towel there the the side effect of this isn't that depending on the how far you're looking ahead and the tile size and stuff it could theoretically end up in a situation where you're standing on a towel and just flipping back and forth constantly because there's nothing on either side so you'd have to be careful about that stuff it's good to think about what your environment is when you're doing these types of things like for example right now we have the code for not walking off the edge and turning around when it gets to the edge but we don't have anything for if it hits a wallet to turn around I can write that up here in a moment but I don't believe yeah they're both on a flat surface us where they would walk down as opposed to up and there's no Wall Systems running to so this is not the greatest example of uh running into walls but all we have to do for the walls part is just take this part right here just grab this you say if that and walking you can drop it right here if that's the case and you just do the flip so we're saying if you run into something on your right or your left you turn around while you're walking if the solid check passes otherwise you can walk there's better ways to close this so you don't have to indent it as far but I'm not going to bother with that anyways that should handle the movement so let's get into the animations here so we'll do this is pretty simple if movement zero does not equal zero self dot set action run because you're moving so you're running else self.set action idle and that's as simple as it is so if you're not moving set it to idle this is a lot simpler than the player's animations because it doesn't have jump or stuff like that so now you can see that they are walking around and stuff and they turn around when they get to the edge which is nice and they kind of walk for random durations and pause for a bit which is exactly what we wanted all right so the next part is to give them a gun so that they can shoot at us so I'll go to entities again and for this we'll need to modify our render function for the enemy so that we can draw a gun on top of it so to do that we'll do def render Self Serve offset equals zero zero so this is like we have down here for the player except instead of hiding it behind something so we can turn invisible we're just going to take this as is and call the parent function for the rendering so this will this is effectively the exact same as not having that function at all but uh we're going to add in some extra stuff here because we want to show the gun so we'll have ifsoft.flip pass else pass so the gun will need to be able to flip depending on which direction the player is facing uh let's go back over to game and load in the gun so we do gun low damage gun.png it's as simple as that and then we go back to NC's and we can render this gun image on top of the player so we'll just surf.blit and this is where we're going to have to handle the flipping because so if self.flip that means the gun should also be flipped as well because if you're facing left then the player image is flipped so since the gun image is facing right by default we also need to flip that so it's facing left so we'll do pygame.transform dot flip you've seen this before self Dot Game dot assets gun and then we'll say true on the x-axis falls for the y-axis so this is flipping on the x-axis we'll do self.rect dot sensor x minus four this part is just to offset the gun so that it's put in the right spot you can play around with this number to visually see how it changes it's just moving it by four pixels and just a specific spot I want this place at and then we'll do minus self dot assets gun dot get width that's just counting for the width of the gun image because we want to render this from the perspective of the top right of the gun image when the gun is facing left in the top left when it's facing right so the subtracting the width accounts for that this is another thing we should just kind of play with the numbers and see what you get so you understand uh and then so this last part is the offset of the camera so this is the same thing used on the player's rendering as well and then we can do the y-axis which is react.center y and we don't touch this we'll just do offset minus one so we don't want any funny offsets there this is a 188 character line of code so this is a bit long remember if you get a loss on this go ahead and check out the reference files in the description so the other side's a bit easier because we had to do special math to do with the flip plus the actual flip transformation in there so we'll do something a lot simpler for this one so surf.blit self Dot Game dot assets gun and self.rect Dot Center X plus four so this is the other direction so we want plus four instead of minus four minus offset zero that's the camera self direct zero wrecked Dot Center y minus offset one all right so this is much simpler it's the center with just a four pixel offset and the camera and that should render our gun that was a bit hectic but here we go oops I must have written self-di acid somewhere where did I do that ah yes this should be self.gameday assets all right so I run it and they're holding guns if you notice the guns are held out in front of the enemies they're floating for now but it'll look a little bit better when we have the outlines later and they're a bit small but that's because it's pixel art in proportion of the player they're probably the correct size it's just a small aesthetic thing that we wanted to tack on now the next step is to make these guns shoot so the way this is going to work is that not that we have a gun object that can shoot but instead we're going to make it so that the enemy renders the gun image and then also generates the bullets before we can actually have our projectiles we need to create something for them to be stored in in the game itself we also need the image for the projectile so for this we can just copy the gun thing and say projectile and just copy that over there and we're good so that has the projectile image you can look in the files if you want to see that so let's go ahead and add the projectiles so I'm going to drop them right around here by the particles we'll do self.projectiles equals Boop so this is going to get pretty messy here we can go down here right below the player because we want to protect our speed on top of the player but not above the particles because we want the particles to be on top of the projectiles so we'll go right here and we'll say four projectile in self.projectiles to copy so we're going to be removing these later and we'll say projectile 0 0 plus equals projectile one so what we're going to do here is these projectiles we could make them objects but it's since they're so simple it's easy to just give it a list so what it'll be is an x-coordinate a y coordinate as a list for the first value so here's what it'll look like we'll have x y and then we'll have Direction and then we'll finally have a timer so all we have to do is modify it since these bullets only fly horizontally we modify the X position actually this would be a list because we're modifying it we're modifying the X position based on the direction so we grab the position we say the X component and then we add the direction for the movement and then we can do projectile two this is the timer plus equals one uh and then let's grab the image here we want to shorten this to image because we're going to be writing this a bunch all right so let's draw the projectile so the self.display dot bullet image projectile 0 0 minus minus image.get width divided by two When You Subtract half of the width of something that just centers it uh at least if you're doing that for rendering because it's based on top left if you subtract half the width it's well it's top Center if you want to do the height two it'll give you the true Center which we will also do but first let's add in our render scroll here so that the camera is applied and then we'll do projectile 0 1 minus image dot get height divided by two minus render scroll one so something important to remember here is that if you're like adding something new into something like a world where you have a camera like this oftentimes if for some reason it doesn't appear it's because you got your camera stuff rolling so make sure that you're always thinking about how the camera should apply to whatever you're working on if you're working on say like a HUD element so like maybe a health bar or something that's popping up in the top left of your screen you don't need to apply the camera scroll to that but if you're working on something in the world you need to apply the camera scroll and then there's the other way around where if you're working with something in the world and you want to convert something that was in the units of the screen space into the world space you have to apply it backwards so it's just something always important to think about all right so we're rendering our projectile here so the next step is to make it disappear if it hits a wall so we'll do if self.tilemap dot solid check this is the same thing that the enemies are using to determine whether or not they should turn around we'll do projectile zero so this is the location of the projectile so we're checking if the location of the projectile is a solid tile and if it is then we want to remove it so all we have to do is soft.projectiles.remove projectile and then we can add some effects here later but we won't for now uh now the other case and this is why we have a timer is because if it just flies off the edge of the map we don't want it flying forever because then if it just keeps flying it never disappears and as the game runs longer you'll get more and more projectiles and eventually it'll lag out because there's too many projectiles so what you do is you say projectile 2 is greater than 360. so we're saying here is if our timer which is this is the reason we added it is greater than 360 which is six seconds then we can do self Dot and this is just this code so we just remove the projectile again all right so let's add one more thing we'll do a lift we want to add something that checks if it hit the player because it's a projectile that's supposed to kill the player so we'll do ABS self.player.dashing is less than 50. so what we're saying here is that if you're not in the actual moving fast part of The Dashing animations as if it's either on cooldown or if it's at zero then we do the rest of what we're about to do here so this check is just to make it so that when you dash you're actually invincible and that's how a lot of games do stuff like that so it's a fun little mechanic all right so now we need to check if the projectile hit so we can do self.player.rect dot Collide point and we give it projectile zero and if that collides with the projectile's position we know that it's hit the player so from here we can do once again removal projectile so this will work for now we could print something out if we wanted later on we'll add some visual effects and make it so that it actually kills you and there's a transition and everything but we'll get to that in a moment so let's go ahead and run this and see how it's working actually we know we can't yeah we need to add the projectiles when you spawn them so for that we go over to the enemy and instead of using like another random Factor the way we'll do this is that when it decides to stop walking after walking is when it'll shoot so this adds a more consistent Behavior to shooting because in games it's nice if enemies have some way of indicating that they're about to attack so in our case it makes a lot more game design sense to line up the shooting with some sort of predefined action so if for example which is what we'll be doing here you shoot after you're done walking then the player has all that time where the enemy is walking to get out of the way so it's a little bit of a warning for the player and adds a bit more depth to the game so what we'll do here is we'll say if not self-doubt walking so if you notice this is under the if self.walking Clause we're saying if not self.walking this is because we have this code right here which is running down the timer so we'll get one frame where it passes the statement then it modifies it to go to zero so it'll go from one to zero and so it'll pass this and then it'll pass this so that we can get to this part of the code and this is where we can add this once every walk cycle effect of spawning a projectile so in order to actually shoot like I mentioned before we need to check where the player is and whether or not we should shoot so first of all let's calculate the distance between the enemy and the player so to do this we'll do this equals self Dot Game dot player got position zero minus self dot position zero and we'll also just do a self Dot Game dot player dot position one minus self.position one so this is just a difference between the player's position and the enemy's position so from here we can determine if we should be able to shoot so we'll say if the absolute of the distance is y-axis so that's uh how far apart they are on the y-axis because we're taking the absolute and we'll say if this is less than 16 pixels so if the y-axis offset between the player and the enemy is less than 16 pixels uh now we need to find some other checks so we need to check if self.flip and this 0 is less than zero so if you look at this think about it so if the players to the left of the enemy you will get a negative number because say look to the left would be say zero pixels and the enemy would be at 10 pixels so you would get 0 minus 10 is a negative number so if this 0 is less than zero that means that the player is to the left and the flip when true means you're looking left so we're checking if we're looking enough and the players to the left then we should be able to shoot and the flip side of this pun intended is if not self.flip and this 0 is greater than zero so if the player is to the right and you're looking to the right then or rather the enemy is looking to the right then it can also shoot so we'll do self.game.projectiles pen and this is just that list data remember so it's position and then Resurrection with speed included and then the timer so we'll just do self.rect Dot Center x minus seven self.rect Dot Center y so this is going to spawn it to the left because we're facing left because we don't want to spawn the bullet exactly inside the player we want it to be cast out a little bit all right and then we give it our speed so it'll be facing left so it's a negative number and then we give it zero for the timer and then we'll copy this and then we'll do the same thing for the right direction we'll do plus seven instead of minus seven because that's right and then we'll also make the speed 1.5 instead of negative 1.5 all right so that should create our projectile so let's go ahead and run the game and see what we have all right so we have two enemies here uh might be a bit difficult to get some suit at me from here so we'll get over here so you saw the projectile just briefly there it my body blocked it so that shows you that that was working I guess technically on this map there's nothing for them to shoot at because well they saw projectile go off to the side there but there's no walls on the shootout so this is not the greatest example for walls but there are walls on the other maps that they can shoot at anyways so I actually can't get up to this other one up here but uh this one is shooting correctly so if you notice if I'm in the right spot when it stops walking so I actually have to be kind of level with it so if I'm standing down here it won't shoot but if I'm level with it or close to level it will shoot and it'll create that projectile which can hit me and uh we can trigger something off of that like dying for example so let's go ahead since we have all this working we can go ahead and load in the custom maps that I have pre-made so if you look in our files here so we have all these images but we also have this Maps folder which has zero one and two so I'm going to actually create a function called load map here we'll call it Def load map self map ID actually let's call it load level and then we'll do self.tel map dot load and we'll do data Maps and then we need to add Str map ID plus dot Json so whatever map ID we give it here it could be an integer it'll convert it to a string and it'll load that map now the other thing to keep in mind is that when you load a map especially since we're going to be loading Maps multiple times because you want to change to different Maps we're going to need to clear out all the particles all the projectiles and all the visual effects and stuff and just reset everything and then we also need to reload like the player position and everything so these things right here these spawners need to be copied down and the projectiles and the scroll so all of this needs to be copied so we'll just take that delete it we'll go over here to load level drop one right there and that should load it in with the level and now instead of doing this tile map to load right here we can do self.load level and we'll give it zero so now this should load one of the pre-made levels that I have you can look into these files if you want you could copy them out and rename them the map.json if you want to open them up in the level editor as well to see what they look like in there but we'll be running this from here so let's go ahead and run that and we have this nice little map if I stand right here and wait for him to shoot at me you can shoot out a wall and you can see that the projectile disappear so that's the test case for that and we have this one's a lot more nature filled than the other test map that I had and there's a little rock section over here as well but it's a pretty nice map there's a few enemies hanging around so the next thing I want to do is to add some visual effects here because if you notice when I get shot it doesn't look too nice uh the projectile disappears if the projector hits a wall it just disappears and also when they shoot it just makes a particular appear so that doesn't look fancy enough for me so we're going to do a little bit of trigonometry here and start making this look fancy so the way we'll do this is We'll add something called the Sparks this is I guess one of the most common things you'll see in my games nowadays in terms of visual effects it distinguishes my games from other games because there's a very particular way that I do these that just kind of stands out I I think it I don't think it necessarily looks that good compared to other methods it's just that it's kind of distinctive so you can I can look at games and clearly see a difference based on the fact that I use this technique all right so the spark effects should be above the projectiles but below the particles so we'll do four spark in the self.sparks.copy and we'll do this similar to how we do the particles or not the particles the projectiles where we wrote the code for managing it first and then added the data so this time though because it's so much more complicated than projectiles we will be creating a class for it so we'll just do similar to the particles here actually we'll do kill equals spark. update and then we'll do a spark.render self.display offset equals render scroll this is the basic rendering stuff and then if self.kill we do the basic self.sparks.remove spark so this is just keeping track of the individual spark visual effects that we need to render so it'll update it determine if it should be removed render remove if applicable so this is just a self-managing thing right here all right so now that we have this we need to have this spark object that we're iterating over in our self.sparks that we're going to update on so we'll have to add that in now so we can do this as a new script so we'll go new file we'll say spark.pi and we'll do import math import Pi game and we'll do class spark this one won't inherit from anything so it's pretty basic we'll do an init function we need to position an angle and a speed that's all we need if you look at my actual game for I have this effect I will actually add more options to the Sparks this is about as Bare Bones as you can get for Sparks normally I will have all sorts of things you can configure the size and how fast they Decay and stuff but I don't want to get too much into it here we'll just kind of assume a lot of things in our update function and a lot of the Sparks will work the same all right so we need to apply our position equals list position and then we need to solve the angle equals angle self.sp equal speed and then we need an update function this will be easy if you remember we did that thing with particles where we converted polar coordinates into Cartesian coordinates using cosine and sine so this time instead of just doing one conversion and being done we're storing the position in Cartesian but our angle is polar and our speed is the length part of the polar coordinate so this the angle and speed together represent the polar coordinates for the velocity Vector essentially if you want to get technical about it to handle that with our Cartesian coordinates position because you need that for rendering we do plus equals math dot cosine self-diangle by self.speed remember if you're doing polar to Cartesian you just do cosine for the X sine for the Y and you're pretty much done and then you multiply it to add in the length but similar to the particles thing the reasoning for using poor coordinates here is so that if you're moving diagonally it doesn't move faster but then there's a second use case here which is where we when we get into the trigonometry of the rendering for the spark you need the angle that the spark is facing technically you could just store it as a velocity and then convert that back into an angle so you can do the rest of the math but it's easier to just keep it as an angle and then deal with that later so the way these Sparks will work is that it's a white diamond shape a very long but thin Diamond basically and it'll move in the direction of its angle but then slow down and as it slows down it shrinks and once it stops it disappears so the size of it should be proportional to the speed as well so that it shrinks down as it slows down that way once the speed runs out it would have just smoothly disappeared because it just would have shrunk to in an infinitely small size so we'll do self dot speed equals Max zero self-duct speed minus 0.1 wide so if in effect the speed is its timer so we'll just subtract 0.1 from the speed every update and return not self.speed so once the speed is zero we return true which is the kill signal which will remove it from the list of Sparks all right let's do a renderer function and then we pass in a surf and offset the same as we always do and we'll take this is where the trig comes in went render points equals and what we'll be doing here is we're going to create a polygon so and then we'll use Pi game to render that polygon I'll go ahead and render the polygon with this code assuming this is filled out first so they can see what we're doing we'll do piagame.draw.polygon we'll give it the surf we'll make it white we'll give render points so packing the dried up polygon will take a surface to render two it'll take a color then it'll render the polygon as and then the list of points that create the polygon so this polygon is automatically closed your start point and end point don't have to be the same point so if you're running a square you have four points not five so it's actually the the four edges now there's an extra argument here if you want to use it for the width but we don't need to use that if you don't specify a width it'll automatically be filled in and that's actually a lot of the shapes the rectangle is the same if you enter a rectangle using pi game and draw a rect if you don't specify our width it'll fill it in if you do specify width it'll just draw the outline of the shape and not fill it in so you can do some other visual effects there if you set a specific width all right so let's fill in our points here so we need to create that thin diamond shape based on the angle of the spark so if you're facing right you should have a horizontal spark if you're facing up it should be a vertical Spark so to do that and that's not just the two cases it needs to work for any direction to do this we'll do self dot position zero and plus and if you remember when you want to apply a rotation for a movement so if you want to move in this direction or something remember you do the math Dot cosine of the self.angle and then you multiply it by the speed so in this case normally if you if you want it to be like five pixels out you just multiply it by five but since I mentioned before that the speed is going to shrink and as the speed shrinks the the actual spark should also shrink but we're multiplying this coordinate by the speed so what we're doing is we're actually casting these points out from the center of the spark by adding the cosine of the angle times the speed so there's a other stuff I'm about to do here to create this shape it's going to get pretty messy here but we're going to cast them out in the four directions so when you have angles if you remember a full circle is math.pi times two so if you want to for example cast out a point behind you you do plus math.pi because that's half a circle so it goes around half the circle and it just points in the opposite direction if you want to go out just 90 degrees in a direction you can multiply by 0.5 so that's a quarter of a circle so it'll just rotate 90 degrees all right so that's roughly how that works so I'm going to get in and do the massive part here so we're going to multiply the speed by three because we want one part of the spark to be longer than the others so we don't want a square we want a diamond or two of the ends are way further out than the two sides and then we need to apply the camera offset so that'll be minus offset zero and then we do soft opposition one and as I said before we just do math.sign same thing uh self.angle by itself that speed by three minus offset one all right so we can take this we copy this and then this is where we start getting into the different angles here so we have the one this one right here since it's using the angle that the spark is this is the forward point so this is the one that's being Tau saw out in front of where the spark is going so now let's do one that's either to the right or to the left you actually don't have to think about which one this is because we're going to go do both sides uh you the order does matter but only in the sense that you don't want to do opposite ends next to each other because you have to go all the way around otherwise you get a weird shape when you try to draw the polygon you'll get like an hourglass shape I think if you do it out of order but you can go either way you can go clockwise or counterclockwise it's just important to not do opposites next to each other so we do plus math.pi by 0.5 so it's 90 degrees offset and then we can just copy this go over to the sign so that's the Y component and do the same thing so that's another offset angle so we're casting it out to the either to the right or to the left and then the next step here is to make it because this is one of the side points we don't want it to be cast out as far so we'll switch this 0.3 well we'll switch to three with zero point five so that the ends are going to be six times longer than the sides so now with this we can just copy this both of them because for this we'll do plus math.pi and plus math.pi so this third one right here this is going to be the one facing in the opposite direction from where the spark is going that's why we're you're adding half a circle to the angle and then we keep the times three because this is another long one and then the next one's a short one so for this one we can subtract math.pi the other option is to add 1.5 times math.pi but subtraction looks nicer to me so we'll do that so this will be the four side so we get forward uh and then this gets a bit weird because of Pi games coordinates and math coordinates are not the same so one of the ones to decide backwards and then the other side so this creates a full diamond all right so I know this is also kind of a bit of a a mess of long lines here so it you can check the reference code if you made a mistake here you can also play around with this create some other shapes and some other nice effects anyways this is all we need for the spark so we can take this and we can start editing it to things so let's first add it to the enemy so when the enemy shoots it should create Sparks from the gun so the way we'll do this is we'll just go right right here and we'll just do a 4i in range four so we want to do something four times we'll just do self.sparks.pend spark and we do need to import Sparks we'll go up to the top here and we'll just go spark Spark and that'll import a spark for us so we can go back down to here now and we can finish our spark so it needs the arguments if you remember it's positioned first so for this we'll just take wherever the last projectile is spawn so self Dot projectiles negative one we'll take the coordinate component because if you remember this part right here the first part is the coordinates so that's the location of the last spark you could just like copy this out write this as a position and reuse it here but this is another way you can make this work and then we'll take random dot random minus 0.5 plus math.pi comma two plus random dot random so you can see vs code telling me what these are so we have the angle here so what we're doing is we're taking a random number and then subtracting 0.5 so it's a random number between negative 0.5 and 0.5 and we're adding math.pi to it so this is the projectile that actually is shooting left so the angle for left is math.pi so that's because the right just pointing right the angle for that is zero if you look into unit circles that's how it is although it's upside down in pi game for some stuff because of the fact that in game development the y-axis has flipped so but either way the opposite direction from right is going to be math.pi so what we're saying here is a random Direction in the direction of left plus or minus 0.5 so there's random variance but still pointing left to some extent and then we'll also take two for our speed and add random not random so there's a variance in the speed of our Sparks here so this will cover that and then now we can go over here and do the same thing except we don't need that plus math.pi because we want it to face right instead of left all right so this will cover are spark spawning for the shooting so the next part is the spark spawning for when it hits stuff so let's go back over to the game where we have this code for the projectiles and we'll say in here if it hits a wall we want some more projectile so we'll do 4i in range four so four Sparks again we'll just append them and similar to before we'll need to go ahead and import these Sparks I'll just copy the import statement up here and we'll go to the top of game drop it right here scroll down and we now have this loaded correctly so we can do other stuff here so we'll just take the projectile zero since we're not using the WASP projector anymore just use some current one that we're iterating over so the angle should be this component and then we want to do a bit of a funky thing where we do plus math dot pi but only if projectile 1 is greater than zero so we're saying is that we want to shoot the Sparks left only if the projectile is going right because when you hit a wallet I mean there's two ways to do this um I like the style where the Sparks bounce in the opposite direction when it hits a wall because that's where the Sparks would reasonably go you could also go in the direction of the projectile so you can float this around if you wanted but um I like it this way so we're saying that it bounces back to the direction it came from it's where the Sparks will go and then we'll take the the speed as well which will be the exact same speed thing from before which is just random number from two to three and this will cover the wall case so the last one I want to deal with and this is going to be the big one is when the projectile hits the player so what we'll do here is 4i in range we're not going to do four we're gonna do 30. we're going to spawn 30 Sparks here for this one I'm going to write out the arguments beforehand so we'll do angle equals random.random by math.pi by two remember that full circle is 2 times pi so random at random times that will give you a random angle in a circle and then we'll do speed equals random dot random by five and then we'll just do self.sparks.pend spark and we'll say self.player.rect dot Center and give you the angle and then two plus random that random so the other thing we have to do here is not only are we adding the spark so we might want to add the particles so this is the death so there's going to be a lot of stuff going on you can do self dot particles.pend do we have particles in here yes we do particle self.game particle so this is a black one self.player.rect.center the velocity equals not that cosine you've seen this before plus math.pi by speed by 0.5 math dot sine angle plus math.pi by speed by 0.5 so this is what the speed is for so the speed right here is randomly chosen for the particles but I'm reusing the angle for both the Sparks and the particles but I added math.pi to the angle here so that the particles shoot off in the opposite direction of the Sparks anyways so that's the mess of visual effects for that actually there's one more we got to do there's the frame so do frame equals and we'll do random that random zero to seven all right so once again go ahead and check the reference code for the section if you're a bit lost on this because the lines are a bit long let's go ahead and run this so there should be lots of Sparks now I gotta wait for one stop walking while I'm up here hey turn around it's gonna shoot oh we crashed enemy has no HP Sparks okay so that was a mistake so that's gonna be over here we have cell phone game note Sparks not self.sparks because this is from the perspective of the enemy Emmy has an attribute projectiles uh yeah that's gonna be the game that projectiles let's see if we got it this time hello no that kill ah yeah that's not self that kill let's kill okay so you saw there were stuff there so a couple things happened there was the Sparks which are working when it shoots but then it crashed when it hit me so gay marriage game has no attribute game so uh what we did here this would be self not self-dog game and that should fix that and we should be able to get shot and explode so you saw there was an explosion there which is great let's see if I can is there let's see if I can shoot the wall so I need him to turn around there's a wall to the right if I can get him to shoot there if he will walk will you walk let's see it's fine you gotta go a little bit further than that there we go so now if I go down here we can see if it hits the wall and the Sparks bounce back the other way just like we added all right so it is possible to get hit uh we'll have to add dying later but first let's make it so that we can actually kill the enemies so to do that we use the dash stuff here let's go over to our entities let's go in the enemy update function and let's go over to the bottom here and we'll say if ABS self.game.player.dashing is greater than or equal to 50. so if you're in those frames where you're actually moving in The Dashing thing and then we can do if self.rect dot Collide react self-doubt game.player.rec so we're checking if the wreck of the enemy collides with the rect of the players that's like if their hitboxes Collide then we know that they touched while the player is Dashing which means that the player hit the enemy with a dash so now we can do something based on that so what we'll do is if we go back to our game here we have our enemy code we want to say similar to everything else kill equals that if kill so after enemies that remove enemy and then we can go back over to our entities here and we can just return true if we want because we're in the update function if we return true it'll remove the enemy so all we have to do here is since the touch return true and this is where we get into the mess of visual effects again so I'm going to cheat a little bit here I'm going to grab the visual effects that we used for the player's death I'm gonna copy them go over to here paste and it will have the same effect so there's a couple things we do have to fix here so this is referencing self.player because it's from The Game's perspective indentation uh this should be self.game.player and then this should be self.game and anytime when we have dot player should be that game dot player so this this is a long thing right here just run through real quick make sure we got everything yep that should be everything now the last thing is I want to add two big Sparks that Sparks Sparks the shootouts from the sides when you hit the enemy so we'll do self.sparks.pend and we'll do spark self.rect.center and our angle will be zero because that's to the right and then five plus random dot random so that's a much higher speed which means the Sparks will be much bigger and go much further and then we went the other direction which is going to be mapped up high so it's just right and left Sparks that are very big that we want to add so the last thing here is that actually these are in these particles and Sparks are in terms of the player this should actually be the enemy so we just chop off the player part so we didn't actually need that player part so we want to do spawn the Sparks and particles based on the enemies position when they hit and then that should be good so let's go ahead and try to delete one of these enemies here Dash through it it crashes not ideal all right so we go over here other mistake um this should be soft Game Nut Sparks and particles now let's try that again sub-optimal what what this time we have oh right here also all right one last time I think this should be correct push through and they disappear perfect so you get those two Sparks shooting off to the side which look nice and fancy and you can like destroy them everything so boom so we can kill the enemies that's great the last thing is that we want to make it so that the player can die so we can get shot and it does the particles and everything but the player needs to be able to die and the level needs to be able to restart so to do that we'll go ahead and add a dead attribute here so we'll do self.dead equals zero in the games load level function we'll have that equals zero so this will just be set to Zero by default when you start the game and and when the player is hit by a projectile we want self.dead plus equals one and here's where the magic comes in we'll go over to the top here and we'll say if self.dead self.dead plus equals one so it starts at zero so this won't add initially but once you're actually dead and it's a positive number it'll keep adding to it and then once self.dead is greater than 40. self.load level zero so all we're doing is we're creating a timer that starts as soon as you die and after 40 frames so two thirds of a second it'll load the level again and restart you so for this all that's left is to actually make the player disappear when it's dead so all we have to do is head over here and say if not self.dead then you can do all these player things like moving around and rendering so let's go ahead and test this out so let's get shot hello nope nope there you go I got shot so if you notice you got the delay where you can see the body disappeared and the explosion of Sparks and then the level reloaded so if I actually kill one of these enemies here and then if I go die to the other one you'll notice that the one on the left will respawn and then I will respond at the start of the level because I reloaded the level and all the stuff for like spotting the players spawning the enemies loading all the titles clearing all the particles and everything occurs when you load the level so that's a nice reset for us so that covers everything for the more hard gameplay stuff here this is probably the last big thing that we had to do for this project the rest is just easy stuff to make the game look nice and just kind of Polish it off so congratulations for getting towards the end of this very very long tutorial definitely the longest I've ever done so I just noticed that I forgot to add the part that handles the case where you're just falling off the edge for too long so let's say you just run off the edge you're stuck like this the only way to get out of this is to close the game and restart it so that's something that needs to be fixed and to do that it's a really really simple trick all you do is you just go in here take the air time if self.air time is greater than I don't know what a good number is so let's say you're in the air for three seconds so 180. we do self.game dot dead plus equal to one simple as that I actually don't want to do plus equals one because this is something that will activate constantly once you pass a certain point which means the death time will last longer actually you know that would be fine because you're probably going to be off screwing because of the following anyway so we'll do plus equals one actually I think 120 would be a better number yeah I think we have to do before this will work is we'll have to go over to the game and we need to find the player say self.player dot error time equals zero because we don't want it to trigger multiple times all right so let's just jump off the edge here and see what happens all right so it spawns back which is good so that way you can't just fall forever and that fixes that issue all right so the next thing we'll want to do is start to get into the polishing of the game the things that I had in mind for this part is the screen shake so the screen shape is a pretty simple concept there's a couple ways you can do it so one way well I mean obviously it's just a screen shaking supposedly one way is to modify the scroll value for the camera so that the camera is moving around and suddenly the other technique which is a little bit more convenient I feel like is to modify just this number right here so when we're scaling up our display onto our screen for the pixel art we put it at the coordinate 0 0. so if we just change this number let's say we put 50 right here and Run the game you'll see that we've got this black bar on the edge we can move where we're rendering this thing so we can like put whatever we want right here and this could effectively be our screen Shake by just changing these values so we can just take these numbers and generate a screenshake value from them so let's see here let's create self that screen Shake equals zero and over here let's do self.screen Shake equals Max zero self.screen Shake minus one and that will make it so that the screen Shake values like a timer we've done this before a bunch of times that goes down to zero and if it's positive we want to offset our screen by it so this is pretty simple we just do screen Shake offset equals random.random by self.screen Shake minus self.screen Shake divided by 2 and this will just make it so that you get a random modification in pixels from the number zero to whatever the screen Shake value is you can just drop screen Shake offset right here and this will offset our screen with the screen check value all right so now we just need to apply our screen shake this is pretty easy let's find somewhere where we want it to be applied so right here when the player gets shot we'll do self.screen Shake equals Max 16 so if that screen shake this is so that it essentially sets it to 16 but if it's higher it won't actually pull it down that way that a larger screen Shake won't be overridden by a smaller one also something to note is that this is kind of like the speed thing for the Sparks where the effect is increased as the timer is higher so the the offset here is going to be greater when the screen Shake number is larger so it kind of eases off but you give a bigger number it gets a large screen shake and lasts longer all right so let's take this and let's copy it over to the enemy code so let's go into the entities and let's go into the enemy update function where we have the killing based on the player's Dash we can just drop it in here and this is the self.game.screen sheet because we want there to be an impact when you hit of an enemy with your dash and Destroy them so now the last thing is when the player is falling which we just added you want to do the same thing you want to screen Shake there we've got to be careful with this so this should be hidden behind a if not self.game.dead actually no technically you wouldn't need that because I just remembered the update function won't be called if you're dead but whatever I'll leave it there it's no big deal all right so now you should get some screen Shake boom so you can tell the screen's shaking when I hit them and let's let's get shot real quick just show the other case boom all right so yeah there's a fair bit of screen Shake which is a very nice visual effect it adds a lot of emphasis to the different actions and it's a really simple effect to add it as you just saw so I like to add it in a lot of my games so we actually have our different levels here if you look in maps we have three of them or rather I made three of them for you to use but there are three maps and right now we just have it hard coded to just load the first one so the next thing is to make it so you can actually beat a level and transition to the next one so the way you can do this is by well when you beat it you just call self.load level you've seen how when you die you have to just reload level zero so now we can just take a level variable and use that to load the next one but I want a fancier transition so it won't be as simple as just writing a couple lines to just once you win move to the next one we're going to have a proper transition so there'll be a timer and everything so for that let's go ahead and do a self.level equals zero just to start us off and we'll use this whenever we reload a level so we can do load level find the other use case which is right here then we can just copy in that self.lovel value so now we can keep track of what level we're on with self-doubt level all right with that now we need a transition variable self.transition equals negative 30. this is on the load level so whenever you load a level I want the transition to be negative 30. the way I want this to work is that we'll have a circle of what you can see that shrinks down at the end of a level and then grows bigger at the beginning of a level so between those two after it shrinks down it's a completely black screen and then at the beginning of the new level it's also completely black screen but then grows into a scene where you can see the whole thing from that Circle all right so the idea is that when it's at negative 30 you want to show a completely black screen and it'll go all the way up to zero for full visibility and the way we'll make this Loop is that it'll go from zero at the end of a level 230 which is when you get the black screen so it'll shrink down it's a bit difficult to explain but you'll see it as I implement it so let's go over here into run and I'll just start writing the logic and I can explain it better once you can see what I'm doing here so we'll have if not when self dot enemies so if you've killed all of the enemies we do self.transition plus equals one and then the other case is if self.transition is less than zero so if that transition plus equals one so what we're getting here is that it starts at negative 30. it goes up automatically until it hits a transition equals zero and it won't go any higher on its own and then once there are no enemies this will automatically increase transition even more past zero and then once we're up to 30 then we can do self.level plus equals one and self dot load level self.level so the way this will work is like I mentioned sir negative 30. this is the beginning of a level here vision goes bigger until zero and then anything above zero and it shrinks so the way you can think about this is that when the absolute of the value of the transition is zero that's when you see everything when the absolute value of the transition is 30 that's when you can see nothing the the fact that I'm using the negative and positive numbers is just a trick to keep track of the state of the transition mathematically the way it needed to be because another thing you have to think about is that you have to load the level when it's black when it's completely black because when the view is shrinking down from the old level you don't want to transition to a new level yet you want to still be seeing that old level and then as soon as it starts opening back up again you want to be seeing that new level so it's a smooth transition so it's important to get this right when it's at the full black screen so yeah that's how that's going to be implemented all right so the next step is to actually visually implement this so what we'll do is we'll do if self.transition we'll do Transition surf equals pygame.surface and this is where we'll do a bit of some graphical magic we'll do self.d display dot get size so this will just create a black surface the size of our display and this is what we'll use for the transition so the trick here is we'll draw a circle on the surface and then we'll set the color key of the surface to the color of the circle that way that the circle we draw is a transparent part so if we BLT this surface on top of the screen you'll just see the outer edges that aren't inside the circle so that'll give us the effect we're looking for we just change the size of the circle so we can do pygame.draw.circle transition surf that's the destination so we're not drawing something onto the screen for once we're drawing it onto something else and then we'll draw this thing which we're drawing onto our screen or display or whatever and so this is going to be white like I mentioned just needs to be different color than black it could be anything because we're going to color key it and then we'll do self.display.getwith divided by two self.display.get height divided by two so if I get them to draw that Circle we'll draw a circle and all these draw functions are like this it takes the surface then the color and then the extra arguments usually if there's a singular position this is where you'll find it so in our case we have the center of our Circle and then now we have to give the radius so the radius will be oh and here is telling us so you can read that if you want so our radius will be 30 because that's the number we were choosing before we could change this number if we want the transitions to be faster or slower and then we'll do minus the absolute value remember I was talking about the absolute value of the transition of the self.transition and then the trick here is to multiply this by eight so this comes from the dimensions of the screen so you can do the math and do 30 times 8 and you get uh 180 so this is just to make sure that the circle can expand to its proper size and everything so also if you notice this is Clause behind the self that transition so when you're not transitioning when it's at that zero and waiting for you to beat the level it won't run this code and that's because this code is actually a little bit expensive in terms of performance because you're generating another surface drawing a bit cervical and then bloating it onto the display so it's an extra step we can cut out to save some performance if we want so we'll do Transition surf dot set color key because we just do the circle onto it and we want this thing to ignore the white color so that will become transparent now and then we just glued it on to the display and just throw it at zero zero because it's the same Dimension as the display as specified here all right so now if we run the game and if we defeat all the enemies it should theoretically transition if I did it right let's see and there you go so we got the circles and here's the second level so you can just like run through it and try and kill all the enemies and there's three of them which I'll I think I'll save the third one for the end here but something interesting to note is that I don't have the let's see if I can go and shoot at me I just accidentally dodged them all um the transition applies differently when you're dead so one hacky way we can deal with that is to go over to this right here and say if self.dead is 10 or 30 because if you notice self.dead actually goes for longer than the other transition stuff actually no it should be 10. we'll do self.transition plus equals one it actually should be greater than or equal to 10. and then what we'll do is we'll actually take this cut out the plus and just do self.transition plus one and then we'll take the min and we don't want it to actually go across 30 because that will trigger it to add to a level count so we won't don't want that we just want the transition to go as high as it can without actually going to the next level so what we'll do is just lock it right before it would go to the next level so that it's mostly done the transition so it looks nice but then it can stay on the same level oops there's a table in there where is that trans transmission that's not correct all right let's try that again there's one more issue we'll have to fix here there we go so that that's working as intended so we get ourselves our nice transition here when you die but is one more thing we have to fix and that's this thing right here we load the level by adding one to it and the problem is this level can go too high so what we'll do is this is just kind of to stop it from crashing theoretically you'd add more levels and extend this however you'd like if you wanted to make this a full game but in our case I'm just going to do something to prevent it from crashing and we'll just take this and we'll say the Min of self dot level plus one and Len OS dot Lister data slash Maps and we got to import OS to do this but the way this will work right where we go import OS the way this will work is it's capping this by using min to the number of maps that we have although actually now that I think about it this would actually be -1 because we have three Maps but since we started at zero the highest value is two so we don't want to go above two if we have three Maps so you just do minus one there if you're naming them like this all right so I won't run through that right now because that would take a while to actually run through the whole game but you'll get to see this cap later on when I finish the game and run through the whole thing but for now I can just test it to see if it's still moving to the next level and that's how I would know and test these things because technically that line of code doesn't execute until you finish a level and as you saw just worked properly there so that's good enough for us so let's go ahead and take that idea of rendering on to a surface that isn't your display for some graphical effects purpose and let's take that to a whole nother level here and have ourselves a bit of fun by adding some outline visual effects so to do this we're going to need a second surface and to do this type of thing in general these types of visual tricks a lot of it actually comes down to creativity more so than knowledge of the tools that you're using I mean you have to know how to use the basic functionality for your tools but a lot of it really does come from the creative application of the basics but this is just another example of something that I I came up with a long time ago and it's fun to use anyways so to do this we need a second display so we'll do self.d display two equals pygame.surface it'll be the same Dimension so 320 240 and we'll do pygame.src alpha actually wait sorry I had this backwards also there's no underscore in slrc Alpha I think there should be but there isn't anyways so we'll drop it right here actually onto the display uh displays on the needs it not display two so the way we're going to break this apart is it will have display and display two so display is the thing that most of the stuff is going to be rendered onto the naming here is slightly misleading because we're trying to make the code that we already have work for this and this is just kind of a Finishing Touch and this is something I actually recommend if you're making games if you're almost done with a project you can kind of just throw code cleanness out the window because if you're not going to be working on it much longer it's not too much of an issue and this actually applies to a much broader section of game development than just the end there's this funny idea that I heard a long time ago is that there's two types of game developers there are game developers that write good code and there are game developers at release games and funny enough I actually have not released a big game since I started writing good code some of that is because I got a bit too much into trying to do things properly and then some of that is just kind of other stuff going on but overall don't worry too much about your code structure especially if you're working by yourself as long as your stuff works a lot of times it's not as important as people think it is also there's the whole performance component as well so you do have to keep that in mind but let's move on here so we have our two displays here and the way this is going to work is that we render most of the stuff as normal onto the display and we want to give some things an outline not everything so the way this will work this is the stuff that we want to give an outline will render onto display and the things we don't want to give a background so we'll do this for the particles and the background for that stuff we render it onto display two and the way this will work is that between those layers so there's the back layer in the front layer so there's the background which won't have an outline then there's the foreground which is going through the particles and before the foreground is drawn we need to add our outlines so there will be a step right before the particles where we turn whatever we've drawn onto display into an outlined version of itself so the way we'll do this is first of all we need to break up our rendering here so we'll go down to where we render our background and this needs to be displayed to now because we're drawing the background and that shouldn't have an outline and now self.display needs its own color and this needs to be transparent because we're going to be doing some tricks here so we're filling display with pure transparency so if you put it on top of something else at this point you would get nothing but anything we draw on to this will not have a black background it'll just be whatever we draw onto it we can just split onto something else and that's what we'll get anyways so the next step is to jump over to that spot I was talking about so we'll find the part right before the particles I do want to include the Sparks with the outlines because I like how that looks but we'll need to create a display mask and we'll do equals pygame.mask.f from surface and we'll pass in the self.d display so what we're doing is we're making a mask from the display which is where we're rendering everything that we wanted to have an outline so if you don't know what a mask is a mask typically I mean it can mean multiple things but usually in computer Graphics a mask is essentially like an image with just two colors black and white or you can think of it as on and off ones and zeros however you want but the idea is that you can do binary operations on them but in our case we're actually using it for something different we're using this to convert something that has multiple colors like tons of colors and to something that only has two so what we'll do here is we'll create display silhouette that is a very difficult word to spell sometimes thank you French people I'm assuming it's French and we'll do display mask so this is a mask we created remember think of it as two colors white and black and then we'll do two surface and actually in terms of Pi game it'll call it the set color and the unset color so set color being kind of the white portion but what we'll be doing is we'll do set color equals zero zero zero 180 and then unset color equals zero zero zero zero so if you don't know when you see four colors like this this is red green blue and then Alpha so the alpha is the transparency so uh if it's zero it's fully transparent that's what we did when we filled the display up uh up here but 180 means that it's slightly more leaning towards opaque rather than transparent so that 128 would be halfway because it's uh 255 to the Max and the these first three are just saying it's black so right here it doesn't matter with the first three are because the last one is transparent but for this one it does matter because well that's going to be black so this is going to be the color of our outlines and the way this works is from surface will actually have a successes so it takes a surface and then it will either use a threshold which you can specify for what gets categorized as set and unset but if you have a color key set or if you have transparent pixels it'll just take those and use that as unset so in our case because we filled it with this transparency anything that's transparent will be unset so what we get as a result is anywhere where we rendered something before is the set color which means that it can turn into this so now what we get is just kind of blobs of black wherever we do something so to kind of show you what this looks like I'll do a display 2.blit uh display actually self Dot and then we'll do silhouette and then we'll give it 0 0 for now and then we'll change that later so if we run it right now we get an interesting result so you've seen this before this is where everything's just kind of lagging behind and we're not clearing the background properly so we do need to fix that the problem here is that we're rendering display instead of display two and we should be rendering display two when we're over here which I forgot but now you should see here we go here's just the Silhouettes Of Everything we're rendering so you can see what's going on here it looks like a nice fancy effect but we're gonna use this for the outline so the way this is gonna work is if for example let's do the next important step here which is display two Dot split self.d display zero zero zero or zero zero so that's just drawing it back on top so this is just putting whatever we drew in this frame back over on top of the display so we rendered our silhouette and then we're rendering our stuff we rendered again so if you run this again you just get the game and that's because this stuff is perfectly covering wherever we drew our silhouette so if we want to see where the silhouette is we can just do like four or four or something and you see we get that outline sticking out well it's not outlined it's a drop shadow kind of so there's a funny little Shadow on everything now but we don't want this shadow in particular we want a proper outline so to deal with that we draw this four times I'm gonna replace that those coordinates with offset and we'll do four offset in and this is where the magic comes negative one one uh zero negative one and zero one and if you do that you render it four times with an offset of these amounts that'll include one pixel in each Direction so there's left right up down you get an outline so now if you look at this we have ourselves a nice fancy outline although now that I'm looking at it I don't like the outline on the clouds so let's go ahead and take display two which is the one that doesn't get the outline this is the output and when we render the clouds we just swap that with display two and we can go ahead and run this and our clouds no longer have the outline so it's just the actual foreground stuff that has the outline which looks really nice and the Sparks also have the outline and the projectiles but if you notice the particles do not and it's important because the particles already black themselves so you don't want to mix it in there otherwise it'll look funny the particles will be weird shapes so it's better if you just don't do the outlines on them it doesn't look out of place like I said because they're black so that's convenient all right we have ourselves our nice outline effect there's lots of different things you can do with masks as well that are quite fun I recommend playing with this thing as well to try to figure out and understand fully how it works if you don't quite understand like I mentioned there's a lot of stuff you can do with masks if you look through my games you can see me doing all sorts of random things with it it's just a nice graphical tool I think in pie game one of the best uses which funny enough is not what they're I think usually used for one of the best uses is to actually just compress an image down into two colors you can do some type of thing but one of the more common use cases is to do to intersections with masks for Pixel Perfect Collision they allow for binary operations where you can say if this mask is set in a location and this other one is then it'll be set so that way if you have like a player's mask and an entities mask you can check for a Pixel Perfect collision by overlapping them and seeing where they're both set and there's all sorts of stuff you can do that people use them for but this is an interesting graphical trick if this is making use of just Pi games abstraction for them for how it can efficiently convert this into a surface there are ways to do this by hand or Not by hand manually where you're setting pixels and it would be super slow so this is just the fast way to do it all right that's pretty much it for the outline slow okay we're in the final stretch here the last thing that I wanted to add to this game before I would say we're done is to add the sound effects and Muse music so for the sound effects it's going to be pretty simple we just do self.sfx equals jump highgame.mixer.sound so this is how you load a sound in pi game used to pack them that makes it that sound and you then you just give a path to the sound so we'll do data sfx jump dot wave and then we'll just go through and then do this for our different sounds there's ways you can optimize this so that it's more extendable and takes less code but I'm not gonna worry about that because this is meant to be a simple example so I'm just renaming all these and making sure we're loading all the things we need so we have Dash hit shoot ambience jump those are our sounds so you could add more if you wanted and then I'm going to tweak your sounds a little bit so we're gonna have self dot sfx ambient set volume 0.2 and we're going to go through all of them and adjust them so these are just going to be constant values that determine how loud each sound should be when we play it normally when you have sounds like this they're not going to be quite at the right level for what you're expecting in the game it's best for your sounds to be as loud as possible when you load them in and then you just scale it down because you can't scale it up so we'll do 0.4 for shoot and 0.8 for hit so 1.0 is the max we'll do 0.3 for Dash and 0.7 for a jump so the quietest sound is going to be the ambience the loudest sound is going to be when something is hit and then it's like the second amount of sound is going to be your jump so we're just kind of organizing it like that I have already gone through beforehand and make sure these values sound good so you can mix it however you want but that should cover the loading in a configuration for our sound so I'm going to go ahead and add the music actually so the music is going to be pretty simple let's go ahead down to to run here we'll do pygame dot mixer.music download it will do data music.wave so for audio files in pi game there's quite a few supported technically but some of them can have issues when you try to package the game into an executable I know that I've had issues with OGG or OG files whatever you want to say I've been meaning to look into it and figure out what the solution is to get this to work because there are much more file size but if you want things to work well it's just best to have everything as a WAV file and then highgame works well with that and then the packaging works out with that I believe there's some restrictions on what can use different file types of the music player and the sounds have different restrictions for what file types you can use but you can look up the documentation to see that the action might be the same nowadays I'm not sure I know that there's some rules on the different operations you can do because for example I believe on MP3s there's some weird stuff with the way they're compressed that affect which types of things you can do it on it so there's stuff for controlling playback like you can fast forward and pause and play and do all sorts of stuff so certain things might be restricted on certain formats and stuff like that but all that's on the documentation so you can look into that if you're interested anyways so I just use Waze for everything if you're concerned about file size you can look into some of the other Solutions but waves are high quality audio wise and then they run fine for most use cases so that's usually my preference all right so we'll do music.set for volume so this is the same as when we set the volume for the sound effects pretty much except inside of it being a pi game that makes it a sound object we're calling it right here so something interesting to notice is that we're doing music.load so this is not outputting an object this is actually just meant to be something where you can have one thing loaded for your music and then we can set the volume for the one music thing and we just do set volumes there five and then we'll do pyogame.mixer.music.play so in the plate there's a couple options here you can see them right here if you want to look into them but what we'll be doing is we'll be using negative one so whenever you play a sound or music in pi game to dot play function can take a number and that will be the number of Loops you want so if you say zero it doesn't loop at all if you say one it'll Loop once if you say negative one it'll Loop forever and that's what you want from music and the other thing we want this on is the ambient so we'll do self.sfx ambient dot play negative one so if I run this you should hopefully be able to hear it so there's music playing and you can hear some birds which is nice uh hopefully I got the mixing right we'll see when I'm editing this later not that I can change it at this point but there's the ambience and the music the next step is to add the rest of the sound effects that we have here so we have a few of them we can just look at them and this is normally how I make games I go through and add the sound effects at the end but you can just look through here and kind of add them based on what they're associated with so like shoot for example is pretty easy just grab this so and this points to the sound object and similar to the music thing you just do to play on it and we also do that with the ambience right here so anyways we just gotta find the shooting code which we know is over in entities and if we go and find the enemy and find where it spawns the projectile which is right here we can play the sound so we just go right here we use self.game.sfx shoot.play and that will cover what we need for that and we also have a jump function so we can actually go down here into where is a jump and well this is a bit messy so we have return true so there's a couple ways we can do this we could drop it in all of these three cases for jumps or we can do it the hacky way and go over to here and say hey we call jump we had that whole thing where we return true so we know if the jump actually went through or not so we can say if that went through then we can just do self.sfx jump dot play and now we have the hit sound so for the hit sound that'll be for two things actually so the enemies can get hit and the player can get hit so for the enemies that's right here so we just drop that and we just grab hit we play that and then the other thing is when the player gets hit and that one's over in the projectile code in the game so if we go over here this is the projectile Loop we find where it kills the player which is right here we drop this we say shoot we play simply and then we have a couple more things I think we have Dash actually I think this is the last one so Dash is pretty easy so if we look over here we all have our Dash function so this one doesn't return anything so we can just drop right here we could add a return here and do it similar to the jump if we wanted or we can drop it right here it doesn't matter at this point so we'll just say Dash up play and that should cover all of our sounds so let's just run the game and see how this goes so you can hear me jumping it's a nice Boop sound if I Dash you can hear me Dash which is cool [Music] and let's see when he shoots you can probably hear it sounds like someone just hit a tennis ball all right so if I Dash through them you can hear the hit or if I get shot shoot oh that just crashes that's gotta be fixed so that's just uh go over to here this should not be self.game that should be self.sfx and that should fix that so let's just test that case real quick and like wait that will shoot that should be hit okay there you go let's see we should have it now there we go all good so all the sound effects are in so the game is technically complete but there's one last thing we need to do the last thing that I wanted to do was to make this game into an executable with python we call this packaging uh for things like C plus plus you might call compiling but technically with python what's happening is that we're taking the python interpreter which interprets python code and then bundling that into an executable with the python code so that way you run the executable and it runs The Interpreter and then it runs the python code so that's how that ends up working so the executables kind of have a minimum size based on how much stuff you're including and also however big the python interpreter is so I think the bottom end of it is like 20 30 megabytes or something and then it'll grow depending on how much you're including all right so to get started with this let's open up a command line so you can just do Windows key CMD you've done this before to install I'll play a game so it could be your terminal your command prompt you could even pull up Powershell if you wanted and do it there but I'll do it in CMD and we'll just do PI Dash M pip so it's the same as when we installed Pi game so whatever pip is for you and then we'll do install and it'll be Pi installer make sure you get the casing right on this is capital p u capital I no spaces and then I already have it so I'm going to go to navigate to my game so I'm gonna go desktop it's gonna be different for you obviously but just navigate to the folder where your game is for me this is going to be ninja game and if I do dirt here you can see all my stuff so this is the same as what I have here on the left and if I go ahead and do PI Dash M Pi installer depending on how a pi installer installed you might be able to just type Pi installer but for me I have to do the pi Dash Empire installer because Pi installer is its own module I mean we just installed it so yeah it's some module sometimes it'll be bound to just pile and Source you can just type Pine solar but we do this and then we have to say the name of the main script that we're running so our case descriptively run to run the game is game.pi so we just do game.pi and this would create the executable but there's extra arguments you can add here so there's select stuff for adding icons and stuff like that there's an icon thing I believe in pi game as well and then there's the icon that shows up on your taskbar and windows I think on different operating systems what has control over what parts varies so I think on Windows that's how it split where I think a pie game controls the top left icon on the window and then on taskbar it's based on the executable but then I think on Linux it might be I can't remember if the packaging has full control over it or if Pi game does but I think it varies by operating system so if you want to get the icon correct you would have to do both of those so you can look into that if you want so there's an argument for that in Pine star we won't mess with that because we don't have a Nikon for this game but there's there's two arguments that I commonly use so one of them is no console so normally if you well if you don't use access and you run the executable you'll get a window that pops up the it's the console output for the game so you know how we have this console down here where I mean we could print out whatever we wanted and we would see it in our output down here so it would create a command prompt window actually not technically command prompt like some kind of shell window where you can see the output of the game so if you don't want that and you don't want the users to see that extra window in the output I know a lot of people get confused by it most games don't have something like that so you would just do one file to or not one file uh no console to get rid of it and the other argument is one file the thing I accidentally just typed so one file will make the whole thing one file the big thing is that it gets rid of all the files that python generates because of it having to include The Interpreter and all the packages and everything so it'll generate like I don't know 50 files at a top level and then there's hundreds or thousands of files lower down that you could dig through that are just all over the place and it looks really messy but the problem with one file is that the people over at Microsoft have still not dealt with an issue on Windows where windows will flag things made with pi and seller as a virus because people make too many viruses with pi installers so Pine solar is really popular for I mean just making any type of executable from a any type of python thing so because python is also one of the most common things used for making viruses people are usually making their viruses and then packaging them with pi and solar so Pi installer especially if you do one file has some very specific things like the kind of identify the executable it's not intended to identify the executable but there's certain things that it just kind of tends to put in there because they're packaged the same way that will show up in those viruses for like the just the data for that executable and then anything else you make with pi and solar that might not be malicious so in our case our game so the people who are Microsoft have not figured out that they're hooking off of the stuff that's in stuff made by pi installer to detect viruses which I mean to be fair I guess it does detect a lot of stuff that is made with buy and seller without them having to recognize it but it catches a ton of other stuff too so anything made with one file will fall under that pretty much so you can remove that and then because people don't generally make viruses where there's like 50 other files and it has to be in a zip folder and everything uh because people don't typically do that I think coincidentally that whatever that signature is that they're hooking off of from the way that pyosaur mixed things is no longer there and because the executable smaller and windows doesn't freak out as much sometimes it still does because I guess maybe some people are still making viruses like this but this is the better way to avoid Windows freaking out when people download your game and try to play it so basically you can use one file if you want it to be one file otherwise don't either way this technically doesn't include all of your assets so a lot of your extra files so like your images and stuff aren't included in this so what you would have to do is you'd copy them over either way to whatever your output is but I'm pretty sure there are ways that you can also include other data like images and stuff so that you can properly make the whole thing one executable so depending on what you're doing you might want to do that it's just good to know about but in our case because if we ever want other people to play this we probably don't want them getting as many virus warnings they'll still get them because that's just how Pi installer is you can do this and if you really want to get rid of those warnings one option is I think to pay for a certificate with I don't know if it's with Microsoft or some other organization to basically prove that your executable is not a virus I don't bother doing that and then the other option is that there's some weird tricks you can use with pi installer for how it builds things to to get it so that it builds it in such a way that it doesn't cause that issue but it's very difficult for myself I don't want to get into the explanation or not necessarily very difficult but it's a bit confusing anyways something else to note is that if you're releasing on Steam steam just takes the executable or actually all of your files and then you tell it which executable the game should run when they click play and because steam itself is just calling the I guess the command line or terminal arguments or whatever to run that executable the windows warning never pops up so uh no matter how you do this it doesn't matter if you're releasing it out for something like steam then I'm going to see any virus warning so uh it really doesn't matter too much if you're doing something like releasing it on scheme but let's go ahead and run this and this will generate our executable it can take a moment and while I'm sitting here something else to mention is that a pine store actually works on a bunch of different platforms so it works on Linux and it's supposed to work on Mac OS it's supposed to use the same Arguments for them but I've had some issues on Mac OS I I know I tried with a I don't know maybe it's like a 2008 Macbook which is like 15 euros old now and I tried that I don't know like five years ago when Pine solar wasn't as popular and I had a lot of issues with it I don't know if it's fixed now so I don't know you can try that and see if that's working I'm pretty sure it works now so it should be mostly cross-platform not that your executed bootables cross-platform but you can build it on multiple platforms and the way that works is that if you want to build it for a specific platform you have to be on the platform you're building for so if you want a Windows executable you got to be on Windows if you want an executable for Mac OS you got to be on a Mac if you want something for Linux you got to be on Linux when you run this so it's kind of a bit of a mess and just one extra tip for those of you who are considering releasing on something on Steam I don't know if this is still the case but I know when I released something in like 2018 and 2019 they had a requirement for all Linux games that they had to run on Steam OS which is the operating system for steam machine which was released a long time ago and they were still requiring that people supported that in order to say that they supported Linux even though almost no one uses the steam machine nowadays and that was running on what was it I think it's over a decade old maybe uh coming towards 15 years this old version of Debian which has some very different requirements for how things run so if you wanted to work on that you might have to add a really old version of Linux to build on for this to pass themes requirements for what counts as Linux supporting that was a bit of a headache for me anyways we should have our executable built if we go over to the game folder we'll see that we have a couple new folders here we have build and dist so just ignore build that's just where a bunch of the build files are we don't need these this exe's doesn't work on its own so what we need to do is we actually go over to dist I think it's for distribution and there's a game folder here this folder is actually named after I think think whatever the name of the Python file you gave it is and then here's where everything is so this has all sorts of stuff in it so these dlls that I think Pi games using and we have like python stuff so here's the python dlls for the running the game or interpreting the code and we have like Pi game numpy open Geo sorry there's all sorts of stuff on here anyways the thing we want is game.exe but if we run this it crashes and says I can't find this can't find a Decor so it's trying to load the decor and can't find it so what you got to do is you go over to your game and you grab the data and scripts uh you don't actually have to grab scripts but I grab them anyways and just anything that the game's dependent on so in our case it's data unscripts we're not using map.js anymore but if we were we would have to grab that too but let's grab these two I'm going to copy them both and then let's go over to game same folder SS executable because you treat this game.exe like game.pi and you just drop them in here so so the paths need to be the same so if we refresh now we have data now which has all of our stuff in it and then we also have our scripts which are there if you want them depending on what you're doing you might have put something in scripts that you might need so let's just throwing in there all right so let's go ahead and run our game foreign and this is the executable not the python script so this will run even if someone doesn't have python so some common issues that can occur are well first of all sometimes there's issues with pi and seller just installing to begin with if you have those you're just gonna have to look it up and try to figure it out there's all sorts of things that could happen and then there's random issues that can happen during the build process which is also usually related to your install unless you have some weird arguments uh and then the last thing that's most common is when you run the game if it crashes and so a couple things that can happen here that you can watch out for are fonts so I know I don't know if it's still an issue I know at least it used to be that the system fonts in pi game did not work when pi and solar another thing as I mentioned before is the audio files so if you use an auger file a lot of times it'll cause crashes as well so there's just stuff to be careful about and then there's also the whole missing resources thing it used to be that and I think sometimes it still does this it depends on the circumstances it will just say like failed to execute script and won't give you the information you need as opposed to doing that thing we just saw here a moment ago where it gives you the whole crash output so you can debug it if it says fail to execute script sometimes you can open up a command line navigate to the executable and run it from there by just typing the name of the executable and then if you get that crash it'll show the output here I think that's more common if you do one file is when you would have to do that to your issues anyways so we have our game here let's go ahead and beat it because we might as well since we just finished it and it's an executable for everyone to play so I'm just gonna run through the levels here [Music] so we just gotta beat all the enemies stash through them don't get shot it's pretty simple I've done this a bunch of times already because um I tested this plenty of times while I was initially making this before I did the tutorial and fun fact this tutorial has been recorded over the span of I don't know 50 something days um and I made this base game a very well not a very long time ago but a while ago I just messed up so this one's the last level it's a bit trickier so you gotta actually jump over stuff and try to dodge things uh you can move right after you spawn which is an issue that could be fixed but it's not that big of an issue so I'll eventually figure this out a bit harder than I remember [Music] if the trick is to go right first but uh [Music] okay we got those guys that's the hard ones there we go let's try to not mess it up this time [Music] all right there we go so now there's this little island down here with a couple guys on it and we have these guys over here [Music] and we just got get a good Dash over on these guys on our left here we go we did it and if you notice like what I mentioned before it Loops back well it doesn't look back it Taps it at this third level and you can't go past it it'll just restart it when you beat it uh because that's the last level so you can add more if you wanted all right so the last thing I'll mention is that you can go over to here and send this to a zip folder and that could be like your game for example and you could call this like a ninja game so another thing is that for example the this executable is a bit hard to find for users so you extract it you see this mess in here and you're like where's the game so this is the game I mean you could rename it to help make it easier to find another thing you can do is create a batch script in this folder that executes uh this executable so that you can see It'll be like just a folder called like the name of your game or something and then there's an x uh batch script right here that you could just double click to run the game if you look at my games that's how I've got it set up at least in the more recent ones so you can go see how that's set up if you want to use that but I won't teach you about bad scripting here so that's pretty much everything for just creating something they can share with others and that's pretty much everything for creating the game congrats on reaching the end of this seven hour tutorial hopefully this is just the beginning for you and if so you may be wondering what some next steps may look like the next steps will depend a lot on your skill level at this point for some you may have some ideas already for how you could make games at this point without having to depend heavily on tutorials if so I recommend you do that since that's one of the best ways to solidify your knowledge if you're feeling a bit daring you could consider participating in a game Jam like the ludum Dare the alcajam or game makers toolkit game jams are a great way to get used to the game development process from the idea to the release stage and I recommend participating as soon as you think you're able for those of you who aren't yet ready to make games on your own don't worry this is actually the norm when you first start and is commonly referred to as tutorial hell it's defined by the absolute dependency on tutorials and in order to get out of it you have to focus on developing your problem solving skills so that you can take what you have learned and generalize it into other Solutions this is the essence of what makes an engineer an engineer in fact I believe that for most people you only have to learn the skill once and if you switch to a different skill altogether even if it's not even related to computers you'll learn very quickly you can improve your ability to generalize concepts best by picking apart things that work and making minor changes to both see what those changes do and to see if you can turn it into something else additionally a sample of one is often not enough to see the abundance of different ways Concepts can be used so it doesn't hurt to check out a few more tutorials if you're still struggling don't Focus too hard on just Pi game either oftentimes people aren't actually stuck with pie game and instead they need to learn more about python or programming pie game itself is pretty simple and there's not too much to learn in terms of its actual functions it's all about how you take those limited tools and build something with it there are plenty of functions that pygium has that I didn't mention in this tutorial but aside from maybe rotations and text they aren't really essential if you want to learn about a few more functions pygame does provide or some interesting techniques you can check out my amateur or Advanced Spy game tutorial playlists alright that's all I have to add if you just sat through the full seven hours and you learn something useful I think that'd be worth clicking subscribe for this tutorial took quite a bit of time to make I put out usually rather Advanced Pi game related content all the time so there's tons of other interesting stuff on my channel anyways thanks for watching and good luck on your gamedev journey
Info
Channel: DaFluffyPotato
Views: 27,019
Rating: undefined out of 5
Keywords: DaFluffyPotato, python, python tutorial, pygame, pygame tutorial, pygame platformer, platformer, tutorial, coding, programming, game development, gamedev, pixelart, course, pygame course, pygame platformer course, pygame beginner, beginner, beginner tutorial, pygame platformer movement, pygame platformer game, pygame platformer collision, pygame platformer level, pygame platformer code, pygame platformer example, example, Pygame Platformer Tutorial - Full Course
Id: 2gABYM5M0ww
Channel Id: undefined
Length: 365min 12sec (21912 seconds)
Published: Mon Jul 17 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.