Transcript for:
Tetris Game Development in Java

Hi guys, this is RyiSnow In this video, we create Tetris in Java Well... why Tetris? So I wanted to create something simple for a change since my last project was pretty big so I was wondering what would be a good subject and realized that I had never developed Tetris before I know it's a pretty common subject and many people have done it but I had never done it myself so decided to try it out and it turned out pretty okay so I decided to share it with you guys If you have followed my previous tutorials, especially my 2D tutorial this shouldn't be so difficult and if you have any questions feel free to leave a comment so let's start First, create a project I'm gonna name it Simple Tetris and then create a package and inside of this package, create a class and first we create a window like this so we use a Java class called Jframe I named it window and set the window title here and set the default close operation so we can discard this window properly and the set resizable false so the window size is fixed and cannot be resized by users and the set location to null this means we don't set any specific location on the screen so the window just shows up at the center of the screen and finally set it to visible so the window is going to be visible on the screen without this line, you cannot see the window so make sure to add this line as well now let's create a new class and I'm gonna name this GamePanel and this graph extends JPanel so it has all the functions of this JPanel class First, let's set the screen size so I chose 1280 by 720 so this will be the screen size and here we set this width and height of this GamePanel and set its background color I chose black but if you like you can choose other colors also we don't use any preset layout so we can customize the layout as we want and now let's go back to the main class and add this game panel to the window so this is how we add JPanel to JFrame, this window and when you add a panel and "pack" like this basically, the window adapts to the panel size so let's run the program and check it so now our game panel is set on the window and the size is 1280 by 720 and the background color is black so we can draw stuff on this black panel so let's go back to the GamePanel and now we create a game loop so game Loop is for updating the screen at regular intervals and in this game we set 60 FPS which means we update and draw the screen 60 times per second so let's set FPS and also decree a thread so we use this thread class to run our game loop and to use a thread we need to implement a Runnable interface so if you have watched my previous videos I know this sounds redundant but please bear with me I just wanna provide some basic information in case people who have never watched my videos are watching this one and when you implement Runnable you need to add a run method so here it says, add unimplemented method then we get this run method and then we start this thread so any method name is fine init, launch, start... I chose launchGame because we launch the game here by activating this thread so we instantiate our gameThread and start it and when a thread starts it automatically calls this run method and before working on this run method we add two methods so in every game loop we basically do two things: update and draw update means update object positions, X and Y or maybe your score so basically all kinds of number information and the draw means draw so draw objects or UI text, stuff like that so now let's create a game loop in this run method and for the game Loop uh I'm sorry but I won't explain how it works in this video because the game loop itself can be the topic of a single video also I've already made a detailed game loop explanation video before so if you don't know much about game loop please check my game loop video or any other videos on YouTube I'll put the link in the description anyway here I'll just type the game loop but as long as it works any game loop is fine here all right so that's the game loop with this, the program calls this update and repaint method 60 times per second and it's a bit confusing but this repaint basically means calling this paint component method so we call these two methods from this game Loop so the game loop is done let's create a new class I'm gonna name this PlayManager and this class is gonna handle basic gameplay elements such as drawing the UI and setting tetrominoes so first we're going to draw the main play area okay so I'm gonna place the main play area at the center of the window... around here and the width is 360 and the height is 600. we're gonna do this a bit later but uh we're gonna set a single block size as 30 pixels so that means we can place 12 blocks horizontally and 20 blocks vertically also declare four variables that indicate left, right, top, and bottom of the frame, main area so let's create a Constructor and set these numbers so to get the left X, I subtracted the half of the play area width from the center point of the window and the right X is left X plus the play area width and for the top y 50 pixels and the bottom Y is top y plus the play area height and then we're going to create two methods and we're gonna call these two methods from the GamePanel and for this draw method, we pass this Graphics2D go to the GamePanel first, we instantiate the PlayManager class then from this update method we call the update method in the PlayManager class and from this paintComponent we call the draw method but we need to pass a graphics 2D so here we need to convert this Graphics to Graphics 2D like this and then call the draw method in the PlayManager class and pass this g2 all right go back to the PlayManager so let's draw the main play area so first we set a color and then set the stroke width so you can pass any number you want and then draw a rectangle so to draw a rectangle we pass x, y, width, and height so I used these variables okay then what are these minus 4 or plus 8? so I modified the values a bit because I set 4 pixels for the width of the frame so I want to make this left X boundary of the frame where collision happens so that's why I subtract the flame width from the left and the top and add 8 to the width and height let's check yep so left X is here, not here then let's draw another frame like this this is like a waiting room for tetraminos so we can see what mino will be coming next Also let's add text around here yeah something like this let's check what...? yeah so we set the font on this g2 then set anti-aliasing and then draw the text so text, and X and Y so we have created the play area and the next mino area so let's create a tetromino and display it on the screen so first create another package I'm going to name this Mino then create a class Block so what is this class? well, in Tetris every tetromino is composed of 4 blocks and this class represents a single block so we combine 4 of this block and create tetrominoes so first, this class extends Rectangle so this class is pretty simple so it has X and Y, the size and the color information I set 30 for the block size and in the constructor, we receive a color information because each mino will have a different color and we have a draw method here as well and it draws the block with its color and that's basically everything about this class so let's create another class I'm gonna name it Mino so this will be the Super Class for all tetrominoes so all the different shaped tetrominoes will extend this class so first we create two block arrays and we create a method called create and instantiate these arrays then create these methods: set XY, updateXY, and update and draw and this Mino class is okay for now next, we create all the different mino classes Mino_L1 Mino_L2 Mino_T Mino_Bar Mino_Square Mino_Z1 Mino_Z2 so in Tetris you have seven different tetrominoes and I call them: okay let's work on this L1_Mino and this class extends Mino okay in this constructor we call the create method in the super class this one and pass a color so this will be the color of this L1_Mino I chose orange but you can choose whatever color you want then we get the default X and Y of this mino here so so this L1_Mino looks like this as you can see it is composed of four blocks so we use this block array to set their X and Y yeah like this so we receive X and Y and set them on this b[0] and here's the important part so this b[0] is not this one but this one because we can rotate this mino but this block's relative position doesn't change and so this is b[0] and I chose this one as b[1] and this is b[2] and b[3] and so this b[1] is above b[0] so the x coordinate is the same but the y coordinate is b[0].y - block size and b[2] is... x is the same and y is b[0].y + block size and b[3] is this one so x is b[0] + block size and y is b[0].y + block size so this way, whenever we update these x and y, we can automatically update the other three blocks too so now let's go back to the PlayManager class so we declare a Mino class and name it currentMino also create these integers to store its starting X and Y and the starting position is around here so add half the width of this play area to the left X then subtract a block size so the start X will be around the center of the play area and for the start Y, add a block size to the top Y okay so let's set the starting mino yeah like this so since we only have this L1 mino now so I set it as the currentMino and the pass the stat X and the start Y so this L1 class receives these XY here and set all these blocks' X and Y accordingly and here we call the update method in the Mino class so okay let's draw this currentMino so first we check if this currentMino is null or not otherwise we might get NullPointerException errors and then call the draw method of this currentMino so this one so in this Mino class we draw the mino and for the color, we use this color we set for these blocks so we draw four blocks using these X and Y I think so we are calling this so yeah let's check okay yeah so our first tetromino has appeared on the screen but I kind of don't like how this looks right now all these four blocks are seamlessly connected but I want to change it so we can see each block and to do that we add a little value to these parameters yeah like this so I created two pixel margin and modified these XY and width and height so this way we can draw a bit a smaller block without changing its actual data, X and Y so yeah let's check yeah like this so I like this better but it's just a preference so you can choose either look okay now let's move this mino and first we Implement auto drop function in Tetris the current active mino keeps dropping down at certain interval so let's do that in this PlayManager we add another integer so I set the drop interval 60 60 means 60 frames so the mineral drops in every 60 frames or one second and okay for this Mino class, add some variables so we create a variable called autoDropCounter and in this update increase the counter and when this counter hits the draw interval this mino moves down by one block yeah so like this so we add a block size to each block's Y and after that reset the counter yeah let's check oops what it's not working calling this update right why why... ah okay sorry I think I haven't called this launchGame method yeah so we need to call this launchGame otherwise the thread doesn't start so okay let's call it from here sorry about that, let's check okay now the Mino is going down automatically yeah looking good so now let's move this mino with our keyboard yeah KeyHandler, so create a class in this main package and this class implements KeyListener and when you implement this KeyListener you need to add three methods keyTyped, keepPressed and keyReleased but we don't use this keyReleased and keyTyped at all we only use this keyPressed method and also we use some booleans okay then we get the key code okay so whenever you pressed a key you receive this key event and you can get a key code from it and if the pressed key is W then upPressed boolean becomes true and so on so I choose WASD for movement but you can choose other keys too such as UP key, UP, DOWN, LEFT, RIGHT all right then now let's add this KeyListener to this GamePanel so this way whenever you press a key while the window is focused you can get a key input with this key Handler class then go back to this Mino class so let's control the mino let's take care of this down first so if you press down key then yeah like this when downPressed is true, the mino goes down by one block and after that, we reset the autoDropCounter so change the boolean to false so let's take care of the left and the right too all right yeah like this so when left is pressed we subtract X by the block size and when right is pressed, we add the block size to the X okay let's check yeah so now we can move the mino with the keyboard no collision yet though and finally the up and this is a bit tricky because this handles mino's rotation and to do that first we create a variable yeah so each mino has four directions and we indicate that with this integer variable so 1, 2, 3, 4 then create four methods so around here and we can leave these blank here we override them in each individual mino class so for example in this mino, L1 class, so this L1 minnow can be rotated to four directions okay so let's copy this and Direction1 is basically the default direction so this one we type like this: so this time we use this tempB array and put the X and Y coordinates the same as this one and then call this update XY method in the in the super class and pass this direction number and so here basically we put these tempB values into this b array well then you might think, "So what's the point?" "Why don't you just directly update this b array instead of using this tempB array?" because we need to handle collision so if a collision happens when we rotate the mino we need to cancel the rotation but if we have already updated this X and Y we cannot restore the original position so that's why we just stored these values in this temp array I will explain a bit later when we handle the collision but right now this is how it is okay so let's handle other directions too so this is the Direction2 and this time, this is the b[0] so the center block and this is b[1] and b[2] and b[3] So based on this b[0] we can get other X and Y okay then Direction3 and 4 so Direction3 and 4 are like this so now we can rotate this L1 mino to four directions so go to this Mino class and when you press this UP key then okay so if the current direction is 1 and if you press W key then we call this getDirection2 method and if the current direction is 2 then we call this getDirection3 and so on so every time you press W key the mino rotates okay so let's check so press up key yeah like this so now we can rotate the mino so let's create other six minoes and basically these classes are very similar the only difference is the shape so we set different relative coordinates so it's pretty repetitive and I think I will just fast-forward the process and occasionally stop it so you can check each mino's X and Y positions okay so here we go okay that's the Mino_T class and so this Bar has only two directions so we can simply call Direction1 and 2 from here and this Square is even more simple because it's Square so basically we cannot rotate it so we don't need to add anything here and okay, two more? so Mino_Z1 and Z2 and this Z1 and Z2 too have only two directions so we call Direction1 and 2 from Direction3 and 4 okay that's everything so now we have seven minoes so let's pick a random mino from these seven minoes so PlayManager here we create a method okay so we use this Random class and pick up a random number between 0 to 6 and based on the number we return a different mino and here so instead of setting L1 we call this and get a random mino so let's check okay so Square yep and we can rotate it wait something is wrong with this uh L2, I think? let me check okay sorry this is Y and this too and this too oh yeah this one too okay let's check okay yeah now it looks okay everything looks okay I think but as you can see there is no collision yet so let's implement it so in this Mino class we create three booleans and then two methods so there are two kinds of collision in this game so we check collision when the mino moves or rotates so first let's take care of the movement collision since it's easier first we reset these booleans and then okay so first the left wall collision so here we scan the block array and check its x value and if it's equal to the play area's left X then that means the mino is touching the left wall so we set the left collision "true" and the right wall collision happens when this mino's X plus block size so okay let me see so this mino's X plus block size is equal to the right X and bottom collision happens when this mino's Y plus block size is equal to the bottom Y and we call this method okay I think should be around here so before we handle down, left, and right movement we check if the mino is touching the wall or floor or not and so if one of these collisions are happening then the mino cannot move so let's handle that yeah like this so the mino can go down only when the bottom collision is false yeah like this so the mino can go left only when left Collision is false and can go right only when this right collision is not happening oh and uh yeah two equals so let's check okay so yeah left collision right collision looks good yeah but when we rotate the mino it ignores the wall so we need to check the collision when we rotate the mino as well also the bottom collision is working but it still doesn't prevent the auto drop but we will take care of that a bit later so let's handle the rotation collision first but before doing that let me Implement pause function so KeyHandler, here we add pausePressed yeah something like this so I used Space key for pausing so when you press Space key the boolean becomes true or false depending on the current condition okay then GamePanel and here I think we only update the on-screen information when the game is not paused let's draw a text when the game is paused so PlayManager here yeah like this let's check okay so now we can pause the game so right now we can rotate the mino ignoring the collision but that's not good so for example when the mino is in the position like this then we shouldn't be able to rotate this so we need to check if it's gonna hit a wall or not and if it does we need to cancel the rotation so in this Mino class and when you hit this W key, UP key, we call one of these getDirection methods for example assuming this getDirection1 is called we first check if this rotation is possible and here update XY so before updating these X and Y we call this checkRotationCollision method and also add this if statement yeah so if no collision is happening then we can rotate this mino but if Collision is happening we don't update this X and Y and now let's work on this uh this one checkRotationCollision method so it's actually very similar to this checkMovementCollision so I think we can copy this and paste it and then change this b[ ] to tempB[ ] because we check the collision using this temp values and also this is not equal but if left X is greater than this X and also this is right X is smaller than this X plus box and this is bottom Y is smaller than temp.y plus block size okay let's check here yeah we cannot rotate anymore and like this so we cannot rotate or yeah we cannot rotate and next we add some code so when a mino hits the bottom then it stays there and a new mino appears at the top and the to handle that we create another boolean so and okay here we change like this yeah so if the bottom collision happens we deactivate the mino and bypass the auto drop okay let's check yeah so now the mino stops all right then let's display the next mino so PlayManager and here, add some next mino data okay so we create another mino and its X and Y also create an ArrayList we put the inactive mino into this staticBlocks so let's set this X and Y so somewhere around here and create our next mino like this like this pick a random mino and pass this X and Y and then draw this next mino like this so let's check okay so let's put this inactive mino into this staticBlocks array create an if statement like this yeah like this we put the currentMino into the array then like this so we replace the currentMino with the nextMino and pick a new next mino which will be displayed in the waiting room and then we draw this staticBlocks array too all right so we scan this list and draw it one by one okay let's check yeah it's working... almost um okay I think I forgot to add margin to this Block class too okay let's check okay now looks better but as you can see we need to implement collision with these static blocks too so let's create another collision check method and call this from both checkRotationCollision and this checkMovementCollision and here first we scan the staticBlocks array and get each block's X and Y and using these targetX and Y, we check down, left, and the right collision so we check if this mino Y plus block size is equal to one of these static blocks' Y and also this time we check this X value too and this means a static block is right below your mino so bottom collision happens okay that's everything now we should be handled this static block collision too so let's check it all right yep okay yeah looks good so now all the collisions are working perfectly however we have a slight problem so maybe you have noticed but when a mino touches the floor or static block it immediately becomes inactive but if you have played Tetris you know it shouldn't be like that you should still be able to move the mino even after touching something so in a situation like this, you want to slide this Square mino to the left so it fits nicely but we cannot do that right now so to make this sliding possible we need to create some time margin so we create this boolean and integer variables and when the bottom Collision becomes true instead of immediately making it inactive we set this deactivating boolean true and at the start of this update method we check if deactivating is on and call a method deactivating so let's create this method yeah something like this so we increase the deactivate counter and if it hits 45 we reset this variable and check the collision and if the collision is still on after 45 frames we set this mino inactive and also PlayManager and we reset oh wait this is public okay let's check yep okay then now delete lines so we create a method and call it from here so so basically what we need to do is count the number of the block on the line and the maximum number of blocks that can be placed in a row is 12 so if we could count 12 blocks in a single row that means we can delete them and to do that we scan this play area by a block size and if there is a static block we increase this block count and while scanning the playy area we scan this static blocks array too and if it's X and Y are equal to this X and Y that means there is a static block at this coordinate so we increase this block count we reset the counter when X reaches the right X because it goes to the next row also when finishing this row we check if the block count hit 12 if so that means this row is completely filled with static blocks so we can delete the line yeah like this so here we remove all the blocks that have the same Y and for the scan we check the slot from the largest number so if this arraylist has 10 blocks then it will be slot 0 to 9 so we start 10 minus 1 so slot number 9 yeah so if you scan from 0 to the maximum like usual this loop doesn't work correctly because we are removing slots and when you remove a value from an arraylist then the variable position is also shifted by one and it produces some weird results so we check from the largest number and after removing the blocks we shift down all blocks above this line by one block so if the block's Y is greater than the current Y, we add the block size to it so it goes down by a block okay let's check okay looks good let's try multiple lines okay now we can all right okay looks good so now we can delete lines so the game mechanics is basically done now so we can play the game but still it looks a bit too simple so we're gonna add some decorations and some effect so I think it would be better if we could see some effects when we delete lines so to display an effect we use boolean, integer, and an ArrayList so when we delete a line here when we delete a line so we set this boolean true and add the current Y to this array we use an array because it can be multiple lines that we delete so if we can delete four lines then we need to get four Y values okay then let's draw the effect so draw method and so here we check if the effect counter is on and if it is, increase the effect counter and set the color then create a for loop and scan this effect Y array and draw a rectangle which is as wide as the play area width and the height is the same as one block and finally when the timer hits a certain number we reset everything and stop drawing this effect rectangle and I set 10 frames but you can choose any number basically okay let's check okay okay not that impressive but not so bad I guess oh and we haven't implemented Game Over yet so let's take care of that too we create a boolean so when we deactivate the current mino we check so when we deactivate a mino we check if the mino hasn't moved from the starting position if so it means we have no space left to move the mino so the game is over and so let's draw game over text I think we want to display at the center of the play area so and also I think we are not using this space, so left side space so I think I'm gonna draw the game title just for the decoration and I'm gonna use a new font and also italic okay let's check okay a little bit to the right yeah like this and let's check the game over too okay game over and we need to stop the game so GamePanel? okay here okay yeah we are almost done and let's add score and the level without score and the level the game is not so fun isn't it so PlayManager so we create variables like this and draw the score frame okay yeah and then draw some texts okay like this yep okay and then in this checkDelete, create an integer lineCount and also here so when we remove a line we increase this lineCount and also this lines and then okay add score so so at the end of the method we add score based on the number of the lines that we have deleted and I set the single line score as 10 times current level so if the current level is 3 you can get 30 by deleting a line okay and also we need to increase the level so here I think also let's increase the drop speed so here's another little touch so this line score increases every time you delete a line and if you deleted 10 lines so every 10 lines oh this is not 10 this is 0 sorry so every 10 lines the level increases also the drop speed and basically every time you gain a level the drop interval decreases by 10 so it starts from 60 and then 50, 40, 30, 20, every time you level up but then when you gain a level when the drop interval is 10 then the drop interval becomes zero and that simply doesn't work as a game if the drop interval is zero then the game is no longer playable So to avoid that when the drop interval is 10 or less we decrease it only by one so 10 9 8 7 6 5 and so on until it hits 1 but of course this is just an example so feel free to think and set your own score mechanics okay let's check okay score increased delete 10 lines and we can gain a level okay level 2 now the dropping speed increases a little bit I think all right then the final touch so let's add music and the sound to enhance the atmosphere so I prepared these audio files the background music, it is called White Labyrinth and also I prepared four sound effects that I made myself with BeepBox sound effect for delete line, game over, rotation, and touch floor so right click on this project and create a resource folder and a copy these files here then create another class in this main package Sound so first we create Clip and URL array and then a constructor so in this constructor, we get file paths of these audio files and put them into this URL array then create a method called play and we receive integer which will be the slot number of this array and also a boolean if we are playing a music, not sound effects then this boolean is true so this is like a template to get the audio file so what we are doing here is using this file path we get one of these audio files and put it into this clip object and if this is music not sound effect then we pass this clip to this musicClip because we'll use it in other methods and then we open the clip by passing the ais wow that's a lot of selections so choose this one, javax.sound.sampled.lineEvent so then we add this LineListener to the clip I didn't do this in my previous tutorials so this is something I learned recently what we're doing here is setting a behavior for when the audio is finished so when the audio is finished we close the script if you don't do this your computer's memory usage increases every time you play an audio and the memory gets stuck so this is one way to free some memory and finally we close ais and start the clip so we can hear the audio and we create two more methods all right so these are for music if it's music, we want to loop the song and maybe want to stop it when the game is over or so we pause the game something like that so we prepare this loop and stop method so that's everything about this sound class so go to GamePanel and okay so instantiate the class so here we create two static sound objects the one is music and the other one is sound effect and first let's play the background music so maybe it starts when we launch the game we're gonna call the play method in this music and the path the slot number and also a boolean and this is music so the boolean is true then we loop it okay let's check [Music] okay so now we can hear the music so let's Implement sound effects too the first one is a rotation sound this one UP key, W key okay so we're gonna play the sound maybe here this time we use this se class, not music and play and the pass the slot number and this time not music so false so touch the floor means bottom collision okay so here yeah like this so we play the sound when the bottom collision is true but only when this deactivating is false so we play this sound once then this deactivating becomes true so we won't play this sound anymore so we only play once because without this if statement we keep hearing this sound until this bottom collision becomes false so yeah we need this if statement oh oh wait yeah so like this and I realized I passed a different number for this UP key so rotation is number 3 sorry yeah not 1 1 is delete line and 2 is game over okay sorry so next is the sound for deleting lines so delete line is not here, PlayManager so delete line may be here yeah so here and finally game over so game over is here so we first we stop the music and then sound effect and also I want to stop the music when we pause the game in that case I think we should do it in this KeyHandler so when we press this space key and when we resume the game and play and zero and true and also make it loop okay maybe that's it that's everything yeah I think the game is now complete so yeah let's uh let's try it okay rotation sound what to do what to do oh it's so fast okay level 10 no I don't think I can make it ah no no no no no no no no no no no no no no no wait wait wait no ah yeah okay so that's the end of this tutorial so we have created the Tetris I hope you enjoyed this video and enjoy the coding as I said at the beginning Tetris is a pretty popular subject for coding so this is just one way to do and I bet there are many other ways to do this so feel free to arrange this and explore your own way anyway that's all for today thanks for watching this long video and yeah until next time bye