Creating a platformer in Pygame with a camera, collisions, animation states and particle effects

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello there in this tutorial we are going to be creating the basic logic for mario style platformer in pygame and this project is eventually going to look like this although that is still quite far way off although by the end of this video you should already be halfway there so let me go over what i'm going to cover in this video first of all i'm going to talk about how to set up this kind of level that's the easiest part then after that i'll talk about how to move this entire level left and right so we can simulate a camera after that is covered i'm going to insert a player so that we can connect the player movement with the level movement so we have a camera following the player after we have covered that we are going to take care of the player movement so at this stage we are going to add collisions jumping and all of that stuff and by the end of that we already have a basic logic for a platformer although arguably not a great one but then in the second half of this video we are going to work on the player character so in here we are going to cover quite a few different things the most important one is the animation states of the player so we are going to figure out what animations we have to play given what circumstances so for example is the player on the ground is he running is he idling is he colliding with a wall is he falling is he landing all of these information we need to play the right animation and then along with that obviously we also have to cover basic animations for the player and those two steps are very much connected so i'll cover them in one bit and then finally i have added some particle effects so that when the player is running jumping or landing we show a bit of dust at the bottom of the player which i feel makes the game look much better and that well is going to be the entire video i believe it's going to be slightly longer but it is going to give you all the tools to start creating a proper platformer and do get subscribed so you don't miss out on the future parts in the series and you can make the whole platformer so with that let's start setting up this level and here we need two bits first of all we need some kind of layout that we want to follow so think of that we want to create a level but we need some kind of map that tells us where stuff has to be and in my case i just used a string that looks like this and all i'm really going to do is to cycle through this string and get the row and the column of each element and then i use that information to place something on the screen you see in a second how that's going to look and the code to place something like this looks like this and this is the very same logic i have used another tutorial of mine and space invaders where i have used the very same logic to place the aliens and the obstacles so this is very much transferable knowledge and i think it's best to explain this when i actually set it up so let's jump into our code and let's have a look at this here you can see the most basic setup for pygame and if your fault along my tutorials this should be quite obvious at this point so if we run this code all we see is the black window with the screen dimensions of 1280x700 and you can't see anything because we're not drawing anything yet now if you have no idea what this code does or what any of this means check out my introduction to pygame that should be really helpful but all right so the first thing i want to do is to create some kind of map so we know where the player is going to be and since i want to keep my code organized i'm going to put all of this in a new file so i'm going to create a new file and i'm going to save this as let's call it settings.pi and this is going to be in the same file as my main file which is the file you have just seen so now we have two python files one called main and one called settings and in settings i want to create a string and for me this looks like this and this is going to be our level map so let me explain how this is going to work generally this thing is a list so we have opening brackets here and closing brackets here and then inside of this list we have a couple of strings so we have string 0 we have string 1 we have string 2 we have string 3 and so on i think in total there are 11 or 10 strings in there let's say it's 10 for simplicity and each of these strings would effectively going to be my row so the top row would be number zero so this will be the row at the top of our game and on its side of each string we have a couple of characters we ever have a space like this one for example or we have a capital x like this one for example and each of these characters is going to be one column so if i go with this row here this would be column number zero let's record one two three 4 and so on and if i combine these two bits of information i can tell very well where a certain element is going to be for example if i look at the x in the bottom left here i know this is going to have the row 10 and it is going to have the column zero so i know exactly where it is going to be and that is the information i can then use to place something on the screen so this is going to be super useful now along with that i also want to specify a couple more bits of information the first one is my tile size and this one i set for 64 pixels which i think is a pretty good size and now along with that i also want to put my screen size and my screen width in here and you're going to see later on why this is important essentially we are going to import this settings file into lots of different parts of our code so having access to the screen within screen height everywhere makes our life quite a bit easier and as a matter of fact we are going to make some changes here now the screen width i'm going to leave as it is if you have a smaller screen it's perfectly fine to make this number smaller we're going to keep the rest of the game responsive to it so make this as large or small as you want whatever fits your screen however now for the screen height i want this to be relative to this level map here so that this thing informs how tall our screen is going to be so effectively what i want to do i want to multiply the number of rows by the tile size so let me put this in there and that will be always with the level on our screen which is going to look quite good i think and this could actually be a really good exercise for you that i want you guys to change the value of screen height so that it is influenced by the level map multiplied by the tile size the first bit of information i have to know is how many elements are inside of this list because each element is one row and for that i am going to need the len method and i want to get my level map but this right now would be a number like 10 or 11 and i don't want my screen height to be 10 pixels high so i want to multiply all of this by tile size and now i could print my screen height and let's run this and we get 704 and if i just print my level map i get 11. so in this thing we have 11 rows starting from 0 and going all the way to 10 so all right we don't need that anymore and now we have a basic setup for our game and this we are going to use in quite a few parts of our game so now i can go back to my main pi file and the first thing i have to do is to import all of this so from settings import start so we import all of the stuff we have created in here and we can use it so that way we can leave screen width and screen height as they are so now let's try this and we still get the same black screen although the height slightly changed by a few pixels but that really doesn't matter too much so now this app is telling us where stuff has to be and now to turn this thing into something we can actually see in pygame we will need two things first of all is some kind of block that we can place somewhere on the screen and this is going to be a sprite in my case and then the second part is a function that goes through each of these elements and place something on the screen wherever we find a capital x and well let me go back to my main file the first one we are going to need is then our sprite and i put this in a new file let me save this and i have called this tiles and in here i want to import pi game and then i want to create a class i call tile and this one has to inherit from pygame.sprite.sprite and again if you have no idea what the sprite is check out my beginner's tutorial to pi game that explains all of this in much more detail but in here i want to create an init file that needs self and now i want to specify two more bits of information the first one is the position and the second one is the size so we can tell where each tile is and how large each tile is going to be and now in there i want to create my super dot init method and then besides that i want to create self.image and i want to create self.rect and now we have to figure out what information to put in here and this again could be a really good exercise then i want you guys to just create a plain rectangle using this tile so this tile should have the width and the height of the size and the position of well the position so let's get started first of all for my image i just want pygame.surface and here i want size and size for x and y so the first size is going to be x and the second size is going to be y and since i'm creating a square those have to be identical so this is already what i needed and now for the rectangle i want to get self.image.getrekt and i want to place the top left wherever my position happens to be and now this way i can place this rectangle wherever the position is and it's going to be the size of my size and there's one more thing that i will need because right now this tile is going to be black so i want to get myself.image and fill it with a color and i went with gray but you can place any color you want in here it really does not matter so with that we have the first part that we have a tile we can place somewhere on the screen and let's actually try this so back in my main file i want from tiles import tile and first of all i have to create a sprite group so let's call this test tile and this is going to be pygame.sprite.group and in here i want to get my tile and i want to place it at position 100 and 100 and those two have to be a tuple and for my size i want 200 and now in our game i can get my test tile.draw on the screen and if i run this we can see our tile so we know this class is going to work although obviously we don't just want to place them randomly on the screen we want to place them in a specific position so i'm going to get rid of this and this because i don't want to create a tile in my main file as a matter of fact i want to create another new python file and this one i'm going to call level.pi so now we have four different files we have our main file we have a level we have tiles and we have settings and all that's really going to happen is that in our level file we create a class called level and this level is going to rely on tiles to place something on the screen and then in our main file we are only going to create one instance of this level and then draw it and that way we keep our entire setup nice and clean but alright let's actually start importing stuff so first of all i need import pygame second of all i need to have access to this tile here so from tiles import tile and once i have those two pieces of information i want to create my class level there's no inheritance but we do need it under init method and in here besides self i want two pieces of information first of all is level data so right now my level data would be this level map but i want to be able to pass different level maps into this level to create different level setups and then second of all i need a surface so the surface i want to draw on and well with that i can define a couple of attributes the most important one is i want to have an attribute called display surface and this one is just going to be surface and for now let me also create self dot level data and this is going to be just level data and now in the main file i want to import this level so from level import level all i'm going to do is let's call it level in here as well and this is going to get level itself and the two arguments i have to pass in here is my level i think i called it level map level map yeah and then besides that i want to have my screen in there so this is the display surface we want to have for this surface and with that in our main file we have access to the level and now all i really want to do in this main file is to run something like level.run or level.draw and that way we are going to keep all of our game logic inside of the level which i think makes sense now obviously right now this level doesn't have a run method so we have to give it one so self.run and for now we don't really have anything to do in here so i'm just going to add pass but there's going to be a lot of stuff in here later on so all right with that with our basic level setup now i want to create a function that cycles through all of this and places a tile wherever we find a capital x and this is going to be in another method and i have called this setup level and i want to have one argument in here besides self and that is going to be the layout which is going to be this level data and i don't actually have to store level data in its own separate attribute instead all i want to do is when we run the init method i want to get myself dot setup level and pass in the level data so now we have to figure out what to do inside of this setup level and the first thing we have to do is to create a sprite group and i have called this one self.tiles this one is just going to be pygame.sprite.group so we know what group to work on and this is actually something we can already do in our run method that i want to get self.tiles dot draw and then self dot display surface so whatever we place inside of these titles we are going to draw on our run method and now we have to figure out how to get the row and the column of each of these items here and this is just going to be a for loop although a slightly more complex one so let me build this one up slowly first of all i want to loop over every single row so for row in layout and in here all i want to do is to print the row and let's actually see what this is going to do so in my main file i'm going to run this and what i can see let me close this is down here in the console we can see our level so this is looking identical to what we have here because literally all we're doing is we're printing every single line so that's a pretty good start but there's one downside here that we don't know on what row we are on we just know the row itself but not where that row is so i want to know what index this row is on and for that we need the enumerate method and what enumerate does is it gives us an index and the information so i have to add in here let's call it the row index and what i can do now let me duplicate this and i want to get the row and the row index and now in our main file let's run all of this again and now get the information on row 0 we have nothing because on this rope there literally is no capital x the same for row 1 and for row 2. so all of this here is completely empty however on the next row we have a couple of axes so we know on the third row we have a couple of axes here and we can still see those so this way this line here tells us what row we have and where that row is going to be which is super important information now next up i have to figure out each column and this is going to be another for loop and this for loop is going to look very similar so i can just copy this entire line and instead now i want to get my column index and you could ever call this column i like to call it cell it really doesn't matter and i want to loop over the row so for each row we are getting i am cycling over every single item inside of that row which is going to be each individual character inside of that string and since i'm still getting the index i know exactly where stuff is going to be so in here for example i could print let's call an f string with the row index then another one with the call index and then let's say double colon and then i want to have another variable with the cell and let's put a comma between there and now if i run this i can see each individual cell so i know in zero and zero we have nothing however at some point there we can see some so here we have a couple of axes so i know in row 4 column 1 we have 1x which is going to be this x here so this x down here is going to be this x up here and now we know exactly what this x has to be at position four and one so this is then exactly what we are going to need so all right this is already working really well and now in here i want to add an if statement that if my cell is equal to the value of x because we only want to place something if there is an x and now if that is the case i want to place a tile and this tile is just going to be our tile class and in here we need two arguments first of all we need the position and then we need the size now the size we already have because in our settings we have a tile size so i want to use that so tile size but to get that we have to import it into this python file so from settings import tile size and now we have to figure out the position and for now let's just go with x and y and be aware here this has to be a tuple don't forget that and now that i have a tile i want to get myself.tiles and add this tile to it and well this should already be a pretty good start so back in my main file let me see how this is going to look and we are getting an error because let me close this all right and the reason for it is we don't have x and y yet so let's create them actually so i want to have an x and a y and this could actually again be a really good exercise for you they want you guys to figure out what numbers have to go in x and y to place each tile properly so let me start with x and in here i want to have my column index because this is the one that goes left and right and for y i want to have my row index because i want to go up and down and now this would be a starting point but if i run this now we can actually see it and you can see in the top left a couple of gray squares now the problem is they are all overlapping with each other because effectively all that we are going to do and let me actually make this a bit smaller all we are going to do here is we get the index of the row or the column so for example the row would start at 0 and go all the way to 10. so when we place this y inside here this is going to be number between 0 and 10 so it's way too small and the same applies to x so instead what i want to do is to multiply this by the tile size and this i want to do for both of them so tile size for both and now let's try this again and there we have our level so this one is looking really nice and let me just explain what happens here so if i go to my settings file and let's look at this row here so this row we know that this row is on 0 1 2 and 3. and then the first x on this row is at position 1. so this x is going to have the column of 1 and for y is going to have the row of three so right now this would just be pixels that we are placing but if we multiply this by 64 so our tile size so 64 for both we are going to place it just to the right of the previous block which would have been 64 pixels wide as well now in this case there is no block to the left of this x but if there were this one would be right to the right of it and this way we can use this logic to place the entire map so i hope the logic here makes sense and also if i run this again we can tell that our window doesn't cover the entirety of our level so what we have to figure out is how to scroll in this map and the logic here is actually amazingly simple because all i want to do is move all of these sprites either left or right by a certain amount so what i have to do in my tile i want to give this one an update method and this one is going to need self and let's call it x shift so by how much i want to shift this tile and really all we have to do is get self.rec.x and at plus equal x shift and now back in my level file when i run all of this i want to get myself.tiles.update and now i have to add one argument in here and by default this is going to be 0. so if i have 0 in here i run this you cannot see any difference however now if i placed a 1 in here and run out of this again now this entire thing is going to move and if i placed a negative 4 and here for example we'll move in the other way and that way we can scroll through our level and later on once we have a player all we really are going to do is to change this number here and since we want to influence it i want to put this into an attribute so let me place it here and let's call it self.world shift and by default it's going to be 0 and this then is going to be the argument in here and let me get rid of this and let's add some comments in here to make this a bit clearer and let's call it level setup and now i can minimize this and with that we have covered the very first part of this video so now we have a level that we can scroll through obviously it doesn't help us particularly much yet because we want to add a player to all of this and well for that we have to add a player and for now just to get started this player will only be able to move left and right and once we have that we can talk about how to connect the player to the level scrolling so we have a proper camera but well the player is not going to be anything complicated it is also going to be a simple sprite class that we can move around so let's actually create it here i'm back in my main file and what i want to do is to create a new file and i'm going to save this one and call it player.pi and in here i want to import pygame as always and create a class that i call player and this one has to inherit from pygame.sprite.sprite and spelling this correctly would also help and in here i want an init method that needs self and nothing else for now and then in there i want to get super dot init so we don't get an error and now in here again we need self.image and self.rect now for the image since we are going to change this later on anyway with proper graphics what we put in here doesn't really matter so i'm just going to add a blank surface that is 32 pixels wide and 64 pixels high and then self.image.film so we can actually see it and i'm going to fill this one with let's say red and now for the rectangle i want to get myself.image.getrekt and i want to place the top left where we have a position and this position we are going to get in the init method so now we have a player class and the next step is we have to place this inside of our level and as a matter of fact i want to get this position from this file here so i want to have the ability to add one additional character in here let's place the character in this position here and let's call it p for player character actually let me put it a bit further to the left so we can see it when we run the level so let's say here i want to have my player and then when we are running this setup level i want to place not just my x but also my player and this could be a really good exercise for you then i want you guys to figure out how to add a bit more code in here to place our player along with the tiles so pause the video now and try to figure this out yourself the first thing i am going to need is another sprite group and let's call this self.player and this is going to be pygame.sprite.group single because we only want to have a single player and now in this nested for loop i want to get if cell is equal to a capital p and if that is the case i just want to copy all of this because the x and the y stay exactly the same but now instead of tile i want to create a player and let me also call this one player sprite and our player is only going to need an x and a y coordinate and right now i don't have player imported so i have to do that as well so from player import player and then i want to get myself dot player group and add my players right so this should then all be needed to place our player now what he could be doing is get this code and place it right before the if statement that might actually be a bit cleaner this might be a slightly better code so now we always get the x and the y coordinate and then we use a capital x or capital p to check if we actually need it both approaches would be fine you could also do this inside of the if statement it really doesn't matter all that much and now with that we have our player in a certain position however we wouldn't be able to see it because we're not drawing the player so i want to get self dot player dot draw and self dot display service and since we're going to add quite a bit to this level i'm going to add some comments so this is going to be the level tiles and this is going to be the player and now back in my main file let's try this and there we go we can see our player exactly in the position where we placed it so this is really good and with that we can open my player and give it the ability to move and this is just going to be another method so let's call this get input it doesn't need any arguments and in here i want to get all the keys we are potentially pressing and this happens with pygame dot key dot get pressed and now in there if keys is pie game dot key let's start with right then i want to move the player to the right and for now let's add a pass in here if that is not the case so l if and i have keys for pi game dot k left then i want to move the player to the left and if neither of those are the case so else then i don't want to do anything so now we have to figure out three different lines of code to make our player move and in my case i have used a vector and vectors i haven't talked about yet but they are ultimately very simple so let me create one and then i'll explain it first of all as an attribute i want to get self dot direction and this is going to be pygame.math.vector2 and this one is going to need two arguments and they're going to be 0 and 0. so let me explain what vectors are and how we can use them in pygame if you have gotten through high school you probably have seen vectors already they are essentially just arrows you can draw on a coordinate system so for example if you have a vector with 150 it would be an arrow that looks something like this and all that really means is that we are moving 100 pixels on the x-axis and 50 pixels on the y-axis and then we are connecting the start point and the end point and that way we have an arrow so all the vector really is is a list with an x and a y value that's literally it and this is going to give us a couple of useful features in pi game that we can use the most notable one is that when we have a rectangle we can just add our vector to it so we don't have to worry about x or y and this is going to make our movement significantly easier and cleaner because we don't have to worry about two different variables for the movement it can all be in one neat variable they do have quite a bit more functionality but i'm not going to use that so all right with that let's go back into the code and in here i now want to change my self.direction if we are pressing right so i want to get dot direction and then here i can either target x or i can target y and obviously x is the horizontal one so this i just want to be one for now and then if we are pressing left i want to do the exact opposite so negative one and then if we're not pressing either of these buttons i want this to be zero so we're not moving at all and now what we can do i can define an update method like we have done for the tiles and now inside of this update method i can get myself dot rekt and for now i just want to move horizontally so x and then plus equal self dot direction dot x and of course don't forget to call self.getinput because we do want to call this function as well and as a matter of fact in my level class i also have to update myself.player so self.player.update just like we have done for the titles although the update for the player doesn't need any arguments so now if i go back to my main file and run all of this nothing happens by default but if i now press right we get a rightward movement and the same for left so this is already working very well but now we have a problem that our player is really slow so that's not great and fortunately that can be fixed very easily because what we can do is to multiply this vector with a number and let's say in my case i think i went with 8. enough around this we get a much more appropriate movement so this is working really well and this speed i want to call self.speed because this we are going to influence later on so self.speed is just going to be eight so if we run off this now this works just as before and now with that we can start thinking about how to connect this player to the actual level so right now if i move outside of the screen well the level doesn't move along with it so let's talk about how to create the camera that scrolls the level when the player is moving too far to the left or too far to the right and the first thing you have to understand here is that we have a limitation that in pygame we can only draw on the window and we can't move the window by itself this is always fixed so let me explain what we are going to do instead and let's do this by an example let's say our player wants to move to the right and we want to scroll the level alone with that so essentially what we are going to do if the player reaches a certain point on our window we are going to set our player speed to zero so the player cannot move anymore however at the same time we are going to move the entire level to the right by the speed of the player so that way it seems like the player is moving at the same speed but in reality the player itself is not moving instead the entire level is moving at the same speed in the opposite direction so to the person playing the game it looks identical but the player doesn't actually move and then we can apply the same logic to the left of the screen and then we have our level scroll and that's all the logic you need to implement this so let's jump into our code and let's have a look at this so here i'm back in my main file and i want to go to my level and i can minimize all the methods here and i want to create a new method that i call scroll x and this one only needs one argument and that is self and in here i want to create a couple of variables first although it's not strictly necessary so let me explain it first of all i want to get my player and this is just going to be self.player.sprite then i want to have my player x and this is going to be player.rect x so we're just taking this player here and getting the center of the x position and then finally i want to get my direction x and that is going to be layer dot direction dot so with this information i know my player i know where my player is on the x-coordinate and i also know in what direction my player is going to move in and this is going to help me keep my code a bit more clean and you don't necessarily have to do this but we are going to use a couple of if statements and if you always have to type the entire line here it would get quite long and a bit hard to read so this is making things slightly easier so alright let's start with the left side of the screen so i want to check if my player x so the x position of my player is let's say for now let's say 200 and if that is the case i want to set self dot world shift to 8 and then my player dot speed to 0 and now if i run this function let's place it right below the player i think that makes sense there so self.scroll x and now let's see what happens if i run this code so now i move to left and you can see we have a scrolling although it never stops so it doesn't help us too much yet but it is a start so i can minimize our run function we don't need it anymore and now in here we have to add a couple more things first of all i want an l if statement that if my player x is greater than let's say a thousand for now if that is the case i want self.world shift to be negative eight and again i want to set my player speed to zero and if neither of those things are the case i want to set self dot world shift back to zero and my player speed should be eight and now let's try off this again it is almost going to work so now we are still moving all the way to the left and the problem here is that once we are reaching this point let's say our player is at position 199 if that's the case our level is going to move and our player cannot move anymore so we're always stuck in this position and the level scrolls forever the same if we're moving to the right so we can never really reach this else statement once we ever reach this or this if statement so i want to add another if condition so i only want to move to the left if the player is in this x position and if our direction dot x is below zero which means that we are moving to the left i want to do the opposite for the right side of the screen so greater than zero and now let's try this again now i can almost make this work so i can stop the movement now but i cannot go back to the right and the reason for that is that i made a mistake that this should be player dot speed and now let's try this again so now i can move around and the level scrolls along with my player so that way we have a proper movement and this is working quite well cool now there's one more change i do want to make that right now these numbers here are constants so if our game window became larger or smaller they wouldn't update along with it so i want to make those responsive and this could be a really good exercise for you that i want you guys to add some code to make these two numbers responsive to the size of the window okay i hope you managed now the first thing we are going to need is the actual width of our game window so in our settings file this screen.whip so i have to import it so screen with from our settings and really what i want to do here is screen with divided by 4. so in my game this would be the coordinate of 300 or 1200 divided by four and now i want to do the same thing for the other condition of the right side of the screen and all i want to do here is to get my screen width again and just from that subtract the same number and let me put it in brackets to make it a bit clearer so this way we get our entire screen width and subtract one quarter of that width you could also get screen width and multiplied by three quarters really the same thing and that if i run all of this again it's still working and this is looking really good so now we could make the game wider or smaller it would still work with the scrolling at least i think it does so alrighty with that we have our basic level setup we have a player and we have some basic scrolling mechanic so that's a really good start now next up we have to start talking about how to make the player jump and move around more really in this and this is going to be a larger section however before addressing the collision i would really like to add some gravity and the jump mechanic to our player and that way we can move around freely on the level and then we cover collisions that should make a bit more sense so for our player let's add some gravity and let's add some jump mechanics and this should be some of the easier parts so i think we can jump straight into the code and let's have a look at this here i am back in my main file and i want to look at my player and in there i want to create a new section and let's call this player movement so we keep the code a bit organized and besides the speed i think the direction should also be in there and i want to add two more variables in here the first one is going to be self.gravity and i have set this to 0.8 and then besides that i want to have something like jump speed or jump height and this one should be a negative number and i've put this to negative 16. and it's negative because i want the player to jump up so with that we have all the basic variables that we are going to need and now that we have these variables i can add a method to apply the gravity and let's call it apply gravity it doesn't need any arguments besides self and all i want to do in here is get myself.direction.y and add plus equal self.gravity so that way our gravity is going to be increasing on every single frame and this number i also want to apply to myself dot rec dot y so plus equals self dot direction dot y and now in my update method i can just call self dot apply gravity and we should be having gravity let's try and we are indeed falling down so this is working quite well but obviously our player disappears immediately so this by itself isn't great because we can't counteract it so for that i want to create a jump method for this one all i want to do is get myself.direction.y and set this to myself.jumpspeed and then this i want to call whenever i press the space button and this could actually be a really good exercise for you that i want you guys to add some code to only call this function when the player presses space or whatever key you feel is appropriate for jump it doesn't really matter so to get the jump button in my case i need my get input method and i have to add another if statement that if keys pi game dot k space is the case so we're pressing the space button then i want to call myself dot jump and that should be all we needed so let's try this now and we have indeed a jump mechanic and our level scroll is still working just fine so this is looking quite good so now we have a basic player movement and this works as intended but it's not great because we don't have any collisions and that we can start working on now because the logic here gets very simple once you understand it but if you don't understand it is going to be a nightmare so let's talk about collisions in 2d platformers and if you ever try to make a platformer yourself you probably already know what the problem is that pygame can detect when a collision occurs very reliably however it cannot tell you where that collision occurred so for example if you have a player colliding with the top right of a tile you know there's a collision but you don't really know if this was on the top or on the right or on both and it's really difficult to work with this and unless you know how to work around this problem you will continuously end up with a problem where you collide with something on the left and then pygam puts your player on the top of a block that you could light it with which would obviously break any kind of game so the major problem here is how to separate the x and the y dimension of our movement and well the obvious solution is that you have to separate your vertical and horizontal movement and i know that sounds a bit silly but really all you have to do is to first apply all the vertical movement then apply vertical collisions then you apply the horizontal movement and then you apply the horizontal collisions and that way you can very reliably figure out which block you're colliding with and to really emphasize this i'm going to create two methods the first one is going to be for the vertical movement and collision and the second one is for the horizontal movement and collision and that is going to make it explicit what we are going to work with and i guess another minor point is that this movement is going to happen in our level class not inside of our player and the only reason for that is that it's much easier to get all the collisions inside of our level so with that let's actually jump into this and let's see how far we get so here i am back in my player class and i want to open my level.pi and let me minimize all this stuff in here we are not going to need any of this and first thing i want to do is to create another method that i am going to call horizontal movement collision and we don't need any arguments and in here we have to do a couple of different things now first of all i'm going to target my player character a lot so just like for the scroll method i'm going to get self.player.sprite and this is going to save me quite a bit of typing later on now the first thing i want to do is to actually apply the horizontal movement and for now we have done this inside of our player class but i want to take this out of here and do this inside of the level class and now we have to make a couple of changes so all the self should just be player and let's actually put all of this inside of our run method as well let's put it right below play update and i think scroll x should actually be in there as well actually i think scroll x should be in the level tiles and then the player gets all of this so self dot horizontal movement collision and that feels like a better setup and let's try if all of this actually still works so let me run the main file and i can still move around even though our movement happens now inside of the level of class but cool this is still working and now with that i can actually look at the collisions so i want to cycle through all of the tiles i could potentially collide with so for sprite in self.tai i think i called it let's double check yep self.tiles and dot sprites to actually get all of the sprites and now i want to check if sprite dot collide wrecked with my player dot right so we are going to use the collide rect function and you might be asking yourself why are we not going to use the sprite collisions and the reason for that is that i want to have access to the rectangle of each of the tiles and if we use sprite collide this would be quite a bit more difficult not impossible but a bit less convenient i would say and this one is still perfectly fine so now we know if the player is colliding with one of the tiles or one of the rectangles inside of the tiles and now i have to figure out if this is on the left or on the right side of the player and the easiest way to figure that one out is to just look at the direction of the player so if our player is moving left and we have a collision we are probably colliding with something on our left and then the same for the right side and that way all we need is a single if statement that if my player.direction.x [Music] is let's say smaller than 0 which means we are moving left then i want to target my player.wrecked.left and put it in sprite.rect.right and if that is not the case so l if player.direction.x is greater than zero then i want to do the exact opposite so player.rect.right is equal to sprite dot direct dot left and let me go over what's happening here here we have the first part and we are just applying the horizontal movement this one is super easy once we have that we are looking through all of the sprites and we check if we have a rec collision and then we check if our rectangles collide so if we have a player and one of the tiles if they are colliding now if that is the case we are checking if our player is moving left or moving right and if these two conditions work so if we have a collision and a movement let's say for this case here then we know the collision is going to happen on the left side of the player so then we can move the player exactly on the right side of the obstacle it collided with and that way we well placed the character in the exact right spot and that for the most basic logic was all we needed so let's actually try this so there you can kind of see it and it's let me go to this side so there you can see that if i have this wall i cannot go further to the right now obviously if i jump on top of something we get some very weird behavior because well we don't have a vertical collision yet but the horizontal one works just fine so with this one method we have covered our horizontal movements and collisions and now all we have to do is do the same thing for the vertical movement and collision and this i think is going to be really good exercise for you that i want you to try to understand this entire method and copy it for the vertical movement and see how far you get and the vertical one is going to be slightly different because we have gravity but it's not all that different so i'm confident you can do it all right so let's try together now first of all i need to create the method itself so vertical movement collision i am not entirely sure if this is a good name or not i am awful with variable naming all right so first of all again i want to get my player itself to save me some write and first of all we have to apply the vertical movement and for that i want to get my player and now i want to apply gravity not in the player itself but instead in this vertical movement and so i have to change self to player and now we are calling jump inside of the player and this is still working just fine so we can leave it in here that's the one exception and now that we have covered that i can just copy this entire block of code and paste it in here and the only change i now have to make is to change this x to a y and for this one let me start with the downward movement first that's i think the most common one it should be on top of the if statement and if that is the case i want to get the bottom of my player and set it to sprite.rec.top and then the other case if we are moving upwards i want to get the top of my player and set it to the bottom of whatever we are colliding with and well now all we have to do is to call self dot vertical movement collision and let's see if this is working and now if i run all of this this seems to be working however if i now stand for a bit at some point our player is going to disappear and the reason for that is quite obvious i think that whenever we call apply gravity we are increasing the gravity and we never stop doing that unless we jump so if we just keep on standing on the ground the gravity is going to increase more and more and at some point it's so large that the movement would jump right over all of the tiles and that way it would fall apart but that fortunately is a very easy problem to overcome that in this line here when we check if our player is standing on top of the tile if that is the case i want to get my player.direction.y and set this to zero and this way after we are increasing the gravity and we have hit a bottom tile then we set the direction y to zero so the gravity is cancelled out and now let's try this again this should work much better and now we have collisions that work pretty good and also works for the top ah there's another problem so here you can see if we hit a top ceiling we kind of don't fall down immediately and the reason for that if i go back to level is that if we hit this if statement here we have hit a ceiling but our direction.y is still negative so it keeps on increasing but it's always being placed in this position again so it doesn't go any higher but it also doesn't go downwards again and well to fix that part all we have to do is to call the same line we did for the bottom so this way we are cancelling out any negative y-direction movement we have and now let's try this and i can jump around this still works fine and now if i hit the ceiling i go down immediately again and well with that we have a basic 2d movement and this is actually working really well okay except i am terrible at playing video games so with these two methods you should be having a pretty good vertical movement and that is also finishing up the first part of this tutorial so now we have our basic 2d movement however there are quite a few more things that we have to do and we have to do all of those for one reason that when we animate our player we have to know quite a few bits of information for example we have to know if the player is moving against the left or right wall we have to know if the player is on the ground or against the ceiling we also need to know if the player is looking left or right and later on for the particle effects i also want to know when the player is jumping and when the player has landed and there are lots of different pieces of information that we need to make this work and this is still very much connected to the collision detection in our 2d level which is why i've put those two parts into one tutorial because they are very tightly linked to each other however for the player animation there's one major problem we first have to address and that is that we have to work with quite a few different images i think it's about 20 in total so for example the run animation has 6 frames by itself and this is somewhat similar for all the other animations as well so before we can talk about the animation we first have to figure out how to import all of this and how to store it efficiently in pygame and in my case there are two key things you have to understand for this tutorial number one is that i have stored all of the animations inside of a dictionary so each key is the style of animation and then the value is a list with the individual images and each image is already on a surface and that way we can access the different animations quite easily now for port number two i have used another module to import all of these images and that module is called os and this gives you quite a few useful features and i'm going to explain that when we actually use it but before that let's go for this step by step and let's start with step number one that i want to create a dictionary with all the animations that the player is going to have so here i'm back in the code and i want to get to my player and let me minimize all of the methods it's not that many and in here let me put it right below the indent method i want to create another method and let's call this one import character assets it doesn't need any arguments and now we need a few bits of information the first one is i call this character path and this is just a path to the folder where all the photos with the different frames are in and this is just going to be a string so we go one fold up then we go to a folder called graph fix and then there there's a folder called character and then i want another slash because inside of that folder there are a couple more folders that each hold a couple of animation frames and i want to target each of these folders now next up i actually want to create my dictionary and i call this one animations and this is going to be a dictionary and in here i just want to create a couple of lists so my first animation is idle and this is just for now going to be an empty list then the second one i called run and they're all going to be empty lists the third one is going to be jump also an empty list and then finally we have fall and well that is all we needed and now a really important thing to consider here is that the name of the animation is the exact same as the folder inside of this folder so we have a folder for idle a folder for run a folder for jump and a folder for fall and that is incredibly important because we are using those names to access this folder so make sure to name them appropriately and well let's actually work on this right now so i want to cycle for animation in self dot animations dot keys and from that information i want to get a full path and the full path is just going to be my character path plus the animation so really all i'm going to do let me draw this actually is that this character path here is just this line here and this animation is one of these four animations and literally all i'm going to do is i'm going to attach this right at the end and that way we get to access the files inside of one folder that's literally all that's happening here and now all i want to do is to target my animations folder and then get the animation i want to work on so now we can target one of these four items and now what i want to do is use this path here to import all of the images that are inside of this folder and this is a function i have called import folder and it only takes a single argument and that's the full path so now we have to create this function here and i have put that one in its own file so let me create a new one and i've called this one support dot pi and in here i want to create define import folder and it needs a path and nothing else and now back in my player i want to from support import import folder so we can actually use it so now we have to figure out how we can actually import all of the images inside of a folder and for that we come to the part i talked about earlier that we use a specific module that python has available that can be super useful and that module is called os os gives you access to lots of system functionalities and one of those is to go for a file system and the specific method for that is called walk so i don't want to import all of os instead i want to go from os import walk so let's talk about how to use walk essentially you're always going to use block inside of a for loop because work is going to return three different things it returns a directory path the directory name and the file names inside of the folder that you specified and then inside of the for loop you can work with that information that's kind of all that's happening here and in my case i am not going to need directory path or directory names because they're not relevant for me all i care about are the file names of the images inside of that folder so i'm going to ignore the first two arguments that are going to be returned but let's have a look at this encode so let's start by creating the for loop so for let's call this information for now in walk and walk only needs one argument and that's a path and now in here let's just print information and let's just see what's going to happen and actually from my player class i can copy all of this and run the function by itself so i want to import folder and the file path is this and let's go with run and let's see what we get and there in the bottom you can see we have gotten a tuple with the name of a folder so the information we already have so we don't need it after that we will get what's called directory paths which is just a fancy name for folders inside of that folder and this folder doesn't have any subfolders so this one is also relevant to us now the important information now is that these are all the actual files inside of that folder and this is what we actually care about so this is what i am going to focus on so when we create the for loop let me get rid of all of this there are three bits of information let's call it one two and three and only three is the important one so this is going to be the image file let's call it and for one and two so the directory name i'm just gonna put an underscore and for two i'm gonna put two underscores so i indicate that i don't care about what's being returned here and with that i get access to all of my image files but now i have to figure out what i actually want to do with them and what i want to do with them is i want to import them and put them into a surface and then i want to put all of these surfaces into one list and return this entire list of surfaces at the end of the function and this could actually be a really good exercise for you try to do all of that and see how far you get all right so the first one we are going to need is we need a list and i call this one surface list and this is literally just an empty list now next up there are two important things for the next step and this is all about this image file and as a matter of fact i should have named this better because this is image files and essentially what we are getting back here is a list with strings so we're getting a list with the name of each file and for now i want to cycle through this list so for let's call it image in image files and that way each of these images is going to be the name of the image we want to target and that i can use to create a full path and default path is just going to be my path that i already have then plus a slash and then plus the image and that way we have the entire path from our code to our image and once we have that information we can just use let's create another variable that are called image surface and now we can just use pygame.image.load and pass in the full path and as always we need convert alpha to make this work properly and then once we have that i just want to get my surface list dot append and then get my image surface oh and a really important thing we have to import pygame and now we are almost done the last thing we need is to return the surface list and that way we should be good to go this is all we needed to cycle through any folder and import all of the images now an important thing to remember here is that you really have to make sure that inside of each folder you only have image files so png or jpeg if you have literally anything else pygam will still try to import it and put it into a surface and for anything that's not an image you are going to get an error so you either want to be careful here or you want to create some extra checks it's really up to you in my case i'm not going to worry too much about it but alright so with that we have our basic setup now i can go back to my player and now this function here should be working just fine and let's actually run this function here at the beginning of our player method so self dot import character assets and now in the main file let's try this and we don't get an error message that's a good sign so with that we have all the images that we are going to need in a file format that we can access however we still can't see it because all we are doing in our player we use these two lines here to create a red rectangle that well looks kind of boring so i have to use all of these animations and actually play them and for that we are going to need a couple more arguments and that is going to bring us to a whole new topic and that is animations and pie game and i have actually created a whole separate video on this ages ago and i'm going to use the same logic here so you don't have to watch it but in the most basic sense all we are really going to do is that we are going to change self.image very very fast so for example if our player is idling we are picking one frame of it every few milliseconds and assigning that to self.image and that's literally all we are going to do it's not that complicated when it gets down to it so well let's go through it step by step so here i'm back in the code and there are two more attributes that i do want to include the first one is self.frame index and by default this is going to be zero and this is going to be the number we used later on to pick one of these animation frames so once we have jump for example we are going to pick one of the frames in here and besides that the other attribute is animation underscore speed and i set mine to 0.15 and well this tells us how fast our animation is going to update and now once i have that i can get rid of self.image.fill and replace my pygame.surface with a specific animation or for now a specific frame from this cellphone animations so i want to get myself dot animations and for now target one animation in here and i went with idle so this will give us a list of different surfaces and i want to pick the first one of it so self dot frame index and that way we should now be seeing a pirate once we run this game let's try and there we can see our main character it doesn't animate at all right now but well it is working reasonably well so we can see something cool so we know this is working but now obviously this doesn't look particularly great because we have no animations whatsoever so this is going to be what we will start working on right now and inside of my player class i want to create another method and let me put it right below importing the character assets and i've called this one self.animate and in here for now we have a little bit of a problem because to play the different kinds of animations so if i want to play idle run jump or fall i first need to know what i actually have to play so i have to know if the player is running jumping falling or idling and right now i don't really know any of that and this is something we'll address in just a second but for now i want to get a basic animation so what i want to do for now is just to cycle for the run animation that should be the one you can see the best so inside of animate i first want to simplify things a bit so i'm going to create another variable that i just call animation and all i want to do in here is get myself dot animations and for now pick run so later on in here we are not going to pick a specific key from this dictionary instead we're going to make this more dynamic but for now this one should be fine and now the first thing you have to do is to loop over the frame index and really all that means is i want to get myself dot frame index and add plus equal self dot animation speed so if i go up a tiny bit i want to get this number here and add the animation speed towards it that's literally all that's happening here and then once i have that i want to check if myself dot frame index is greater or equal to the length of my animation this one then i want to set self dot frame index back to zero so what does that mean let me go up a tiny bit again and we are looking right now at these two numbers here and on every frame of our game we're going to add 0.15 towards this frame index and essentially what i want to do and let me actually illustrate this so we start with 0 then we have 0.15 then we have 0.3 0.45 0.6 0.7 five then zero point nine and at some point we get let me put it in here one point zero five and this is going to keep on going for basically forever and now the problem is inside of each of these lists we only have a limited amount of surfaces and essentially what i want to do is that if i am on zero and for any number that comes after 0 i always want to pick the element 0. however once we are reaching let me use a different color for that once we reach the number 1 i want to pick the item with the index 1 from the list and this way i can control the speed of the animation quite well now the problem we have here is that by itself this number would keep on increasing so after about a second we would already be at possibly something like 10 and there are not 10 elements inside of run so we have to make sure once this number is being greater than the amount of items inside of this list here we want to set this back to zero and that way we get a loopable animation that works really really well so i hope that makes sense and well that's all we have done here and now once we have that all we have to do is to target self.image and set this to the animation and from there pick self dot frame index however this by itself would not work because to pick an item from this list we need an integer and this right now is a double so we have to convert it to an integer and now this should be working so let's try to call it and see what happens so self dot animate and let me run the code and there you can see the animation obviously it still doesn't work and also we get a bit of a problem with the collisions we're gonna fix that in just a bit and also if i move to the right okay it still works mostly well but there are some minor problems with the collisions but now we have our basic animation so now we have a basic animation and we can also play different kinds of animation so in here if i change this run to idle and run this code again i can see that our player is playing a different animation so this is already working reasonably well and the main problem we have now is to figure out what the status of the player is so is the player on the bottom is the player running is the player against the wall is the player falling all of that kind of stuff is what we have to figure out now and once we have that status we can play different animations so we have to think about how to get different states of the player and there are four different states that we have to figure out we have to figure out jump for run and idle and to figure all of this out the direction of the player is incredibly useful because we can use the x and the y information inside of the vector to tell what the player is doing and let me actually jump into the code and explain how this is useful so here we are back in the code and i want to have a look at the player and let me minimize all of this and i want to create a new method let's put it below get input and this one is going to be get status doesn't need any arguments and in here i want to figure out what the player is doing so is the player jumping running falling or idling and for that this self.direction is really useful so for example if the direction.y is negative we know the player is going upwards and the only reason for the player to go upwards is if the player is jumping so if the direction of y is negative we know the player is jumping and by the same logic if the direction is greater than zero we know the player is falling and if direction.y is zero then we know the player is standing on the floor and once we have that we know we can check for x to know if the player is running left or right and well that's all we really need here so let me actually implement this the first thing i want to figure out is the y-axis so if self self.direction.y is smaller than 0. so our player is going upwards and if that is the case i want to create self.status and the status here would then be jump and actually let me put in the init method let's call it player status and here i want to create layer.status and by default this should be idle so we already know when the player is being initiated that we have one state so we don't get an error so now we know if the y direction is negative we are jumping now we need an l if statement that self.direction dot y is greater than zero and if that is the case i want to put myself dot status to fall because if the direction is greater than zero we know the player is going downwards and the only reason for the player to go downwards is to fall so this would also be an easy status now if neither of these cases is true so if direction.y is equal to zero we know the player is standing so i can add an else statement in here and now i want to figure out if the player is running or idling and this could be a really good exercise for you try to think how to approach a direction.x to figure out if the player is running or not all right so all we really need in here is if self.direction.x is different from zero because if we are running we have a moving left or right and if f of this is the case we are going to run so if direction.x is different from 0 self.status is going to be run and then finally if none of these are the case i want self.status is equal to idle and that way i already have my basic status there is one problem but i'm going to show what that does in a second but first of all before i run self.animate i want to get self.getstatus and now in my animate function i can replace this idle with self.status and now we should be getting different animations at least kind of so when i jump and fall this is working however if i'm on the floor we get something very very weird and well so let me explain what the problem is essentially if i go to level and to the vertical movement the problem we have happens essentially in all of these lines here and basically what the problem is that if our player is standing on the floor so let me actually draw this so here we have our player and here we have the floor now every time we are applying this movement collision we are moving the player down by the amount of gravity so my case i think this was 0.8 and after we have done that we are checking if the player is overlapping with something and if that is the case we are moving the player back to exactly on top of this tile so that way it looks like the player is on top of the tile all the time but that's not really the case instead what is actually happening is that the player is being moved on top of this on every single frame once it overlaps and this is working reasonably well the one problem with this approach however is that our self.direction.y so this number here is never going to be just 0. it is always going to be either 0 or positive 0.8 and as a consequence in our player if we are checking for this number here sometimes it is zero sometimes it is greater than zero even though we are on the floor so pi game plays either fall or run or idle really fast between each other which then ends up looking very very silly so how could we address this and well the answer is actually really simple here all we have to do is change this zero to a one or more precisely any number that is greater than this gravity here so that way we are only going to play the fall animation if our player falls at a certain speed and that way we are fixing all of this so now our player looks much better and all the animations play pretty well so this is already looking quite good cool but now we have a couple more problems the first one is that our player always looks to the right it never looks to the left and also if you look a bit more closely at the animation it doesn't actually work out perfectly because our player is well it looks like it's jumping for a tiny bit and that's not how the animation is supposed to be played the feet are always supposed to touch the floor i'm going to explain in a bit how to fix that but first of all let's work on the direction and for the direction i want to create another attribute and let's call this self dot facing right that's the default so by default this is going to be true so if this is true i want my player to face to the right and if it is false i want the player to face to the left so now we have to figure out how can i change this attribute here and well it's actually quite easy when i run my get.input and i press either left or right then i want to change these values so self dot facing right if i press right is true and if i press left then it's going to be false so we are essentially changing this value if the player is pressing left or right that's all we needed and now inside of our animate function so this one here i can add another if statement so if self dot facing right if that is the case self.image should just be what it used to be however if that is not the case i want to add an else statement and in here i want to flip the image and let me reorganize this code a tiny bit so let me copy all of this and place it right on top of the if statement so right now we're just creating a local variable inside of this method and if we are facing right we're just assigning this image to self.image so no change so far however if we're facing to the left i want to create another variable that i called flipped image and to flip an image all we need is pygame dot transform dot flip and then here we need a surface so mic is image and then we need two boolean statements the first one is if you want to flip this image horizontally the second one if you want to flip it vertically and in my case this should be true and false so this true here is for the flip and the x-axis and this value here is for the flip and the y-axis and i only want to flip my surface in the x-axis i don't want to rotate it around the vertical axis and now once i have that i want to get myself.image is equal to the flipped image so that should be all we needed let's try this and now if i press left this is working pretty well cool so now our player is actually moving in the right direction oh well it's facing in the right direction but this is then already looking so much better so with that we have our basic animations however they don't work as well as they should and this is something i've shown earlier so for example if our player is just idling it looks like it's jumping a tiny bit so the feet are slightly lifting off the floor and a similar thing can see for basically all of the animations and this is not intended in the animation that's a problem in our code and the problem here is that each of the surfaces has a slightly different dimension so each image inside of the idle frames has a slightly different height but our rectangle is always the same size and we also always have the same origin point and since pygame always puts the surface on the top left of the rectangle if the surface is too small for the rectangle it looks like the surface is floating a tiny bit so to fix this we have to do two different things number one we have to figure out what the status of the player is in terms of what the player is colliding with so is the player on the ground on the left or right wall or on a ceiling and once we have that information we want to get a new rectangle and place the origin point of that rectangle to wherever the collision point happens to be so let's go back into the code and let's have a look at this so i first want to get back to my player and first of all i want to add a few more status information i want to have an attribute of self dot on ground that is false by default then i want self dot on ceiling then self dot on left which is false and self dot on right which is also false so this is the information i need to place the rectangle properly after i have animated all of this so now we have to figure out how to get these four bits of information and they all need slightly different approaches so let's go through this step by step and let's start with the easier one self. on ground and self.unceiling because all we really need to figure out is in our level we already have the vertical movement open if this condition here is true we know the player is on the floor and if this condition here is true we know the players on the ceiling so all i need to do here is player dot on ground is equal to true and for the else statement player dot on ceiling is going to be true here however we have to add a bit more and let me explain why let's say for this line here after our player is touching the floor we set on ground to true but the problem now is if our player is jumping this on ground is still going to be true even though we know the player is in the air so we have no way to switch off on ground again and for that we have to add another few if statements and i want to check if player dot on ground is equal to true and now i want to check if the player is jumping or falling so if i play as underground and player.direction.y is smaller than 0 or player.direction.y is greater than 1. and if all of that evaluates to true player dot on ground is gonna be false so really all we're doing in here is we are checking once the player is on the floor and the player is jumping or falling then the player cannot be on the floor anymore and then we can do the same thing for unsealing so if player on ceiling and now we have to add another condition and in here obviously if the player is on a ceiling he can't jump anymore so we don't have to check if direction.y is below zero however we do want to check if the player starts falling again so really all i have to check in here is if player.direction.y is greater than zero and if that is the case i want to get my player on ceiling and set this to false and that way we should cover pretty much all of the on ground and on ceiling so now we can use that information back in our player to set the animation properly so we have to effectively add a whole nother section that i called set the rectangle and in here for now i just want to check if self dot on ground if that is the case i want to create a new self.rectangle and this is going to be self.image dot get wrecked and in here for now i just want to set the mid for tim and where that is going to be is self dot wrecked dot mid bottom as well and if that is not the case and instead we are self dot on ceiling then self.rect let me copy the entire statement before is going to be self dot mid top is going to be the mid top of the previous rectangle and it's probably a good idea to have another else statement in here just in case something goes wrong and well all i want to do in here is to get my new rectangle is going to be at the center of my old rectangle and now we can try to run all of this and this is already working so now we can see the animation looks significantly better because our player isn't levitating on the floor anymore and well we can still jump around and here you can see kind of a small problem that our player sometimes jumps up on the ceiling it's kind of hard to replicate can i get this no okay it's very situational but i'm going to explain the bug you have just seen in a bit more detail but there's one more thing i do want to work on because right now our player can jump on in the air which is not the point of the game so i want to give some extra code that the player can only jump if he is on the floor and this could be a really good exercise for you try to add some code that the player can only jump if he or she is on the floor all right so if i go back to my player and i minimize the animate function and the init function i want to check at my get input and in here i have a check that if i press the space button and literally all i have to do is i want to check and self dot on ground so the player is only going to jump if we are pressing space and if the player is on the ground and only if both of those are true the player is going to jump so now let's try this again and now even though i keep on pressing space the player only jumps once once he hits the ground and well this is then feeling much more natural and we are making some really good progress to a proper 2d platformer now actually if i run this again maybe i can make it happen no it's kind of difficult to work out yeah there we go so now you have seen a small bug in our code and let me explain why that happened and let me go to a player to actually explain this properly so we need the animate function this one so the problem we have right now is that whenever we are moving the player around we are also getting a new rectangle at least whenever we are on the ground but now the problem is for the idle animation some images are wider than others so we would get a rectangle with a different dimension on every single cycle of our game loop and for most situations this would be fine however if we are touching a wall and updating our surface there is a small chance now that there is an overlap because we have a wider surface that creates a wider rectangle and then pygame gets confused and thinks that the player is overlapping on the bottom and as a consequence it places the player on top of the tile which then makes the game look kind of buggy and the way to fix this is by checking if we are colliding with the left or right wall and i already have the attributes that's on left and on right those to check if we are colliding with a wall on the left or on the right but getting that information is slightly different compared to the vertical ones so let me go through it step by step so here i'm back in my player and i want to go to my level and here we already have our player on ground and player on ceiling but i want to look at the horizontal movement and in here we have to start in a similar way compared to our vertical movement so i have to check if i'm moving left and colliding that i want to check self dot on left to true and if i'm moving to the right and colliding i want to get my player dot on right and set that one to true so now we can switch on the attributes if the player is colliding with a left or right wall however the problem now is turning these attributes off or to set them to false is kind of more complicated so let me explain the problem here let's say our player is moving to the right and then is colliding with an obstacle so we set player on right to true but now the player keeps on pressing right and jumps in this case the player is not colliding with a wall anymore but player dot on right is still set to be true as a consequence we can't really use our direction.x to figure out if the player stopped colliding with a wall because it might happen that the player just keeps on pressing to the right and then jumps over a wall so we have to figure out something else and here's how i fixed that every time our player is colliding with a horizontal wall we are getting the x position of the player and then to turn off player on left or play on right we are checking if we are exceeding this point either positively or negatively and that way we can tell if we are moving past the point of collision so you kind of have to think about the solution here a little bit but i hope it makes sense but let's implement all of this here i'm back in the level and the first thing i want to do is to create a new attribute right at the top and this is going to be self dot current x and by default let's put it at zero it really doesn't matter what it is and now if we are colliding with a left or right wall i want to get myself dot current and set it to player.rect.left in this case and if we are colliding with the right wall this should be the right side so this way we can tell the exposition of where the collision has occurred and we are storing that information in self dot current x and now let's say for example for the right side of the player so this one here so here is the player and let's say here we have a wall on the right side now as soon as there is a collision we are checking this point here or well rather we are checking the entire yellow line and really all i want to check is that if the right side of the player is further than that point and if that is the case i can safely assume that a player is not touching this right wall here anymore and this is what this current x is for we are really just checking what the collision point is um again the name probably isn't perfect but i guess it's good enough so with that i can add a couple of if statements like i have done here to figure out when to set on right and on left back to false and let's start with the left side so if player on left and now we need a large if statement so what i want to check is player.rect.left is smaller than self.currentx and if i have that information i know the players exceeded the obstacles so i know it's not touching that obstacle anymore and if that is the case i can set my player dot on left back to false however there's one more situation that if we are touching a wall on the left it's perfectly possible that we are just moving to the right and that way would also stop touching the left wall so what i also have to check is an or statement in here that player.direction.x is greater or equal to zero so that way if we are touching a wall and stop moving to the left or moving to the right we know that we are not touching the left wall anymore and if that's the case we can set on left to false and now same thing if player dot on right and now we can basically copy all of this and make some changes so first of all this is player dot on right and is greater than self.currentx and this one should be smaller or equal than zero so we are moving left or we're not moving altogether and if that is the case player dot on right is equal to false and now with that we have all the basic information we need but first of all let's run this game and see if this is working okay for now we haven't implemented the new rectangles yet the main thing i'm looking for is that we don't get an error message and that looks pretty good so cool this is working for now so that way we know that let me go to my player we can get these information reliably so we know what the player is colliding with so now we have to use all of that in here to actually set the rectangle or more precisely to set the origin of the rectangle and this is going to be quite a few different if statements so let me go through them one by one first of all if the player is on the ground is one thing but i also want to check if self dot let's start with on right so if the player is on the ground and touching something on the right if that is the case i want to set the bottom right of the player and the position should be where the previous bottom right rectangle was and now we have to do the same thing for the ground and the left side so on left and well now i can just copy all of this statement here and change bottom right to bottom left and same thing with bottom left here so now we have the situation where the players on the ground and colliding with the right wall he is on the ground colliding with the left wall but i also want to have the situation where the player is only on the ground and not on anything else and well if that's the case i just want to get my mid bottom again the same thing i had in the beginning and now that way we are covering all the possible scenarios for the player being on the ground now we have to do the same thing for unsealing and well in my case i am going to copy all of this so i don't have to type too much so this has to be an l if and now we have to change a few things this should be on ceiling and we are on the right and now we don't want to check for the bottom right we want to check for the top right and same thing with the previous rectangle top right then we have to do the same thing for the left side so this would now be the top left and the top left here as well and then finally just for the plane on ceiling now i would just look at the mid top and the mid top for the previous rectangle and that way we have covered the possible collisions now let's try all of this and see if this is working and there we go now the bug doesn't seem to appear anymore and this we can just keep on moving around and now the animations actually look quite a bit better so well this is then helping our player animations quite substantially so all right we are making some really good progress and all right so with that we have covered the entire player i know the final part did get a bit more complex so i hope it made sense if you are completely confused just let me know in the comments i guess but now that we have the player the last one we have to do for this tutorial is to add the particle effects and in here we have three different kinds of particles we have run particles we have jump particles and we have full particles and the run particles are very easy to get we are basically doing the same animation we have done for the player except with different sprites however for the jump and the fall we have to create a slightly different kind of animation but let's do this step by step and let's start with the run animation and let's go straight into the code and let's have a look at this here i'm back in the code and i want to have a look at my player and let me minimize all of the methods we had so far so there's a bit more space and now the first thing that i want to do is to import the dust animation frames the ones for the run particles and let's call this word import dust run [Music] particles and in here i want to create a new attribute and let's call it dust run partic quilts and really all i have to do in here is to get my import folder function again and now to pass in the path and the path is going to be this and we want to look at the run particles and now in the unit method let's call it right at the beginning i think it makes the most sense in there so import does run particles and let's try if this is working we don't get an error that looks pretty good so now we have the list of images that we need next up we have to figure out how to animate through them and basically what we are going to do let me go to the animation part so effectively what we are going to do is we are going to copy this stuff here but with two differences number one we don't use the player animation instead we use a dust animation so the one we have just imported and number two we are not going to put all of this on self.image instead we are going to pass the display surface into our player and then blitz straight onto the display surface and that is because the dust particles are not supposed to be on the player they are supposed to be on the actual background but those differences in terms of code are actually fairly minor so most of this logic is still going to apply but let me go through this one by one so let me minimize this and now actually let me actually put this a bit further down and let's create a new section here with a comment and let's call it dust particles that's probably a bit easier to read and now essentially i'm going to copy those two attributes here so we have our frame index and our animation speed and let's call these two the dust frame index and the dust animation speed and this would allow us to create a basic animation but so far we don't have anywhere to put this animation on so what i want to have is i want to get this screen display surface available in my player and well to achieve that i have to create a new parameter let's call it surface and this surface is going to be self.display surface ideally spelling this correctly and this is going to be surface and now when i create this player inside of inside of my level class and inside of setup level and down here i also want to pass in my display surface and i already have my display surface it's this one here so all i have to pass on here is self dot display surface and that way my player is going to have the display surface available and just to make sure all of this is working let's run the code it starts properly so this seems to be working just fine so now i have all the basic parts i need to create a basic animation so i can close or minimize all of this and let's say right below our animate function i want to create dust animation actually let's call it let's call it run dust animation and in here we are going to add the code for the animation however we first want to check if we even want to play the animation because if our player is jumping or falling or idling there shouldn't be the run animation dust particles so i first want to check if self.status is equal to run and i guess just to be sure and self dot on ground not particularly necessary but i guess it can't hurt and if that is the case i want to get myself dot dust frame index that one and towards that i want to get myself dot dust frame dust animation speed that one and now with that if self.dust frame index is greater or equal to the length of self dot dust run particles then i want to set self.dust frame index back to 0. so this so far is exactly the same thing we have done here except with different numbers and we are going to continue doing the same thing because now i want to set it dust and that is going to be self.just run particles and in here we're going to get the integer of self dot dust frame index so if i open the animate function again this is the equivalent of this line here so so far literally nothing new and next up we have to again figure out if the player is moving to the left or to the right because that informs if the dust particles are left or right and well we have that information if self dot facing right so for players moving to the right i want to get myself dot display surface and now i want to blit it in a certain position so i want to get my dust partic kill and now i have to figure out the position and actually let me place this in its own separate variable so it's a bit easier to read and in here i need an x and a y variable and in here really all i want to get is self dot wrecked dot bottom left so if our player is moving to the right i want to spawn the dust particles in the bottom left of the player and then draw them on the display surface should be quite straightforward and let's actually run all of this and see if this is working so after animate i also want self.run dust animation and if i run this we are getting an error because this double colon shouldn't be there and now let's run the code and if i move to the right we are getting some dust particles although not in the right position so they need some kind of offset but if we move to the left nothing's going to happen so we are almost there so let me go back to my player and the problem right now is that the bottom left is too far down so i want to give this bottom left an offset and for that vectors are incredibly useful because literally all i have to do is to get the bottom left of the rectangle and subtract from that pygame.math and in here i can specify x and y variables and subtract that from the bottom left so just to explain why this is useful that this bottom left has an x and a y attribute and if you use anything other than a vector you would have to specifically target the bottom and the left of this position but if you have a vector you can just subtract the entire vector and pi game is automatically subtracting the correct x and correct y values which is making our life significantly easier and now in here you just have to figure out what values are correct or what values make this look good and in my case i went with 6 and 10. and i get those from just experimenting there's no universal value here but now for run we are getting some okay that was terrible let's try this again now if i run to the right we are getting probably looking dust particles so this is working pretty good so now we have figured out if the player is moving to the right next up we have to figure out the left side and this could be a really good exercise for you add the code that if the player is moving to the left we also get dust particles and again this is going to be very similar compared to the animate function that we created earlier all right so first of all i need an else statement so we check if we are moving to the left and then again i want to get a position for my dust particles and now i don't want the bottom left instead i want the bottom right and for these two values here for the offset we are going to check how this is going to look in just a second next up we have to flip this dust particle and let's call this flipped dust particle and then here we need pygame dot transform dot flip and now we want my dust particle i want true and false and again this boolean statement here is for the horizontal flip this one is for the vertical flip and we don't want to flip anything in the vertical axis and then finally i want to get myself dot display surface dot blit this is my flipped dust particle and my position and well that's all we needed let's try this now and if i move right and if i move left this works pretty good and i think the position here actually works quite well so okay down there i have a bit more space and yeah i think with that we have a pretty good animation so with that we have our basic run particles so that's going to be the first particle effect but there are two more one is for landing and one is for jumping and those two are going to work differently compared to the run animation and the major reason why it is different is that the run animation is supposed to be looped so if it goes to the end we want to go back to the start however for the jump and for the land animation we only want to play them once so after we have finished all of our animation frames we want to end the animation so the logic for the animation has to be slightly different and there's another issue we have to work on that for the run animation we want it to be in the same position as the player with a slight offset so it's moving along with the player however the land and the jump animation are supposed to be static and really all that means in practice is that if the player is jumping we want to jump particles to stay on the ground they're not supposed to follow the player so there are a couple of things we do have to address and i approach this by creating a whole new class that i call particle effects and this one takes care of the entire logic and i think it's going to be best if i explain this while i actually create it so let's jump back onto the code and let's have a look at this so here i'm back in my main file and i want to create a new file and let's call it partykilts.pi and from support i want to import import folder because we are going to need it and now i just want to create a class let's call it particle effect and this one is again going to be a sprite so pygam dot sprite dot sprite and in here we are going to need an init method and in here the parameters we need are going to be self the position and the type of animation we want and let me spell all of this properly now we are still going to create an animation so i need my frame index by default this is 0 and self.animation speed and let's set this one to 0.5 and again here this number just choose whatever you think looks good there's no objectively right answer to this and with that covered we have to figure out where we get our images and for that i want to check the type so if the type is going to be let's call it jump then i want to create an attribute that's called self.frames and i want to get my import folder and in there the file path is this one so we still have the same dust particles folder but now the subfolder is jump and in there we have a couple of frames and then along with that i can copy all of this and let's call this one land and in here also the lan folder and with that we have all the information we need to set up a basic frame so self dot image is going to be self dot frames and self dot frame index and then self.rect is going to be self dot image spelled correctly dot get wrecked and then here i just put the center at the position where i want it to be and this is then all we needed for the basic setup and with that we are going to need two methods the first one is the animate method and the second one is going to be the update method and really important here the update method is also going to need an x shift variable because if we are scrolling through the screen this one here is also supposed to move with the level so it needs it as well and well in here all we need is self.animate and self.reg.x plus equal x shift so all we are going to need now is the basic logic of our animation and well we need frame.index plus equals self dot animation speed then if self dot frame index is greater or equal than the length of self dot frames and if that is the case in the past we set self.frameindex back to zero but in this case we don't want to do that because after the frames are run out we want to get rid of all of this so instead what i want to do is self.kill and this way we are destroying the sprite after this animation has run out but if that is not the case so an else statement so if we are still inside the length of our frames then self.image is going to be self dot frames the integer of self dot frame index and that way we have an animation that is going to run only for the duration of the frames if we are running out of frames we are destroying the sprite so this is all we are going to need for the particle effect so now we have two more problems the first one is we have to figure out where to play is so what's the position of this particle effect and the second one is we have to figure out when to play it so we know when the player is jumping or how do we know when the player is landing and well the easier part is to figure out when the player is jumping because we know the player is jumping when let me go to my player and let me go to get input and in here we know the player is jumping if the player presses the jump button so that one is very easy however the problem is that to get the position i have to create this particle inside of our level dot pi file because only in here can we use the world shift argument which we couldn't do inside of our player so both the landing and the jumping particle effect have to be inside of level so let's create the entire thing in here and let me first minimize all of the other methods because this is going to be very hard to read otherwise and let me put it right at the top it doesn't really matter where you put it and then here i want to create a function that i call create jump partic kilts and this one is going to need self and a position and if you're running this method i want to create a new particle effect and let's call it jump particle sprite and this one is just going to be the particle effect so the one we have created in here and to use it we first have to import it so from particles import particle effect and for this one we need a position and the type and let me just copy them over so we can work with this a bit more easily and the type is the easier part because i just called this jump so the same jump we create here for the type but now we are going to need a position and the position we are going to get from the argument that's going to be passed in here you're going to see in a second how that is going to work so now we have a sprite and i want to create a whole new group for that sprite so let me create a new section in the init method and let's call it just let's keep it at dust and then here self dot sprite and this is going to be pygame.sprite.group [Music] single because i only ever want to have one animation of the dust particles because we have a jumping or landing we can't really do both at the same time and now all i want to do is to get myself dot dust sprite and add the jump particle sprite to it and then in the run method of the level let's put it under the player i want to add my dust particles and in here self dot dust sprite dot update and again we need self.world shift and then self.sprite.drawself.displaysurface the exact same thing we have done earlier for the tiles so now so now the one thing we have to figure out is when to call this method here and well we need to get this method inside of our player so that we can call it inside of this part here and well that's quite easily done so in setup level besides the display surface i also want to pass in self.create jump particles and make sure here that you don't call this method so these brackets here should not be there and now inside of the player we get another parameter and that is it's called a create jump part tip kills and again do not add the brackets in here and now in the init method we can create this let's put it here so self dot create jump particles is going to be self dot create jump particles and now what we can do inside of this if statement once we are jumping i also want to call self dot create jump particles and this one now is going to need a position and the position well is going to be fairly easy to get because all i want is self dot wrecked dot mid bottom and well that should be all let's try this now and we are getting an arrow let's have a look oh and this should not be having itself at the beginning so now let's try this again and there we go and now if i press jump we are getting another error so let's have a look at this and in here you can see it in the error message already that pygame.surface object has no attribute get rekt but the rect is a capital r so that's a quite an obvious error so back in the particles this one here should not be capitalized and four times the charm okay or not ah and i forgot now we have the attribute error no sprite g and what that means is we need super dot init and now stress again and there we go now we have our floor particles except now the problem is that these particles are not exactly in the right position and they are in front of the level so two things we have to address the first one well the second one is very easy because what i want to do is to put all of this behind the tiles like this and now if i run this now this looks much better and now we get some nice particles so this is looking pretty good and now i want to give the particles an offset and this offset has to change depending on if the player is facing left or right so let me demonstrate and let me jump here so if we are facing to the right and we are jumping the dust particles are a little bit too far to the right but if i'm facing left the dust particles are a bit too far to the left so they need a different offset depending on if we're looking left or right and well this we can address quite easily so back in the level and when i create the jump particles here and really all i want to do is to check if the player is facing left or right so if player dot sprite dot facing right if that is the case i want the position to have an offset of a certain vector so pygame.math.vector2 and in my case i went with 10 and 5. and again choose whatever value looks good in here there's no objective answer and then we can do the same thing for the other case so else so we are facing left then i want to offset my position and in here i want to add plus equals and then minus five and this is all we needed i think so let's try this now and we are getting an error so let's have a look at level and this should be self dot player there we go and now the jump particles are right below the player and that does feel quite a bit more realistic so alright with that we are making some pretty good progress now for the final part we are going to need the landing particles and those are a tiny bit more difficult to get because we have to figure out some code to understand when the player is landing and this can't be done in a single line of code so let me explain the logic of what we are going to do here inside of our level class we are going to create two new methods and the really important thing here is when we are calling these methods the first method is going to check if the player is on the ground or not and this method is going to run before the vertical movement of the player so we know that before any collision happens if the player is on the ground or not and then we're going to run another method and this one is going to check if the player was in the air before the collision happened and if after the collision the player is on the ground we know there was a landing and if that is the case we can call the landing sprite animation in the very similar way compared to the jump sprite animation so let's create all of this and that's going to be the last part of this tutorial it's it's already a really long video so here we are back in the code and i want to go to my level and then here again let's minimize all of this stuff and we don't need any of this and now i want to create one new method for now and let's call this one get player on ground it needs self and nothing else and really what i want to do in here is if self dot player dot sprite dot on ground and if that is the case i want to create a new attribute and i called this one player on round and this is going to be true and in the init method i want to create this attribute so self dot player on ground and let's say by default this is going to be false and if i go back to it now if that is not the case so else then player on ground is going to be false and this case here if player on ground is false we know the player is in the air so this is the really important bit of information now this by itself might seem kind of pointless but you're going to see in a second why that is useful so now this method here i want to call before we are calling our vertical movement collision so before that self dot get player on ground so before any vertical collision happened i want to know if the players in the ground or not so that's the first step but then the second step is let's call this method create landing dust and this one again needs self and nothing else and now in here we have to check an if statement and first of all we want to check if the player is not on the ground so if not self.player on ground so this attribute we created earlier but now we're also going to check if sprite self.player.sprite.org on ground is true so i just leave it like this and if that is the case i can create a full dust particle and this is just going to be particle effect and in here we need self.player.sprite.rect.mid bottom and then the type of animation is going to be landing i think i called it let's double check and yeah land and then self dot dust sprite dot add fall dust particle and now this method here let me actually copy it this method has to be called after the vertical movement collision so self.greatland particle and now with this logic we basically have two storage variables for the status of the players in the air or not the first one is the player on ground so let's say this is the first one and then the second one we get from the vertical collision so this is our second condition and now really all we are going to check and create land dust is if these two conditions have differed after we have run the collision and if there was a difference between these two we know that the player has landing so the player went from being in the air to being on the ground which is a well it's a landing so that's the entire logic here but there are a couple more things i would like to add and the first one is another part of this if statement and that is and not self dot dust dot sprites so we only want to play all of this if there's no sprite inside of our group single and the reason for that is there's a small chance that if we are landing this animation may play multiple times which would look well silly so this kind of ensures that we are only going to play this animation once and then besides that just like we have done for create jump particles i want to create a bit of an offset so i need another if statement that if self dot player dot sprite dot facing right then i want to create an offset and to offset is going to be pygame.math.vector2 and i went with 10 and 15. again numbers that just look good and then i have an else statement and let me just copy the offset here and the offset here is just going to be negative 10. and now for the position i can just minus the offset and well with that we should be good to go let's try and we are running the code and we're getting an error so let's go through this first of all attribute error level object has no attribute dust so let's have a look at level and the problem here is self.dust is wrong this should be self.dust sprite and yeah down here it's the proper way so that was one arrow and we got another one and here again reading the error messages is really useful that module pygame.math has no attribute vectro to so i just made a typo so let's go back to the level and this is just a simple typo that i also copied so another trial of this and there we go this is looking much better so now i can run around and i get some really nice particle effects for all of this and the entire thing works really well except now i suck at video games and we don't really have a death state in this game but well that's not really the point of this tutorial but that was basically it so i hope that was helpful and i'll see you around
Info
Channel: Clear Code
Views: 73,387
Rating: 4.963367 out of 5
Keywords:
Id: YWN8GcmJ-jA
Channel Id: undefined
Length: 139min 23sec (8363 seconds)
Published: Tue Aug 17 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.