How to make Tetris in Unity (Complete Tutorial) 🧩🧱

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello my name is adam in this video we are going to learn how to make the classic game tetris in unity tetris is a tile matching video game created in 1984. in tetris players complete lines by moving differently shaped pieces which descend onto the playing field the completed lines disappear and grant the player points the player can proceed to fill the vacated spaces the game ends when the playing field is filled the longer the player can delay this eventual outcome the higher the score will be this tutorial will utilize some more advanced programming techniques if you have never written any code before then i would recommend starting with one of my other tutorials such as snake or pong if you need help at any point in the tutorial feel free to join our discord community where we can offer direct help there is a link in the description of the video please consider subscribing to the channel to support the amount of effort it takes to create a video like this one it is completely free but it helps me a ton thank you enjoy the video let's begin by creating a new project using the unity hub in the top right corner we can click the blue new button and specifically we can click the drop down next to it to select the version of unity you want to use for this particular tutorial i'm going to be using unity 20 20.3 which is the current long-term support version or the latest long-term support version so unity 2020.3 and here we can choose our template we want to use in which case we're going to select the 2d template since this will be a 2d game this will install various 2d packages and for this game the the one that's going to benefit us the most is the tile mapping so select that template name the project whatever we would like choose where you want to save your project and click the create button and then from here this might take a couple minutes to initialize so we'll pick it back up as soon as it's finished once your project finishes initializing it should look something like this the first thing we're going to do is import a few custom sprites we're going to be using throughout our game this is absolutely not required but i'm going to do so just to make our game look a little bit prettier if you want to use your own sprites you are absolutely more than welcome and also you don't have to use custom sprites at all just using a simple square sprite will be completely sufficient for this game if you want to use just a basic square sprite or any other the any other basic sprites that come with unity you can right click in your project panel create 2d sprites and then they're square circle etc the square sprite is absolutely all you really need for this game from a simplistic level but like i said just because i want to make the game look a little bit better i'm going to import some of my own custom design sprites so first let me create a new folder called sprites and i'll go ahead and drag all of these in and once again you are absolutely more than welcome to use these same sprites that i'm using they will all be available through github as well as the rest of the source code for the project there's a link in the description of the video for that when you open up github it should look like this and specifically the sprites will be found in the assets folder and then the sprites folder and these are all the exact same sprites that i'm using right now so here once we import these sprites into our project we do need to change a few settings in them so i'm going to select all of the sprites and the main thing we need to change the pixels per unit to 192. all of these sprites are designed specifically at that pixels per unit value and then from here we also want to change the filter mode to point and we can go ahead and apply those changes to all of them we do need to change a couple settings specifically for the grid and then that's it so for our grid here we're going to change it from clamp to repeat because this is going to be a repeating grid pattern and then on that same note we need to change this mesh type to full rect which is also needed in order for us to tile this specifically for a grid so that's it in terms of importing our sprites let's do a little bit of scene setup now using some of these new sprites we've added first thing i'm going to do is go to our scenes folder here i'm going to rename this to tetris so with that being renamed on the left here is our hierarchy which displays all of the game objects in the scene and if we select one of those game objects on the right is our inspector where we can see the components that have been added to this game object and all their values so for example this main camera has a transform a camera component and audio listener so for our scene setup here we're going to customize this a little bit first i'm going to change our camera's background color to match um some of the sprites we've imported so let me grab this color over here we can open this up let's change our hexadecimal here to 2f35 3d once again this is you know you can customize this however you'd like this is the color i'm going to be using because it matches the sprites i've designed um if we go to our game view here we can see that background color has changed all right and the other thing i'm going to do is change our size of our camera here to from 5 to about 12. you can see this white outline is changing so by me increasing the size we're effectively zooming out we're sort of pulling the camera further back so we can see more area and so a size of 12 will give us exactly enough space to fill in um our entire tetris game board size 12 that's it for that and let's go ahead and add a couple of these sprites to our scene so first let's add our grid all we need to do is select that and drag it into our hierarchy and what this is going to do is going to create a new it's going to create a new game object and it's going to add a sprite renderer to that game object with that particular sprite being set and we're here we can see that grid object being rendered in our scene now that's just a single you know that single spray we want this to tile because it's a grid so we can change our draw mode here from simple to tiled and then all we need to do is specify a size so for tetris typically the size of the game board is 10 by 20. so i'm gonna use that 10 by 20. there we go we can see that being drawn there and let's see what else i think that's it for our grid next let's go ahead and add our border so once again we just drag that into our scene it already looks perfect we don't really have to change anything there that one just renders as is once again if you're using the sprites that i'm using everything's kind of already designed and set up for you so it makes it makes it um the effort a little bit but these two game objects i've added here are not required by any means they're more of just a visual aid to the player to show the grid lines and so on but they're absolutely not needed so feel free to customize this use your own sprites do whatever you want to do or follow along like i am [Music] let's go ahead and set up a tile map that we're going to be using to draw all of our game tiles onto so in our hierarchy here we can right click 2d object tile map and rectangular this will create first a grid and then a tile map is a child of that grid for the grid i'm going to rename this to board you know for game board and then we have our tile map and we actually don't need to change any of the settings here there the defaults are exactly what we want for this game we are all good there um we do want to change actually one thing a lot we want to change their order in the layer here so by default it's zero and it's also zero for all of our other sprites we've added two the problem is or what this does is it determines the order in which things are rendered and so we have to think about the layering here of our objects from in the furthest position the most background element should be this grid here because everything's going to get drawn on top of that so we want our grid to be zero then followed by our border we'll render on top of that so that can have well actually the border will render on top of everything this will always be the border here will always be the outermost so we can for example maybe set this to um you know just any large value really there's when all is said and done our game is going to have four different objects so if we go in order it'd be zero one two and three so since this is supposed to be the top most we'll just set that to three but really it doesn't matter as long as it's the largest number and then for our board here our tile map specifically this will be um two and later on we're going to add a second tile map for a special like ghost feature which will be order one so basically our grid is zero followed by our ghost tile map followed by our main tile map is two followed by our border is on top of everything i suppose we can go ahead and create that ghost tile map right now although we're not going to utilize it right away but basically we can just duplicate our current grid here now we'll have a second one i'm going to call this one ghost and i'm going to rearrange these in the order that they the rearranging these doesn't matter but just to make it clear we have our grid a 0 followed by our um our ghost will get set to um two or war one and then we have our board is two and then our border is three so zero one two and three so those that's it we have our tile maps we have our two grids making sure they're drawing in the correct order and we're good there now let's actually create some tiles to draw a tile onto our tile map we need an a tile asset and the tile asset has a sprite associated with it as well as some other data if we go to our scene view here if we select our grid here our board there will be this button open tile palette so we can click that and then from here we can create a new palette of tiles that we can use to draw onto this so we can create that i'm gonna call i'm just gonna call this like blocks um it really doesn't matter blocks tiles um pieces i'm gonna create i'm gonna choose where to save this i'm actually gonna select a new folder so i'm gonna call this tiles put that in there and so yeah there is our blocks asset and now we can drag in all of these blocks we want to add so let's let's grab all of these um [Music] everything other than border and grid we can add to our palette here so we just select all those and we drag them in once again we're going to select a folder because it's going to create assets for every one of those sprites so we're going to select our tiles folder boom it created a tile for all of those notice here in our folder we now have tile assets and so here when we select one of these tile assets it is like i said there's a sprite associated with it but there's other um information such as the color oh you can use the color to tint it and then the collider type and so on so these tiles are what actually get drawn onto a tile map so let's go back let's open that up again for example if i just select any of these say orange i can literally just draw these on here right now we're going to be drawing these on here programmatically but it's the same concept and we still need these tile assets in order to do it programmatically so that's really all we needed to do is make sure that these assets are created for us and so we're good now all right next we need to set up some of our tetromino data or the data for our actual pieces that's what they're more formally called as tetramino or tetrominos plural there are different shapes iotjsl and these are the typical colors you will see for those various shapes so we're going to set up some data to indicate these shapes um so let's go ahead and first create a new folder called scripts and we're going to create one called the dramino [Music] and let's also create a couple others real quick too let's create another one for data and let's create one more for now called board which is our tetris board and so our board script here we can throw on to our actual tile map um or not the tile map but the grid so this parent or not there goes i have the wrong thing selected but our grid here for board the object that's called board we'll add our script call board to it and we'll go and add edit this in a minute but first we need to grab some data we need to actually set up some data for our pieces let's open up this once unity um once you open up a script um oh here let me suck so if you're using vs code like i am it might prompt you to select one of these projects so it knows how to load you'll want to select a solution file that dot sln file so we go ahead and select that that way i get my intellisense and all that i'm assuming you have your c the c sharp extension working but you're you definitely don't need to use vs code it's your personal preference whatever you want to use so here is our script when you create a script by default unity creates a class that inherits from mono behavior for this particular one we're actually not going to have a class with model behavior we're just gonna define a custom enum an enum is like an enumerated list of values so and then you provide names to those values so of tetrameno and in here we will list out those different shapes and so let me pull up that um wikipedia page again and we're gonna just literally have the letters as br names you can call them something else if you'd like but i'm just going to literally call them that so i o t j l s and z so those are our different options and finally now we also need to add a custom data structure that's going to store certain data for each of these respectively so we'll have a data structure called petro detromeno data and we need to make sure that we can see this data in the editor right i'll show you in a minute let me actually come back to that let's add a couple things to this first so you have tetrameno data from here we need to select which of these values do we want to associate this data for so we're going to declare a variable of this with the same type of our enum and we can just call this tetromino and then we also need to be able to select which tile we want to draw for whatever tetramena you've selected so we can define a tile asset here and actually to use tiles we need to import unity engine dot tile maps now we can use this class called tile and we can specify the tile there so that's our basic data structure we're going to flush this out a little bit more but now if we go to our board script here let me clear this out i when i always like to kind of start fresh on all these so i usually get rid of all the default stuff they provide to you and just kind of start with an empty class but that's just my preference we want to define an array of tetrameter node data we need an array of these so we can customize them in the editor and if this is confusing it'll all make sense as soon as we see it show up in the editor so let me finish typing this out we have an array here an array of tetrameter data i'm going to call this tetroman plural and notice what happens in the editor now once this recompiles well we select our board asset with our board script you actually don't see anything nothing happened and that's because this data this data structure can't be serialized and show up in the editor it doesn't know how to display this data in the editor so to fix that all we need to do is add a custom attribute here we can say system dot serializable and now unity knows how to serialize this so it can get displayed in the editor so now here when i select our board in our script you see this new array pop-up right so or this new property pop-up which is our array that we declared and we can specify our data for these so there are seven different pieces so let's set these sizes seven and for each of these we can now say okay for i we need to associate a tile with it um and so let me select each of these i o t j l s and z and we need to assign the corresponding tile we want to render for that given letter so standard is um i is cyan the o is yellow the t is purple or magenta um j is blue l is orange s is green and finally z is red so completely up to you to you don't have to choose those you can you can completely customize them whatever tile you want to draw is absolutely fine with you um but that is the standard tetra setup there so we've set up our tetromino's data so we're actually not quite done setting up our data for our tetrominos we still need to define some coordinates that make up the cells of every piece that way it knows how to actually you know form that shape so let's actually go back to our tetrameno data structure here and let's add a new property for our cells this will be an array of vector two um we actually need to import unity engine first so we get all the standard data structures that come um so an array of vector two um actually it'll be vector two ins since this is a 2d game everything's kind of whole numbers and well it doesn't have to be whole numbers but for our cells there will all be whole numbers so vector to in array we'll call this cells and so here in the editor now we can see all of those show up so for example for each of these we can say cells so we could you know we can go through here and manually assign all these coordinate values for each of our pieces it would be a little bit tedious to do that so we're actually not going to do that i'm going to do this a little bit differently but you could in theory do it this way and what is cool about this approach is you actually could define some custom shapes if you wanted to using these coordinates you can completely kind of customize um you can make any shape you want really and they don't have to be four cells it could be more than four cells but for normal tetris and for the sake of kind of time i'm going to do this a little bit differently so we have this data class here that we never did anything with let's go ahead and actually revisit this because this is going to just store a bunch of static data that define um or that we'll use throughout our game um so let's clear all this out this is actually not going to inherit from model behavior it's just going to be a static class which means you cannot directly instantiate this class you can't you cannot create an instance of data this is just static data that we can access from wherever um and once again for the sake of time i'm not going to write it all out because it would be quite tedious and it's a lot of just numbers that are somewhat arbitrary and so i'm going to actually copy this from my other project um and i can show you how to copy it from github as well i'm just going to copy all of this data class here and i'll explain i'll explain what it's doing but let's copy all this and get this in place um and first before i jump into that if you want to copy all this too you can see here it's just a lot of numbers a lot of vector two ends for coordinates there's these things called wall kicks um there's our cells there's yeah it it's just a lot of kind of tedious typing out of data and so this is the one thing where it's like let's kind of just copy and paste this for the sake of time that way we can focus more on the actual game logic um in this tutorial video i don't want to sit here and spend 15-20 minutes just typing out all these numbers so if you want to copy that you can certainly pause the video and just kind of type it all out that's an option but it would be a lot easier to just open up the github page once again link in the description of the video to the github page and you can go ahead and go to assets scripts and then data you know when you can copy this whole class here it's just a bunch of static data that we'll use throughout the game so i normally don't recommend people just kind of copy and paste because then you're not really learning anything but in this case i don't know that you'd really be learning anything anyways just sitting here typing out a bunch of tedious numbers and stuff so you know you have my blessing to just kind of copy and paste this one um so there's all of our data now we still need a way to actually pick out and like grab these array of coordinates um and assign this value here right so for example our cells is a dictionary dictionary is a key value pairing so here's our key as you know trauma i for example and here's our value which is our cells so for every key for every piece we specify what um array of cells are associated with that so yeah we need now a way to actually assign this though using this static data so first let's go ahead and actually just create a public function called initialize and we can assign our cells here oops say this dot cells equal and then we can access that data class and then we can say data.cells and to look up the particular value given a key we the key in this dictionary is tetromino which is our enum that we declared earlier so we can just say this step to terminal and now we're looking up the associated you know the cells that are associated with this tetromino and now we're storing that into our um entire field here oh and one thing i forgot to do so this right now would still show up in the editor we don't need it to show up in the editor so we're gonna change this from being a c sharp field to a c sharp property and to do that we can just define some getters and setters here so we can say it's getter with a private setter and this will no longer show up in our um in our editor here none of those cells show up anymore which is what we want well you can if you want to do that approach you absolutely can keep do that approach and not go through um you cannot use the static data instead keep this a field and then you can manually assign the coordinates in the editor which would potentially allow you to create like custom shapes if you're interested in doing that but for our standard tetris here we have our cells we're assigning that to based on the static data looking up those values using whatever piece you've assigned so that is it for setting up our data there is more stuff in here we'll utilize later you might be wondering like what the heck are wall kicks um that will be used for rotation once we get to it but one step at a time we're at a point now where we can finally start to spawn our pieces onto our game board let's go ahead and open up our game board script and we can go ahead and spawn our first piece let's first define a function called awake which is one of the built-in unity lifecycle functions this will get called automatically when your component is first initialized and so in awake here we actually need to go through loop through our list of data and initialize all of our data right so we had declared this initialize function but we're not calling it from anywhere yet so we need to go ahead and call this when our game board is first initialized so we can loop through for n i equals zero can't type i is less than this. length i plus plus and we can go ahead and access each element at that index and call our function initialize um by the way for the record we will be adding more data to this structure but for now we don't need anything other than our cells using the cells we can actually properly render things all right what else do we need in here so in order to draw something onto the tile map we actually need to get a reference to our tile map so let's go ahead and declare a variable for that so tile map we need to first import unity engine tile maps we can have our tile map variable here and i'm going to make this getter with a private setter and i can just assign this in here in our awake function this dot towel map equals get component in children if you remember our tile map is actually a child of the game object that our board script is attached to so we'll get component in children so now we have our tile map which we can use to actually set tiles on and draw our draw our pieces we've initialized all of our data and now let's implement our start function so when our game starts we can spawn a piece and we'll have another function here called spawn piece because we're going to there'll be multiple places where we call spawn piece we want to add all the logic in here and we'll just simply call spawnpiece from start so in spawnpiece we need to just pick a random um element from our array here um it i guess it doesn't have to be random but typically in tetris you it kind of just picks a random piece so to do that we will store a random index into an integer here and we can use unity's random class unity has a class called random and we can say random.range so it's going to get a random number between a min and a max and our min will be zero and our max will be the size of our array the length of our array so this dot tritromenos.length so there we're getting a random index and now we can grab the data at that index this.chatrominos and we'll grab the data at that given random index [Music] awesome so from here we need to actually set the pieces or set the tiles on our tile map um and this is something we'll do quite a lot so we're going to add a function called set and we need to provide some data that we want to set in here we actually need to go and declare another class before we move on to this though so let's go ahead and go back to unity here and let's create a new script just called peace this will be like our game piece essentially and with tetris it's kind of nice because you can only control one piece at a time so you really only need one instance of this um in a lot of games you would repeatedly spawn a new piece over and over but in a in a sense you really only have one piece you're controlling and then once it gets locked in place we can just re-initialize that same piece with different data and re you know and we don't have to like reinstantiate it every time so we can actually go ahead and go to our game board the same place where our other script is added and we can add our piece there as well and let's go ahead and open up this file and let's add some logic here as well so as i just mentioned we're going to be just we're going to use this one piece throughout the entire game and then it's just a matter of re-initializing it with the correct data every time so we should probably have a function called initialize where we can pass in the data we want um [Music] and so we got to think about what is that data for one there should probably be some kind of spawn position right so what's the initial position you want to you know place this piece and also what is the tetromino data that you want to use while this piece is active right and then lastly we should pass in a reference to our game board because our game board we're going to need to call various functions on the game board during certain when certain events happen from our piece so the difference in these two classes is our game board is going to control the kind of general like overall state of our game it's kind it sees the bigger picture of things it it has the entire tile map and it can render um and set tiles and so on our piece is just the individual piece and this is where we will add logic for like inputs and controls you know moving rotating et cetera um but we'll need to kind of need to communicate back to the game board anytime you know the piece moves we need to communicate back to the game board hey reset this piece in a new position etc right so we should also pass in a reference to our game board and i'm going to add that as the first argument or the first parameter now we need to store all of these things into variables so let's declare those so first we'll have board and i'm going to declare all of these as properties so public getter with a private setter same thing for the others we'll have data here public editor private setter and then finally our position um vector three or yeah vector three inch it'll be vector three in because tile maps use vector three ins instead of vector two ins so position get with private setter and we can just directly sign these assign all these one to one so this dot board equals board this opposition equals position and this dot data equals data now the one other thing we probably should do is um have a an array of cells um we're storing the array of cells in our data here however we're going to be manipulating those cells per piece so we need sort of a copy of this data the cells here defined in our data structure are kind of like the original you know the original default state of that that piece but once you rotate it and so on that will need to be changed so we really should have an array of cells as well um so let's have and once again these were vector two ants well so we're actually going to treat these like tile more like tiles so they'll be vector three ins an array and we'll call these cells and we will get with a private setter same as the others and we can assign all of those um in here as well so if that array is null that's the first thing we need to do if we've never initialized this array we should go ahead and do that so we can create a new array here vector three int and we need to provide the length of our array which will be however many cells our data has so this dot data or we can just say data dot cells dot link which would be for like i guess you could hard code it to four because every piece has four but potentially if you're using custom shapes and so on like it would be better to do it this way it's usually you wanna avoid hard-coding magic numbers like that um so that's just initializing our array we still need to populate that array with these cells so we're just going to essentially copy the data from here into our new array here so we can loop through for n i equals zero i is less than data dot cells that length i plus plus and we can go and set all of these so this does tell us i equals um let's see equals data dot cells i now this is giving me an error because our cells here are declared as vector 2 ins but here they're going to be treated we needed them to be vector three ants in order to set them on our tile map so we can actually just cast this to vector three ends and there we're good there later on we're actually gonna end up changing this a little bit once we implement rotations but for now this is fine so just at least getting the initial structure in place um cool so that's like our piece where there's gonna be a lot more we add to this but that's a good start let's go ahead and finish our set function so let's jump all the way back to our board let's go to our set function and we need to provide the piece we want to set so we can pass that in we can say set piece that way we can read the cells that we want to actually set on our tile map so to do that we need to loop through every cell in the piece so for inst i equals zero i is less than piece dot cells dot length i plus plus and from here we just need to set the tile on our tile map so we need to get a vector three inch we need a position to provide to our town map so we'll call this tile position and this is going to be our cells piece um oops forgot my equal sign but we need to offset this based on the position of the piece so you can think of the cells as being like kind of like local coordinates i suppose or they're they're always just all those values if we look at our static data here are like negative one it's they all range between negative one and positive one so negative one zero one there's actually some case where it's two i think yeah for the letter i but that doesn't give us a sense of like where on the board as a whole is it right it could be anywhere on there um so we need to offset each of those cells by the overall position of our piece right so it's really um each cell plus the position of our piece as a whole and together that forms like our our actual in the actual spot they need to get set and then finally we just need to set the tile on our tile map so tilemap set tile we provide that position we've just calculated and then we need to provide a tile to render and if you remember we associated all of our tiles with those pieces right so we can um just pass that in we can say piece dot data dot tile and there we go that is our function for setting this piece now we just need to call this from somewhere and so when we spawn our piece we oh one thing we didn't do is we need to actually um we need to first get a reference to our game piece so let's add that so we'll have um peace and i'm going to call this active piece same thing this will be a property with a public getter and a private setter we can assign that here as well this top piece or active piece equals get component in children call this piece [Music] and then when we set up our data so in spawn piece we pick some random data and then we need to initialize that piece with that data so initialize we need a reference to the board which is this we need the position we want to initially spawn this at so we actually are missing that um let's come back to that i'll put some question marks for that for now and then the data which is the random data we picked out so what about the initial position we should sp we should spawn this piece at well that's kind of like it's kind of up to you where you want to spawn that and so what we can do is we can declare a public field here and i'll call this vector3 in i'll call this spawn position and we can just pass that in here so we can change this value in the editor and have our piece spawn wherever we want it to spawn all right so we're initializing our piece we're passing in a reference to our game board we're giving it the spawn position and then we're passing in whatever random data we picked out and finally once we've initialized our piece now we can set our piece on our tile map so we can say set active piece and it will go through it loops through all the cells offsets those by the overall position of the piece which ends up just being our spawn position and then we set the tile let's go ahead and test this out i know that was quite a lot of just coding without really testing anything so let's go ahead and try this out the one thing we need to do is actually set our spawn position i suppose for now we can leave it at zero zero and see what happens and look at that so there is our piece it spawns right in the middle because that's zero zero i believe let me double check real quick but i think the actual position we will want um so you'll might notice too it's more in most tetris games they actually spawn more towards the left than the right because most of the pieces aren't perfectly centered um the only piece that can be centered properly are the i and the o but the rest of the pieces neither either be one to the left or one to the right either way it's going to lean one way so usually they actually spawn them to the left so we can actually make our x be negative one then of course it should spawn at the top of the um screen and that will be let me undo that so i changed that value while our game was running so it didn't actually end up doing anything so negative one spawn position our y will be towards the top now our game board is 10 by 20 as a whole and zero being the middle so we need to spawn it if we go up 10 that would be the very top however our pieces would then um you know if we want them to spawn within these bounds we need to go a little bit less than that and the pieces at the most take up two rows so we're new 10-2 which is eight and that will be a more appropriate value that said this can really be anything you want um you know it's up to you how and where you want to spawn those potentially you can even spawn them out of bounds and have them fall down although the particular implementation we'll be using for this video this works best so you can see here every time i replay the game it's spawning a new random piece because we're just grabbing random you know we're just picking a random thing from our array there and so there we go there's spawning tetris pieces that's a really good really good start let's go ahead and start implementing the movement of our tetris pieces now so most of this logic is going to be defined in our piece script so here we need to start handling input player input so to handle player input we will do that inside of our update function update is called automatically by unity every single frame the game is running and this is typically where where you will do um your input so we can say there will be a few different pieces to this let's start simple and then we'll expand upon it so from a simple perspective we can say input dot get key down and we can say keycode.a for example you can use whatever keys you want i'm going to use wasd i think more standard tetris games use like the arrow keys and so on but whatever you want i'm going to use wasd so we can say a for left and then otherwise if you say get key down e code dot d for right we need to move our piece so let's create a function called move and we need to provide a set of transl we need to provide a translation or how much do you want to move all right so we can say translation here this is a vector to int you're telling it hey i want to move this amount in the x and this amount in the y and so here we can say move vector to inch vector 2 and left vector 2 yep vector 2 and left and move vector 2 and right and oh vector 3 the vector 2 there we go so we're moving we're calling our function move whenever you press certain inputs and in terms of the actual movement we basically just need to update our position right so we can essentially just say the stop position plus equals our translation however we need to verify that that position you're moving to is valid because it could be out of bounds there also could be another piece there so how do we handle that that's what makes this a little bit more complicated so first we need to calculate what that new position would be so we can test if it's actually valid so let's do that first so vector three into new position equals our current position and then we'll increase that newposition.x plus equals our translation and position dot y plus equals our translation and the respective um axis great so when we have our new position we need to test this to make sure it's valid so we need to communicate back to the game board to know because the game boards can see sort of the overall picture of everything so only the game board can really tell us if this is a valid position so we need to add a function to our game board here and it's going to return a boolean which will be a true or false value so this will say is valid position right and we're going to pass in the game piece we want to test and then that position we want to test vector three ends so is valid position in order to test that a position actually is valid we have to test each individual cell that's part of that piece is valid um because maybe three of the four cells are good but one of them would push it out of bounds or something so let's loop through all of our cells in our game piece so i can't equal i equals zero eyes less than peace dot cells dot length i plus plus and from here we can grab our tile position just like we did here we will look we can literally just copy and paste that actually we need to get our tile position um for that individual cell although it's not going to be whatever the current position of the piece is because we're testing a new position so we haven't yet updated the current position of the piece so we need to not do piece position we just need to do this that we're passing into the function and so there's our tile position and then from here there's a couple things we need to check one is it out of bounds and two is there already a tile occupying that space so to check if a tile is occupying that space we can just say if this dot tile map dot has tile at that position well that means this is an invalid position because there's already a tile there and we can't overlap onto another onto another tile okay we also need to check if we're out of bounds now we haven't really defined what the bounds of our board is so let's go ahead and add a another variable here for bounds um or for board size really so we can have vector 2 in board size i can provide a default value here of 10 by 20 since we we know most tetris games are 10 by 20. and now we can actually calculate a bounds the the bounds itself will be a rectant and we'll call this bounds and this is going to be a c sharp property that we compute so in the getter of our bounds here we need to first get the position um we need a bounds you need a position and then the size so we have the size we just need the position and the position needs to be the corner of um of our board right so because from the corner then it takes the size and now you have a full rectangle here um so the size or the position of our board initially is zero right so we need to offset that by half the size of our whatever the size we specify is and by offsetting it by half it'll put us down into the corner which is what we'll need so here we can say position equals new vector 2 int we're going to offset this towards the minimum direction towards the negative direction um so negative this dot board size this dot board size dot x divided by two and then same thing in the y negative this top board size dot y divided by two once again the position is initially in the center so we need to offset by half the size of our board to put ourselves in the corner and now we can finish completing our wrecked into your new wrecked int and you can see there is a constructor for position and size which the size we already have just as a variable great so there's our bounds which we can now add here so let's get rectant bounds equals this dot bounce okay and we can test if our piece is out of bounds using a built-in function that is provided by our rectangle which is why we did this i guess i didn't really explain that but we're using a built-in rectang function that kind of tests this for us automatically it kind of just simplifies some things so we can say if bounds contains some position which is our tile position and if it does cont or if it doesn't contain so if our balance does not contain it which means it's out of bounds well now this is also invalid so we return false and here it's giving me an error because our rectan is using it would it needs to test for a vector 2 in and this is a vector 3 in so we can once again we can just pass this to a vector 2n all right so that's it those are the two tests we need to do whether it's out of bounds and whether a tile is already occupying that space if either of those fail then the whole immediately we know it's invalid and we can return false if it loops through all of the cells and it never returns false well that must mean it is valid and we can return um true when all is said and done [Music] there is our function is valid position this will be a really important function we'll use in quite a few places later on so finally we can go back to our move function in our game piece here and we can finish this so we can say um if our we can check you know if it's valid so desktop board is valid position we provide this new position that we calculated from the translation and if that is a valid position now we can do a couple things um we can set our new position so we can say this stop position equals our new position so we can actually go and update that and one thing that is useful that we'll need later on is it might be useful to also have this function return in boolean whether or not the move succeeded [Music] so here at the end of our function we can just return valid all right so we return whatever that get got set to um oh we didn't pass in all the correct values here so is a valid position we need to pass in a piece which is this and then our new position there is our move function um there's still more we need to do to this though we're not quite done just yet but this is a good start um let's go ahead at least test it out actually no we need to do one other thing before we test it out once our piece moves we need to set it again because it's going to be at a new position now so we need a recall set but we can't set it until we've cleared it beforehand um because we need to clear where it was previously and then reset it at its new position so we need to have a clear function as well so we can literally just copy this set function paste that in i'm going to call this clear instead and the only difference is instead of passing in a tile we're going to pass a null instead so that's it's unsetting tile and now we have set and clear and so for our game piece in our update function the very first thing we're going to do is clear um clear the piece from the board so this dot board clear this and then once we're done doing any kind of processing movements rotations we will reset it so we have clear and then set and all the actual logic falls in the middle now we can actually test this out and see what happens um we do need to make sure yeah so board size is set to 10 by 20. i don't think we need anything else let's go ahead and try yep so i'm left and right and i can actually move so there we go nice look at that now we can actually also add our move down too so let's go back to our piece and we can add a move down as well this is i think referred to as a soft drop because in tetris there's a hard drop which is where you immediately fall to the bottom but there is also a soft drop where you can kind of manually make it fall down one row so we can say get key down and i'm going to say s key will be our soft drop so we can call our move function and this time pass in vector 2 and down instead and that will make us go downwards yep yep and so i can so i can like literally move the piece completely now i can't rotate it yet that's kind i can it stops me from going out of bounds so i can't go any further at this point so all that is working good um the only other thing we could do now is do our hard drop um and that one ends up being pretty easy actually so let's go back to our piece here let's do our hard drop so let's say if maybe they press the key or the space board or a space board space bar so let's say space we can do our hard drop and for our hard drop i'm actually going to separate this into a different function just so it's a little bit more clear what's happening hard drop um so here we will hard drop [Music] so when you press space you will hard drop and here basically we're just going to continuously move down until we can't move down anymore so we can create a while loop while we try to move down we will continue to move down until we no longer can right because this is where our move function actually returning a boolean is really helpful um so essentially we're attempting to move down if this succeeds it'll return true therefore we enter the body and then we just continue and it repeats again and then it just keeps repeating so long as it continuously successfully moves down as soon as move down fails it returns false well then our while loop stops and then that's it um there will be a little bit more we add to this function later once we add like a locking um but we're not we're not ready to do that quite yet but now i can press space and it just instantly falls to the very bottom so that's your hard drop and there we go that's a good place to stop and move on to the next section all right next let's go ahead and do the rotation of our pieces which might be the most complicated aspect of tetris in fact there are actually many different ways you can implement rotations for tetris games and they even have different systems for doing so so for example the current tetris guideline or standard is to use something called super rotation system or srs but there are a number of other ways you can implement rotations but for my tutorial here our game is going to be based off of super rotation system you can see in this diagram here all the different rotation states for each of the seven pieces so there are always four different rotations for every piece and also for the i and the o they those two in particular are rotated a little bit differently because this this little circle here is kind of the the center point that it's rotated around and you'll notice that for the eye it's not exactly on the one of the tiles like it is for the others those two even have a little bit more of a unique rotation but we're going to go ahead and implement this it gets implemented using a rotation matrix so it's actually just calculated kind of on the fly using a rotation matrix so we'll go ahead and see what that looks like let's open up our game piece and let's first add some inputs to actually handle rotation so input get key down i'm going to use keycode.q and then we'll also do input getkeydown or e so you can rotate either direction left or right um and we'll call a rotation function when you do so so let's define that rotate and let's pass in the direction you're rotating and so up here when you go to when you press q for example we're going to rotate negative one but when you press e you'll rotate positive one so the way you can think about this is if each of these rotations is an index so this first column is index zero the second column is index one index two index three and so on here you're basically saying shift down an index or shift up an index to then get to the next rotation so we actually need to store the index we're currently on so let's add a variable for that we'll say public in and we'll just call this rotation index and we'll make this a property with the public getter and private setter one thing we need to do is make sure we set this to zero as soon as we initialize this piece anytime a new piece is initialized this needs to reset to zero that way it spawns in its default state cool so rotate minus one positive one let's actually go ahead and implement our rotate function so for this we need to update this rotation index apply our rotation matrix to all of our cells and then later we will do some wall kick tests which are um well we'll i'll explain those when we get to them but at the very least we need to set this up so let's say this dot rotation index plus equals the direction however this is actually immediately causes a problem because there are four indexes right zero one two and three if you're already on three and then you rotate to the right it would go to four but that's out of bounds that's an invalid index and so it needs to wrap back around to zero and let's say it's the opposite if you're on zero and you rotate left it would be negative one that's also out of bounds so it needs to wrap back around to the other end so there's a very common utility math function for this called the wrap and i'm just going to kind of copy that into our project here i'm kind of skimming over the details of how this math works just for the sake of time because i think i literally could spend 10 to 15 minutes just explaining how this wrap function works but it does use some modulo operators to you know make sure it wraps to either end of the range based on whether you're below the minimum or above it so this is a standard utility wrap function and so instead of just directly um increasing our direction we need to wrap it so we're going to say wrap this rotation in x plus our direction and we're wrapping it between 0 and 4 because there are four possible rotations now from here we just need to apply our rotation matrix so um now this is where the math for this gets a little bit complicated like this at this point it's really just math um so we'll try to walk through it and explain it the best i can but we need to apply this rotation matrix to each cell um in our piece so let's loop through all of our cells this dot data dot cells that length oops i plus plus and first we need to just grab the cell itself um and these are vector threes or well those are oh so now let's not do data we actually want to if you remember we have a copy of the cells that's unique to this piece and this is why we did this because now that we're rotating them they're this these cell data is going to start to defer from the like the source data so we have a copy of our cells so we don't want to loop through this.data.cells we want to just say this dot cells and these are vector 3 ins however we actually need these to be just vector 3 not int because the i and o that i mentioned those two rotate differently and the way they rotate differently is that we offset the we offset the position by half a unit so once we offset it by half a unit we now need floating point values and so if we use integers then it's going to cause problems so we're going to grab our cell here as just a factor three and let's declare two variables x and y that will become our new coordinates after it has been rotated now we can apply a rotation matrix however once again i and o are rotated a little bit differently than all the others so we need to make a switch statement to make sure that um we can apply different logic based on which you know which letter is being rotated so we can switch on the trump tetrameno i can never say that and we can say if it is i or if it is o you know we'll do one set of logic otherwise we'll do a different set of logic for the default case which is every other case right so let's actually start with the default and then we can see how i and o differ from that so we to apply our rotation matrix um you know this is if you if you've learned this in school at some point this is a pretty standard thing basically we just multiply our x and y component wise using cosine sine sine cosine etc in that data class that we set up earlier we actually have our rotation matrix defined already for us so we just need to use each of these values in the array and multiply them with our cells so what that'll look like is int x equals cell.x um times data.rotation matrix and then the first item in our matrix and now we're also going to times it by our direction here that way you can either rotate you know it can be sort of an inverse rotation matrix or or not depending on which direction okay and then we add our so let me wrap this in parenthesis so it's a little bit clearer okay now we're going to do the same thing to our y coordinates so that y times data rotation matrix now we're going to use the second element or the second value in our matrix also times it by our direction and that's our y ignore the error for a second i'll explain why it's giving us an error same thing cell um cell.x times data rotation matrix now the next one would be value 2 or second index not value 2. so that y times data rotation matrix and then finally the third value or the final value in our rotation matrix and also multiply that by direction so x equals cell x plus l y y equals x plus l y and then you know multiplying by each of the values in the matrix now this is yelling at us because it's trying to convert from a float to an in and so we need to round all of these values oh also i just realized i meant to put this down here i said we were going to do the default first this actually is the default i just typed it in the wrong spot um so we need to round these all to integers so we'll round out all of this and then that is it for our rotation matrix um all right that's all good now right yep oh i don't need to redeclare though so we already declared x and y there so we can just assign them here x and y there we go so that's it now let's copy this for i know but it's going to change a little bit so first before we actually do that we need to offset these points by half a unit so we will subtract half a unit from each one um oh this should be cell dot x and sell that y so we're attracting kind of the cell position by half a unit because it rotates around a different point and then also instead of rounding to it we need to seal to it instead ceiling rounds upwards it always rounds upwards whereas like normal rounding will you know round down or up depending on if it falls below the midpoint below or above the midpoint so we offset by half a unit and we seal instead and that's the difference between those two now we have our new rotated coordinates we just gotta assign them back to our cells so after our switch statement we'll say this dot cells i equals new vector three inch x y and then z is zero [Music] and there we go let's test that out um all right let's run a piece here someone press e so it rotates to the right great let's see if i can rotate the opposite way yep i can rotate both directions so that looks good and if i'm lucky we can get one of the other pieces that rotate different um either i or oh if i this takes too many tries i'll just stop but come on give me an eye or an o [Music] all right two more tries two more tries keep getting the red one nope all right we'll forget it i'm just gonna assume that is working correctly later on as we continue to build out the game we'll we'll know for sure if it's not working once you know once we're able to play the game in full but that is our rotation there is one problem however let me show you oh here i go i got the one i was looking for let's make sure this one rotates correctly yep it still rotates correctly so that's good too awesome now let's see try this problem um there's a way to kick yourself yeah so see i'm getting out of bounds um that's the point of wall kicks um so when you're up against the edge and you rotate you need a kick off of the wall to reposition yourself in bounds and so that's what we're gonna focus on next all right so let's go ahead and implement our wall kicks let's open up our game piece script [Music] and down below where we have our rotate function we need to add some additional logic in here so after we have applied our rotation we need to test various wall kicks and there are actually five different tests [Music] for here let me show you this graph it'll make more sense so there are five different tests but the values you test are different depending on which rotation you're doing if you're rotating from index zero to one you do these five tests from one to zero you do those five so it ends up being eight different eight times five different combinations so it gets a little crazy and then unfortunately i has their own unique set of tests and earlier when we went and created our data or set up our data class here all of that this entire exact um data set is included right here that's what all of this information is so we have the data we just need to use it basically um and the first thing we need to do is we need to add some of that data to our our tetromino data structure here so because we need to be able to associate which wall kick to which was which set of wall kick data do we need to use for this tetromino um right because unfortunately i is different than the rest if it was if it was all the one thing then it would be even easier but unfortunately it's not so let's first start by um adding a new field or a new property here for vector 2 in and this is a two dimensional array right it's a table of data so it's two dimensional we're just gonna call this wall kicks will be a public getter private setter and we're gonna initialize this pretty much the same way where we can just say now data.wall kicks and we can look up the correct set of wall kicks based on that and so that's it in terms of initializing the data um once again earlier in the video we kind of just copied all of this data this data set from github i mean you could manually type it out that's fine when i you know i just went through and took all these coordinates straight from this table and plugged them in here so it's just very tedious to do so but um so once we've added that we can go to our rotation code in our game piece here and we can start to utilize this so we need a function that can actually test these wall kicks let's add that and our function is going to return a boolean so it's going to return like a pass or fail or true or false test wall kicks we need to know which rotation index we are currently on and we need to know which direction we're rotating so we're going to pass those in as parameters um and then let's see test wall kicks so from here we need to figure out which of these values in the table we need to test you know are you going from zero to one or going from one to zero which index are you doing here so that that by itself is kind of its own set of logic that we'll put into its own function so we have a new function in here that's going to return an integer it's going to say get wall kick index and we're going to pass in these exact same two things pass those in and that'll be the very first thing we do in our test wall kicks is we need to know which index that way we know which tests to run um so get wall kick index passing those same values great all right so let's actually implement this function here so when i was first figuring out how to calculate this index i noticed a pattern um and so we can do a little bit of pretty simple math actually all we need to do is we need to multiply our rotation index by two and then if you are rotating like decreasing the index we need to subtract one because so you know it says like index zero is going up one and then we skip we skip one and then go to the next is going up and then we skip and go to the next then we skip you know and it goes to the next now if we're rotating the opposite way that's where you know you would you would increase by two or multiply by two but then subtract one uh that's pr everything i just said is probably you're probably like what the heck did he just say i don't understand but how this specific works doesn't really matter it's just like a pattern that i noticed that simplified how we can look this up so wall kick index is going to be rotation in x times two and then if you're rotating negative we have to subtract that by one and that is our value we need to return however it can be out of bounds just like um just like our rotation of index up here so we need to wrap this as well so we're gonna use that same wrap function and we can say walk wrap the walk-in kick index between zero and then the length which is going to be eight however instead of hard-coding that we can say this that data wall kicks get length zero just in case for whatever reason this were to change in the future you know we don't have some magic number floating around that causes problems all right so that's getting our wall kick index that's once again essentially telling us which of these things do we need which of these data sets do we need to test all right and now we can go and perform those five tests so here we're gonna create a loop for n i equals zero i is less than this that data wall kicks get length in the other dimension so instead of the dimension zero we're gonna get dimension one which are our five tests and we're looping through those each of these tests is defining a translation like a movement right it's trying to see okay first test you know although the first test you'll notice is no movement did you rotate and if if that's a valid position with no movement then great awesome we stopped there if that fails we move on to the next test okay let's try to shift the piece over one to the left does that work if that works great otherwise nope we gotta continue to move so you run through each test and um if all five fail well then you have to you know then that rotation as a whole fails and we actually need to revert everything we did here um but to test if our translation works first let's look that up in our wall kick dictionary here for our not our dictionary but our um in our two-dimensional array so we're grabbing the translation from our data set here using the wall kick index and then the test index which is i and now we can just try to move right we can just try to call our move function which once again returns a boolean of whether or not that movement is valid and so if this works then great you know if it if that succeeds then we can just return true immediately we don't even need to test the rest of the things because we're good we're already in a valid position if it ends up looping through all of these and it still hasn't returned true then that must mean the thing as a whole fails so we return false great so that's our testing wall kicks now we just need to actually apply that in a rotation function so we need to um so after we're done rotating here we apply our rotation matrix then we need to do those wall kick tests if test wall kicks if that succeeds really if it fails we need to revert everything we just did um so let's structure this code a little bit differently to make it a little bit simpler for one i'm going to take all of this rotation matrix code this entire for loop i'm going to put that into its own function i'm going to call this apply rotation matrix [Music] we paste that into its own function it needs this direction there so we'll still pass that in okay and so here we can say apply rotation matrix in whatever direction you know you're rotating and instead of immediately storing our rotation the next year or before we update our rotation index we need to store whatever our current one is that way if this ends up failing we can revert back so let's keep our original rotation is whatever it currently is and then we'll update it we'll apply our rotation matrix and then we do our wall kick tests okay if our wall kick tests fail um what do we need to pass in here we need to pass in the rotation index and we need to pass in our direction okay so if these fail we need to revert back so we can revert our rotation index back to what it originally was and then we can apply our rotation matrix again but in the opposite direction so we'll negate that and now it's the opposite direction and that is our wall kicks let's go ahead and test it out and we're not going to test out every single scenario because there's like 40 different scenarios plus there's different scenarios for the eye but um i know right here if i rotate to the left you know it would it wouldn't allow that because it would push me out of bounds so here it's good i'm not getting pushed out of bounds at all no matter how much i rotate i'm not getting pushed out of balance let's try a different shape or a different piece yep so great it's not you know it won't i can't no longer like you know rotate myself out of bounds so that's all looking good that is our wall kicks which is a pretty neat thing um in tetris that you probably might not even realize that it's something that's happening but it indeed is happening [Music] next up we need to implement steps and locks so a step is when the game forces your active piece to step down to the next row without player input and eventually that means your piece will reach the bottom of the board and then it will lock into place the other thing with this step is that this is how you introduce difficulty to your game is we can control how quickly that happens if if it's really quick then your pieces are going to fall to the bottom of the board really quickly which gives you very little time to make decisions of where you want to place them so if you add different difficulty levels to your game or if you just add levels usually what they do is that step time gets progressively shorter or quicker however you want to think about it at step time becomes quicker and quicker in order to increase the difficulty as you progress in levels so both steps and locks are related and they're both going to utilize a timer so let's create a few variables for this so first we're gonna have our step delay and let's say by default it is one second which i do believe is the like a level one in tetris like the first beginner level is like one second late so it's actually pretty slow and eventually once you're at like level 15 it's really quick um but you can actually implement that yourself you can customize this value however you see fit to your needs or desires of how you want your game to be um the other one will be our lock delay and let's say this has a value of half a second i believe most tetris games use half a second but once again both of these are customizable you can edit them in the editor if you would like now we need a couple private fields here one for our step time and one for our lock time which should both be floats all of these are floats and so the first thing we need to do is initialize these values as soon as our piece is initialized so our step time will get set to time dot time this is how you access the current time of your game plus whatever our step delay is so in other words this is saying we want our step time to be you know one second later than whatever our current time is so and then for our lock time this one's going to be a little bit different where it's going to actually just start out at zero and it's going to increase and eventually once it exceeds our lock delay then that means it needs our piece needs to lock into place so really what this is doing is it's saying as long as your piece is inactive we're increasing this timer and if you'd make an active move like a rotation or a just movement then our lock timer will get reset to zero so why don't we actually go ahead and do that so in our rotate and move functions we can reset that so for example in move assuming that movement is actually valid we can set our lock time to zero again so it gets reset and this actually works for rotation too because if you remember our rotation function does these wall kicks and our wall kicks actually do a movement so by adding this here it actually applies to both movements and rotations and so this will get r reset our lock time anytime you make an active move now we need to increase our lock time in our update function before we do anything and to increase our lock time we just say lock time plus equals time dot delta time delta time is the amount of time that has passed since the last frame was rendered and we want to do this before all of this other code because then if you end up rotating or moving it would get set back to zero [Music] now for our steps we need to actually check our current time and if our current time exceeds whatever we set here then we need to call our step function and we're going to do this after everything else so if um time dot times so our current time is greater than or equal to whatever our step time is well then we need to call our step function which we haven't added yet so let's go ahead and add our step function here first thing we want to do in our step function is actually set our new time again we need to push that time further into the future again so we say this dot step time equals our current time plus our step delay so it's the exact same line of code we wrote up here we're just pushing that time further into the future you know by one second or however long your step delay is that way this will get called again in one second and then after it's called you know it just keeps getting called over and over and over again every one second or however long you set this to in terms of the actual step all we really need to do is tell our piece to move down so we just call our move function and say vector 2 in that down and then finally when we perform a step this is where we will check for a lock so we're only going to check for lap a lock when we actually stop so here we can say if our lock time has exceeded the amount of time we've specified it can be inactive for well then it needs to lock into place so we'll call our lock function let's go ahead and add our lock function um oh i mistyped this this should be this dot lock time is greater than or equal to our lock delay there we go lock time is greater and equal to our lock delay and it locks into place when it locks into place we need to just set the piece on our board so we say board let's set this piece and then we need to spawn a new piece we can call our spawn piece function we added earlier and there will be one other thing we do in here but we will come back to that in another section the other thing too is in our hard drop function it should instantly lock in place so we can also call our lock function from here and i believe that is it so we created some variables we initialize those when our piece is initialized in our update function our lock time will always increase and then it will get reset in any rotation or movement that's happening here it gets reset back to zero anytime you do an active move and then for our step time this one works differently where we just check our current time and if that exceeds this value here then we call our step function then that time gets updated again we tell our piece to move down and we also check to see if we should actually lock the thing in place now you might be wondering if we call a move here which resets the lock time won't this never get called then our lock time will always be zero and it'll never exceed our lock delay no because eventually our piece hits the bottom of the board which prevents it from moving down so our move down will actually fail and therefore our lock time won't get reset and so then this will eventually gets called we'll get called and we can let's go ahead and just test this out and make sure it all works correctly and once again we can customize those values here in our editor so if we want you can play around with those and once again our step delay importantly is how you can increase the difficulty of the game and i'll show you that so right now i'm not pressing any inputs and our piece is moving down automatically i can still go left and right and it'll still just continuously go down let me let me help it get to the bottom a little bit quicker here and once it gets to the bottom it's going to take half a second and then it locks into place and then a new piece spawns and now i'm controlling this piece if i hard drop this piece boom it instantly falls and it instantly locks in place and a new piece spawns now notice here if i have a piece at the bottom just because it's reached the bottom of the board doesn't mean it just locks in place right away as long as i'm making an active movement i can still control this piece but as soon as i stop making an active movement it will then lock in place and this is useful and this is actually a really good example because this allows me to then shift the piece over and underneath our green piece here and then i'll stop moving and it finally locks into place so that's exactly what i want now notice here if we decrease our lock time let's say to 0.1 second these are falling much quicker which makes the game a lot harder because i have very little time to think about and plan out my moves and this is actually will get even quicker at higher um higher levels of tetris so there we go we can actually it actually feels like i can play the game now which is pretty neat all right so at this point we're actually very close to finishing our game of tetris the only real thing missing is the being able to clear lines as you fill them up so anytime a piece locks into place we need to test to see if that each row is full and if that entire row is full then it needs to get cleared and then everything above it needs to shift down we're going to sort of do this not recursively but we need to make sure we're iterating through every row in the correct way otherwise it won't work correctly let's open up our board this is all going to be happening in our board script so we can add some functions here for testing or clearing lines so let's say this is a public function called clear lines and we just need to call this from one place in our piece function so i mentioned anytime a piece locks into place that's when we're going to try and clear any lines so we'll just call that and we want to call that in between set and spawn so we set our current piece then we try to clear all the lines any lines that can be cleared and then we spawn the new piece afterwards and so from here for our clear lines we need to basically loop through every row in our tile map and then determine if every column is you know full if there's a tile set in every column and if so that means that entire row is full which means we need to then clear it and then shift everything down so there's a few different things happening but it's actually somewhat straightforward to iterate through our rows let's declare a row value here and we want to iterate from the bottom the very bottom row to the top um so we can utilize our bounds for this we established these bounds earlier wrecked ends here so let's grab our bounds here bounds equals the southbounds [Music] and we're going to start at the very bottom so all we need to do is say row equals bounds dot y min and then we can loop from y min to y max and we'll do this in a while loop so we'll say while row is less than bounds dot y max we're going to continuously check for a line clear so first we need to check is this row full and we'll handle all of that logic in a different function so this will be a this function will return a boolean and we'll say is line full we'll pass in the row we want to test and this is now going to iterate through our columns now to verify if the line is indeed full so for this we also need our bounds so we'll grab that again and we can set up a for loop to say i'll call this column and we'll say column equals bounds.xmin and then so long as our column is less than bounds dot x max we will increase our column so we're iterating from left to right it doesn't actually matter whether you go left right or right left and we're going to grab the position there so the position of that column so will be a vector 3 inch and the column is our x values our x coordinate and the row that we passed in is our y-coordinate and then our z is just zero we're not utilizing z but it needs to be a vector three because that's what tile maps use and here all we need to check is if our tile map has a tile set there so if this dot tile map has tile at that position that means it's good and it should move on to the next column so really what we should do is say if if there's not a tile then that means this line is not full if if there's not a tile there we immediately know this line is not full and we can we just return false right away and so if it ends up looping through this entire thing without ever returning false then that must mean every tile has been set and therefore we can return true and so that's what we want to test here while we loop through all of our rows we first want to check if this line is full for the row we are currently on [Music] well now we need to do a line clear and so let's add another function for line clear [Music] now if and we'll come back and implement that in a second let's just finish this if the line is not full well then we need to iterate to the next row so we can increase the row and now there's an important thing here if we do a line clear we do not iterate the row we only iterate the row in the case that it's not full and that's because once we do a line clear everything above will fall down which means we need to re-test the same row again because it's actually going to have new tiles on it so we only increase when it's not full so finally let's finish up our line clear function here this basically needs to do very similar things to what we've done here we're going to need our bounds again so we know how to loop and we're going to do the same exact for loop for our column so i actually just copy that we have the same for loop of looping from the x-min to our x-max we're gonna grab our position the exact same way so i'm gonna copy that line of code two and then we just set our tile map tilemap set tile at that position to null right so we're clearing the tile from that at that position oh and i didn't i meant to we need to pass in the row to our line clear function so let's pass that in and then let's pass that in here there we go so that's the first thing is clear all the tiles from that current row that is being cleared makes sense the harder part is having every tile every row above this one fall down so let's establish another while loop here while row is less than bounds dot y max so this is the same while loop we did up here while row is less than bounds dot y max and then for every for every row now we need to once again iterate through every column in that row so we'll have that same for loop and for every column we once again need to get the position of that tile and now in this case we actually want to say row plus one because we're grabbing the row above it we're grabbing the tile above this row and then that tile will get set to this current row and so here we can say we can grab what that tile is too because we need to get what that tile is and then just set that set it with the same value so here we actually need to declare a tile base and i'm going to just call this like above so this tile is the tile above and we'll say this tile map get tile at that position which once again is the position at the row above us now that we have that tile we can change our position back to just being our current row so column row zero instead of row plus one it's just our current row and now we can set our tile map this.tilemap set tile at the position using the same tile that is above so we'll pass above in there and great so and we're doing that for every column and every row all that's missing is we need to increment our row here so we'll increment our row and that's actually it and that in sense completes our game too we're actually going to do a little bit more but this is now a fully playable fully playable game at least it should be let's play the game a little bit and test this out so here i should get a line clear which it did nice let's play a little bit longer make sure everything is still good i should get another line clear here there you go that line cleared and you can see how things get shifted down as the line's clear there we go and this time it'll clear two lines so we should be left with orange orange orange green red red in the bottom and there you go orange orange orange green red red so there we go everything is working we have a fully playable game of tetris now one thing we can do really quickly because it's only like one or two lines of code is a very simple game overstate let's go back to our board script all we need to do for game over is when a piece spawns if when that piece spawns it's already in an inactive position or sorry in invalid position well then that means you've gotten the game over right so as soon as we um spawn our piece we'll initialize the piece and before we set it we only want to set it if that if it's you know actually valid so we can say if this is a valid position this active piece that we just initialized at our spawn position oops this dot spawn position then we're good we can set the piece otherwise if it's not valid well you've got a game over so we call a game over function let's go ahead and add our game over function game over and just for the sake of time i'm not going to really do anything fancy in game over all i'm going to do is clear the entire board so it starts fresh again so i can just say this dot tile map clear all tiles and that's it for now you you very much can do many other things inside here you know for example you might want to load a game over menu display some game over text or some ui just for the sake of time i'm not going to do any of that but at the very least i'm going to clear the entire board so we can basically start playing again from from scratch and that's it that's our game over we can test this really quickly by just spawning and letting pieces drop instantly so i'll just hard drive all of these pieces and as soon as i reach the top so boom yep so at that point it failed it tried to spawn a new piece but it couldn't and therefore i get a game over and clears the board so at this point we could stop this is a fully playable game of tetris with all the game logic in place i do want to add one more thing though in a lot of tetris games you'll be able to visualize where your piece is going to land before it actually hits that spot so the they call these like ghost pieces so while your pieces at the top you will see like a sort of ghost version of it at the bottom to indicate exactly where it's going to fall so let's go ahead and we even created that tile map in the very very beginning of our tutorial we just haven't done anything with it but let's go ahead and actually implement our ghost piece let's go ahead and create a new c sharp script i'm going to call this ghost and we're going to honestly just be copying a lot of the code we've already written so this shouldn't be too bad let's start fresh here we do need to declare a couple new variables for one we need to establish which tile we want to render um for the ghost piece and i need to import unity engine tile maps in order to to declare this so we'll establish our tile we also need our reference to the main game board that we will be tracking and we also need a main a reference to our main game piece that we will be tracking i'm gonna actually call that tracking piece and finally we need a reference to the tile map not for our main board but for our ghost board we'll have a tile map here and this will be a property so this will be a public getter private setter and then finally very similar to how we have our other game piece we need to have a copy of the cells we can have that exact same thing and we also need our position it's the same thing as well finally let's establish a couple of these references in our awake function so here we can say private void awake we can establish our tile map by saying get component in children tile map and then our cells we can initialize that array it can be a new vector 3 in array with four cells i'm just gonna assume four cells you might need to do something different here if you're doing like really custom shapes for your tetris game but that's a very uncommon thing now in terms of the logic we need to apply to our ghost there's really kind of four steps to it there's first we need to clear the piece then we need to copy the cell data from the piece we're tracking then we need to drop the piece we need to basically simulate like a hard drop and then we need to set it let's establish those four functions we have clear we have um i can't type right now we have copy [Music] i am completely failing at typing we have let's see clear copy um drop and set clear copy drop and set and we're going to call all of these in order inside of late update late update is a special update function that gets called after all other updates and that's important because you know we're we're handling some game logic in our main update function for our our game piece but our ghost piece needs to track this and so if we've done a movement we need to make sure our ghost piece has you know is tracking that correspondingly therefore we need to make sure our ghost updates after our main piece and so we can just do this in late update to make it easy so we'll call all of these functions in order clear copy drop and set and now clear and set are really simple because those are the same thing we've done for our board our game board we can literally just copy this our set function here we might need to change a couple things what we're changing here is it's not going to be p ps.cells it's just going to be all of this is just going to be this so this dot cells you know this stop position and then instead of this data dial it's just this dot tile so we loop through all of the ghost pieces cells and then we get the tile position for our ghost piece cell and position and then we set the ghost piece tile and then for clear same exact thing we can copy that all we need to change is instead of setting the tile we null out the tile to clear it [Music] for our copy and drop here copy is pretty straightforward we're going to establish the same for loop where we loop over all of our cells and we just need to assign our cell data to be equal to our tracking piece data that way it you know if we rotate our tracking piece our cells here for our ghost piece will get updated too so we said this cell is i equals this tracking piece um dot cells i and that's that's it that's oops i mean to do that that's all we need to do for a copy and finally there's drop this is the little bit harder one it's essentially simulating our hard drop that we added in our game piece so on the right here we have our hard drop but really this is calling our move function so we have to like basically re-implement a move function which can get somewhat complicated um but unfortunately we got to do that so let's add vector three ends first we just need to get the current position of our tracking piece um so position equals this.trackingpiece.position and from here we need to basically loop um [Music] we need to loop through every row in our tile map in our our board like the actual board tile map and from the bottom to the top and as soon as we find a valid position then we know we can put our ghost piece there if it's not valid well then we can't place our piece there so to establish our loop here we need to know what our current um we need to keep track of what our current you know row is going to be so the current row we are on or the current row our active game pieces on is going to be position.y right and then let's establish a variable for our bottom of the boards we need to calculate the bottom of the board here which is going to be negative this dot board that board size divided by two so we're if you remember from earlier you know positions are in the middle of the board so we need to get half of that to offset offset by half and then we're going in the negative direction so it puts us towards the bottom of the board instead of the top and then we actually need to subtract one from this we need to we need to push it one lower so we will subtract one more from that value oh and then i this should be board size dot y we only care about this in the you know for the bottom it's the y axis so that y now we can go ahead and actually loop through all of our rows here so for ins row equals current so the row we're currently on then as soon as our row gets to the bottom and notice here we are looping actually more so from the top to the bottom and some of the other loops we've done we've usually looped from bottom to top but in this case we're looping from wherever our pieces on the board to the bottom because we're simulating it dropping so it's falling to the bottom so we're we have to decrement our row here it needs to decrease every iteration and our position here we can change its y so we can say position.y equals whatever this new row is at which point we can test if that position is a valid position so we can access our main game board and all our is valid position function pass in so we need to pass on our piece here first which is our tracking piece pass in that position we want to test and if this is a valid position then we can update our our ghost pieces position to it and so essentially yeah that spot is free for sure our our piece will be able to fall to that position therefore we know our ghost piece should get set there too and if it's not a valid position then we stop there we can't go any further we're going to prevent any more rows from being tested because that is now where the piece will drop if you were to do a hard drop so to stop our for loop we can just break out of it we just call break now the one thing that's actually missing here this will cause a problem when we check if this is a valid position this is going to cause a problem because we're tr checking let's say the pos we're starting at the position of our the piece we're tracking this what say this is going to return false because that very same piece is occupying that position and therefore you know this it thinks it's invalid so what we need to actually do is we need to clear the tracking piece from the board before we loop and then after we loop we can set it back all right so all we need to do is save this that board that clear our tracking piece from the board and then we'll set it back after we're done doing the this test and once again the point of this is because if we don't do this is valid position is going to continuously return false because that same very that very piece is already occupying the position we're trying to test for our ghost piece and so we clear it from the board to pretend like it doesn't exist that way we can check all the valid positions without any interference and finally once we're all said and done we'll add it back in the same position it was before um so that's it for our ghost let's actually test this out [Music] let's add this ghost script to our ghost grid here let's establish some of these references so for our tile i'm going to use the ghost tile that we established for the board i need to reference the main game board so i drag that in from our hierarchy over here and then i also need to reference our game piece so i'm going to also reference that in or drag that in to reference it and let's go ahead and test this out so there you go so you can see at the bottom that is our ghost piece down there it's mimicking our mimicking our current piece just you know pretending like it was dropped all the way even if i rotate it still gets updated and everything is good there and so this is a lot better this just really helps the players to better understand what moves they're actually making because sometimes there can be a little bit of a disconnect between the piece being at the very top and you trying to visualize where it will fall into place so this just really helps this definitely isn't required but it's in most tetris games you will see some implementation of this of this so and that actually completes our tetris tutorial there's nothing else we really need to cover in this video there are more things you can do so i would challenge you individually to see how you can implement maybe scoring as well as um a difficulty curve right so how can you increase this step delay to be faster and faster as the game progresses as the game progresses so it gets harder and harder those are a couple things that i will let you guys try to experiment with and try to implement on your own i really appreciate you watching this tutorial and i hope you learned a thing or two along the way give the video a thumbs up or down to let me know how i did subscribe to the channel for more videos just like this one and leave a comment recommending what you would like to see next if you want to support my work even more you can become a patreon member to receive exclusive benefits alongside my tutorials i also create unity assets that you can get access to links in the description of the video thank you for watching see you in the next one
Info
Channel: Zigurous
Views: 136,732
Rating: undefined out of 5
Keywords: unity tutorial, unity, how to use unity for beginners, how to make a game, unity 2d tutorial, game development, how to make a game in unity, unity 3d, unity 2d, game dev
Id: ODLzYI4d-J8
Channel Id: undefined
Length: 117min 39sec (7059 seconds)
Published: Sun Aug 15 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.