Create 2048 in Unity - Learn how to make a grid game [Part 2 of 2]
This is part 2 of the 2048 series. If you haven't watch it, go check it out now and come back: https://youtu.be/TeurfjuEIgA
In this part we learn how to shift and merge our blocks. Now it's getting exciting!!
Scripts and Assets: https://bit.ly/38CA07z
this is part two of the 2048 series if you didn't watch part one go watch that now and then come back here to finish the game let's get into it okay so last time i helped you create this grid here which spawns our blocks onto the nodes so now we need to actually shift our blocks uh depending on the player input so we have just finished our spawn block state and we need to go to our waiting input state so let's say change state weighting input and then let's hook into our update function so basically if if it's not weighting input we don't want to do any things so we'll say if state does not equal waiting input let's return don't do anything but if it is let's say if input get k down and let's say the left arrow now we need a function here to shift all the blocks so let's create a new function void shift and this will send in a direction like that so then we'll say here shift and this will be left as that's the key that we're pressing um so now let's actually think about this because this is where it gets a little bit uh confusing so let's make it let's just let me get a good example here come on come on okay perfect so we're shifting left here so if we were to calculate this block first we would say all right is there a space available next to me yes okay is there another space available next to me here no because there's this four block here so this one would actually shift to here then we would go to calculate this one which would shift to here but that's not what we want what we would actually want is them both to shift across so this one would go to here and then this two would now shift across and take this spot here so we depending on the way that we're shifting we need to order our blocks so that they calculate in the correct order so how do we do that okay so if we were shifting left here we know that we need to calculate from left to right so we would order our x to go from zero up and also in the same way if we were going to shift uh if we were going to shift down we would also want to order our y to be from zero upwards because we would want to do from bottom to top wouldn't we so let's just do that uh instance first so let's say ordered blocks equals uh blocks dot order by now first we're going to order it by the blocks x position okay because we want to go from 0 upwards and it would be very handy if in the same way that we've got this helper function here on the node we've also got it on the block so now we can just say let's order it by the blocks x position and we also want to do the same thing but with the y so we'll say block um whoops no so first we want to order by then we want to do then buy so first we'll organize by the x then we'll organize by the y so b and then the b position y so now this ordered blocks list has blocks ascending their x and y but what if it's the other way what if we're shifting not to the left but we're shifting to the right or what if we're shifting not uh what if we're shifting knots down but we're shifting up so if we're shifting up or right we need to change our behavior so if direction equals uh right or direction equals up let's actually full on order uh reverse our list so this will be to list and this will be ordered box reverse okay so now our ordered blocks list with just those two lines will already be in the correct order for us to calculate all of our blocks hope that makes sense okay now this is this is really getting into the meat and potatoes of it okay now that we've got our ordered blocks we will loop through them [Music] lock and we will so i was thinking about how to do this actually in the shower last night how to best to do it so i'm going to do it in a do while loop and the reason i'm using a do while loop instead of a while loop is because i always need to do this at least once for every block so the difference between do while is it will always execute once and then basically we'll just act like a normal while loop so basically what we're going to do is we are going to start with our current position so var and i'm going to call it next and this will this will all make sense in a moment and i'm going to call this block and i'm going to take its node which i haven't put on yet so this block just like the node holds the occupied block the block itself is also going to hold a reference to the node that it's on so node node and we actually also need a function here so that when we actually set down our block it sets its node so set block and this will take whoops it will be a void set block this will take in a node so i'm going to say first if we're moving this block and i'm already on a node i need to release my current nodes hold i don't know so i'm going to say but this but we may not actually have a node because it might be the first time we're actually setting the block down so no node is set so we have to actually if check this if node does not equal null we'll say node and we'll take its occupied block and we'll say null because it used to be me now it's not because i've moved we'll set our node equal to the new node that we've just sent in and we'll also say the nodes occupied block now this new block that i've just um occupied i'm going to say i am now the new block that is part of that occupied block so set block so now before we continue here when we're spawning these blocks here we're initializing them but we also need to set the block to the node okay all right so now down here as we've got our node reference now so basically what i'm doing is i'm saying the first place that we're going to check is ourself okay so that that's always going to return true there is a spot because we know we we're already in it so here the first the first thing that we're going to do is we're going to say block set block and we're going to send in um next okay so we're immediately we're already saying all right the next block that we need to check is our own okay so immediately into this do loop we're saying yep block set ourself again okay we're literally unsetting it and setting it but now we need to do something we need to set next to another block the next one down the row so that on the next iteration of the while loop this next will actually be the next block in line right okay so what we need to do now is we need to find a possible node and we need to take our current node which is next okay so that could be the node that the block was originally on or it could be the next the next block next to it depending on how many iterations of this while loop there's been we need to get uh so let's make a little function here actually this will be node get node at position and this will just take in a vector 2 pause and now the beauty of using just a standard list as opposed to a 2d array here with an x and a wire is we don't need to do any special checks to see if it's out of bounds of the array we can just say i'll return nodes first or default node position equals position that we just sent in like as simple as that if we were using a 2d array here we'd have to say is x less than 0 is y less than 0 is x more than the width is y more than the width we would have to do all those checks before we can even even uh ask the array or else we'll we'll throw an error and out of bounds error so that's the beauty of just using a list not as performant but far easier to write and understand so possible node get node at position and we're going to be sending in our next dot position okay so the one that we're already on plus direction so our shift direction now this could return null if it's out of bounds so this might be like minus one uh so we need to check here we need to say if possible node does not equal null so now we know that a node is available we know a node is available sorry not available is present so now we just need to check is that node available for us to actually place on so we can say if possible node dot occupied block equals null awesome that's what we want let's say next our next block to check equals possible node do you say so now let's just go through this we're saying we're starting with our current node and we're just setting it immediately but then afterwards we're saying all right is there another node next to me if there is and it's available let's set this next variable to this new block that i've just checked is available okay so then in this while loop will say this will be the condition to see if we should go back up to the top and set this new block as our as our furthest block so we'll say this condition will be if next does not equal uh block dot node yes sorry our next as long as yeah so if next is different to our currently set node then we know that we have actually set something here and that we can continue this loop does that make sense and just for um demonstration's sake let's just transfer let's just change the transform direction right now like as we're here so we'll say uh block.transform.position equals so we've here just discovered our furthest block that we're actually can move to we'll say block transform position and we'll send it to now the block node dot position because just remember through this do loop we are setting the new nodes each time each time we move we're setting it that doesn't mean that we're actually moving to it okay so we've got to manually do that here so let's try this out let's go back to unity and if we press left here perfect so it moved to here and then it reset the do loop and then it moved to here reset the do loop and then it tried this one it tried this but it said it returned no yes there is a possible node here but is that is it occupied and yes it is so it did not set the next node all right which means this next is the same as the block's current node so then it came down to here is next different to this blocks node if it if it isn't then we haven't found a new node to go to so just exit the block and then move the block there okay does that make sense let's just try it one more [Music] time perfect working just as expected now uh just teleporting the block there isn't too fun and it also doesn't uh give as much visual debug like if you can imagine when there's all these blocks that are going to be on the screen and we're trying to work out all these bugs you know why is this happening why is this happening just pressing the buttons and then the blocks just teleporting all over the place it's not going to tell as much it also doesn't look that good so instead of showing you how to manually load objects which is not actually how i would do it anyway i'm going to show you a package which will do it for you and to be honest it's the the best unity package uh that has ever been released in my opinion i use it in every single project so it's called uh dot twin and basically it will just handle all of your transform movements okay and you can add different um ease types to it and it's just it's just super super easy to use so go there this is free by the way uh just grab it from the package manager if it's not showing up here go to the unit asset store just click check click import into unity and you'll have it so i'm just going to import when it imports you'll get this little menu here and the good thing about dot twin is they know that you're not going to use every single component of it so you can actually turn off everything here which we don't need any of this and apply and it makes the binary the the compiled version far lighter on your on your end result so now that that's done if you just go to your scripting editor re reload as a whole load of stuff has just been injected now instead of us saying this here right we can say now this is this is the magic we'll say block.transform and dot twain has given us a whole load of functionality here we can now say do move okay do move and we want to say the first the first parameter is the end value where do you want to move this and we want to move it to the position of the node and then we want to say how long do you want this transition to take now i want all my blocks regardless of their travel distance even if they're just moving one or if they're moving all the way four i want it all to take the exact amount of time it will i'm sure it will give it a nice snappy feel so serialize filled float travel time and i'm going to default this to something like two that might be a little bit too fast but we'll see so now i'm just going to send in that travel time just like that so now you'll see with just just that one little line there how much improvement this will make like that and while we're at it let's um duplicate this line here and we'll add writes let's not write alts right and then this will be up array up down array down so now we should be able to move all over the joint down beautiful look at that [Music] well yeah we can do that because uh [Music] we're not actually stopping player input we'll get to that okay so actually we can get to that right now we can say as soon as we've triggered this shift we'll say change state um moving so now we won't be able to actually add additional input okay now we need to worry about merging so um okay so now in here we're saying if the if the block is not occupied cool we can move to that location but what if the block is occupied but it is the correct block that we need so let's change this let's say if possible node dot occupy block does not equal null and possible node dot occupied block dot value is equal to r block value if so [Music] we need to say that this block can merge into this new block so uh let's add a public variable here on our block and this will be called block and let's just call this merging block so we're saying if this is set we can check this and then everyone can all other blocks can know this block is in the process of merging okay we don't we don't care what block this is they just need to go if it's if it's not null then we know this block is merging in this transition so here we can so here we've said the block is the the block on the next space is not null and it's the same type as me so what i'm going to do is i'm going to say block merging block equals the occupied block and then we also want to make sure that if there's other blocks behind me they know that they can now take my new spot also i need to let the other blocks who might be behind me know that my spot is now available even though that i'm not setting setting block okay so a few things are going to have to happen here so let's let's create a function on the block public void merge block and this will take in a new block and this will be the uh the block to merge with so then in here we can say our merging block equals the block to merge width [Music] we want to allow other blocks to use our spot so we will say node dot occupied block equals null [Music] i know that there should be something else here okay so we're saying that yes other nodes can go into our spot we're saying that i'm merging already so i so nothing else is allowed to merge with me because i'm already merging but what about the block that we're merging with so the block that we're merging with also needs to be telling other blocks that they can't merge uh because they're already merging so yeah so picture this ah perfect picture this so say there's a two here a two here and a two here okay and we push them all to the left so then the first twos will start merging and we only want two blocks at a time to merge we don't want all three blocks to merge so these two will go into this block this one will unlock its space and say yep i'm merging with this one so then this one will come along and it will check this block and it will be like nope i can't merge into you but then it's going to come over to this original block here and there's nothing saying that this block can't merge because it's a two so i'm just going to merge into it so we need a way to make the original block also say that they're merging so this here is for the block that is merging but we also need a variable here for the original block so this will be merging so then we can say block to merge with dot merging equals true and that that will protect both blocks um set the base block as merging so it does not get used twice or three times or four times depending on how how big your board is so uh let's also make a little helper function so public bull can merge and this will take in a value and will return first up is say so this function will be called by another block so it's going to detect the block it's going to send in its value so we're going to say first if value equals value so these two values match and [Music] we're not merging and uh merge merging block equals null [Music] so we need to check all of those things and if all of those things uh return true then we can say yes we can actually merge this block so instead of doing this check here manually we can just say uh is the block does it have an occupied block and it's not null yes okay well let's say possible node occupied block can merge and we're going to send in r block value so that will return can our new block merge with this current block if so let's say block merge merge block and we'll send in the possible block the occupied block and we only want to run one or the other so let's say alph elsa here i know this may sound a little bit convoluted but it's a work in progress we'll see so here we know the note is present if it's possible to merge [Music] set merge otherwise can we move to this spot and then if none of those hit we're basically not doing anything we're just we're just saying you know uh if not if none of those hit we're saying none here um and do while loop because we haven't found a new block to do anything with so let's see if i haven't broken anything yet and it looks like i have ah that's because we're setting our chain state to moving and then we're not actually continuing the game so to do that this is an asynchronous function right like we're we're calling it and then curry can continue down here but while that's happening this is still moving to its to its destination over the travel time so we need a way a way to wait for this um and to be honest we don't really want to do it here anyway so let's now now that we have uh gone through our ordered blocks and we've set our new block positions whether it's by setting it to the actual place or saying that we're merging we can now go over this 4h in ordered blocks we're going over the list again and we're going to say far like where do we need to move now so it's either going to be to our new node or if we're merging as a block it's going to be to the base block isn't it so uh move point equals and we're going to check block are we merging if so our move point will be the block dot merging block dot position otherwise we're going to be moving to the block dot the node that we've set dot position so now we have our end point now a beautiful thing about dot twin here is we can we can queue up a whole load of different animations and then get a call back when it's finished so look here we can we can actually say on the end of this [Music] dot on complete right and then we can actually run a function once this completes so we can run this logic when this when this block actually finishes transitioning but we actually need to do this for a whole load of blocks don't we so we need to know that they're all finished so we can do this we can say var sequence equals dot twin dot sequence and we can take this little move script here and we can just say sequence dot insert and we're going to be running these all at the same time we don't we don't want to sequentially run them we want them all to run asynchronously so we're going to say we're going to insert this at index 0. we're going to just chuck in that that twin that that we just had up here right and now this sequence is holding all the blocks uh travel places travel travel animations so we don't want to hard code that we want to actually move it to the move point and now instead of directly saying on here on complete we can now say sequence sequence on complete [Music] please run this code once once they all once they've all finished their their animation we don't care about the ones that we're just moving to a new spot but we do care about the ones that were merging we need to do something about that so 4h uh ordered blocks where block uh merging block does not equal model yes so we're going to loop through all the ones that are merging into a new block and to merge we need to do a few things so let's make a function merge blocks and we'll say block base block and then block uh merging block [Music] so the first thing we need to do here and let's just call this match blocks block and then block merging block pretty easy now we already have a function called spawn blocks spawn blocks so that's that's being called with a number with an amount and then we spawn all the blocks uh using that but that's not really what we want we want another function called spawn block we just want an individual place to do this and we want to send in a node and probably also a value [Music] so if we just grab this code here so then the node is being used and then the value straight up and we'll just cut that out and say value and now we can call this spawn block send in our node and then send in that that value string again so that's now using this and now that we've got this function here we can specifically spawn our new block so spawn block uh base block it'll be on the base block node and then the value will be the base block dot value times two yes so now we're spawning this new block then we need to destroy the two merging blocks so let's make another function remove block block and then we'll say first we need to remove it from the list so blocks dot remove and then remove block yeah finish it for me and then we need to actually destroy the object so let's destroy block game object cool so now we need to just destroy these two blocks so remove block face block and then remove block merging block [Music] cool so we're going we're moving them when they're finished we're going to go through the ones that actually merged and we're going to say merge block which is going to spawn the new block of the new value and remove our previous blocks that just merged yes cool and we don't need to reset these two variables because these blocks are actually getting destroyed so then once we merge those blocks now let's say change states spawn blocks spawning blocks and that will now go to spawn the blocks but now the round is at least over zero so now it's only going to spawn the one block then it's going to go to our waiting input so let's see if that works it's going to be a miracle so up down oh object reference nut let's check what that is make point block dot block.merging if block's merging do that otherwise so we this merging block is not the orchestrator we need to say if block merging block does not equal null then we'll use the merging block position otherwise we'll use just our position yes let's try that up down that merge but that didn't look right yes that is not right at all um block merging block dot position aha so here we're saying the move point is going to be the merging block position but that's not correct we need to set it to the new node of this merging block because if you remember we're setting the nodes but we're not actually moving to them and then we're just trying to say here let's just set it to the current position of the merging block which is incorrect so we need to set it to the node the new node of this block's position yes so so as a pointer like when you're going through this you should always set it to the position of the node and not the block because the node may be out of sync with the block good rule of thumb space that is working pretty nicely don't see any bugs until right there so what happened there what happened so we've got a four and an eight there what did that mean what did that mean okay so we're we're merging in the wrong place so merge blocks block merging block spawn block base block node aha okay so we're we're i'm so i've made this function here merge block and saying that the first one is the base block but then here i'm actually sending in the block as the base block which is incorrect it's the merging block which is the base block and then the block is the one merging into the merging block okay let's try that [Music] was that right yes i think it was right [Music] oh yes nice i think that's working [Music] i don't know if i'm good or bad at this game i think i'm probably [Music] not very good [Music] i'm sure you guys don't want to watch me play this the whole time um plus i was probably about to lose cause i'm trash so uh we need a win condition don't we so we can actually to make this dynamic we can say serialized field private inch win condition and let's default this to 2048 as that is the name of the game but you can change this to any uh duplicate of 2 below 248 and we can actually make that the win condition so you could possibly create a game where the first few levels is only like 32 you know as a little practice so where do we actually look for a win condition here probably in spawn blocks right so if we've spawned a block here we can actually check lose so instead of just putting this comment here we can actually say change state lose as we know that they have just spawned in a new block in the very last square which makes moving impossible and then down here and change states we can now say blocks is there any block with a value of the win condition if there is let's send in um game stage win otherwise let's go back to waiting for player input what are you doing oh that is incorrect so is there is there any any blocks with the value of when condition win otherwise waiting input so obviously you have to set your win condition correctly if you set it to a number that is not actually available they'll never be able to win so i don't know if this would be better as uh like an enum you'd have an enum for each number or or what but right now i've just got as an int cool so now here we can let's make some uh two more references private game object win screen and the lose screen and then in our win we'll say whoops screen set active true golly gosh my typing then uh loose screen blue screen set active tray and then in the game let's create a canvas and this would just be text on a screen and this will be the wind screen oh my golly gosh when screen and then lose the screen let's make these both centered in the screen and they're tiny so then this wind will be the winner this one will be loser [Music] and then let's disable them by default and then in game manager slap in our windscreen where are you windscreen lose screen and let's set this win condition to 32 just to see if this works if i can get to 32 let's see let's see i think i'm pretty trash of this to be honest [Music] well i'm close i'm so close [Music] 32 winner there you go that's the game that's uh i think i i couldn't see any bugs not too much code not so clean but could probably be uh refined quite a lot but uh that's about it so there it is i hope you enjoyed the video i know it was pretty lengthy leave a like if you stayed this far and let me know in the comments what you thought tara dave out