Awesome Grid Building System! (City Builder, RTS, Factorio, Survival)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello and welcome i'm your code monkey in this video let's learn how to make an awesome grid building system so we have a grid in our world and we want to build something on it and that something can obviously be anything you want to build on a grid so for example you can use it to place enemy positions in an rpg or maybe resource nodes in a survival game play spots in a farming game or maybe some traps in an action game this is an extremely versatile very useful system you can definitely look forward to me making some more complex tutorials using this system as a base for example i'm currently building a factory automation game that uses this system as the underlying base so if you're watching this in the future check the playlist linked in the description where i'll be adding any systems i build on top of this one the example that i won't show here is on placing buildings kind of like a city builder and just for fun i won't be making this in 3d but it works pretty much exactly the same in 2d as well the underlying gridless supports both 2d and 3d i will showcase the working 2d version in the end now i've already made a building system once before that one is a free-form building system it works based on physics so as long as there's no collision the building can be placed anywhere go watch that video if you want to see how that one works and that method is also what i use in my complete builder defender course so that's a great freeform system but here let's go with a more rigid grid-based approach and for that obviously the very first thing that we need is a grid like i always say you should always reuse as much code as possible so as you might expect i'm going to be using the grid system that we made in a previous video it's quite amazing how versatile this grid class is i've used it in dozens of videos by now and it's extremely useful if you're new here go watch the video link in the description to see how this class was made completely from scratch so let's begin making our system let's make a new empty game object name it the grid building system and let's make our script so an euc-sharp script for the grid building system on the object let's reset transform keep things nice and clean on zero zero and just drag the script okay now here first things first let's start off by making our grid so let's make a private void awake and on the way let's instantiate the grid alright so here is the basic instantiation code again go watch a grid video if you're not familiar with all of this we're just defining a grid width a grid height and a certain cell size then we're instantiating the grid creating a new one passing in the width height cell size the origin and then a constructor for each grid object and for that one it's right here the grid object which stores a reference to the grid the x and the z like i said you'll watch the grid video to see how all this was made everything here was created coming from scratch in a separate video so what this does is it creates a grid and on each grid position it contains a grid object so let's visualize this to see what it looks like for the visualization let's go inside the grid class and in here i've got a very simple show debug volume so if i set this to true it will automatically draw some lines and create some moral text so on the grid object let's also just do a public override override the two string and here let's return just the position so x then a comma and a z okay so we should be able to visually see the grid let's see and if right away we can see the grid positions and if we enable gizmos we can now see each area for each word position so as you can see over here we've got zero zero then one zero two zero three zero and so on and here it is all of them going all the way up to 99 all right so here is our fully working grid in 3d space now in each of these positions we have an instance of this grid object so on each of these grid objects we can store any data we want for example saying that there is a building on this grid position so to begin let's just instantiate a building on our grid building system let's make a simple priority update on update let's listen to the mouse button down so when the left mouse button is down let's spawn a building and for the building let's go up here and let's add a serialized field make it just a transform for the test transform so now to drag the reference over here in my project files already prepared a bunch of visuals so for example this building right here so here in the prefab we have the building then down here we have two objects so one of them is the area which as you can see the scale is at 10 10 which is the exact same scale that we set for the cell size of each width position and very important i've got the anchor which is the pivot so the pivot on the visual is right in there so it's not down the center it's on the lower left corner which is going to match perfectly with our grip i made a video on how to move the pivot if you're not familiar but essentially you just move the children and that way now the building the pivot is right in there oh and also the buildings that i'm using here are from a really nice asset pack there's a link in the description if you want to pick it up so just drag the reference of the building on there okay now here we've got our transform so we're going to go when we press left mouse button we're going to instantiate our test building and then we're going to need a position and rotation so for the position i have a class to get me the mouse position in 3d space so here it is get the mouse on position and what it does is a camera main screen point using the mouse position and so on so just getting the mouse position in a 3d world so i just use that put it in there and with no rotation so quaternion identity so now as we click we should be able to place down a building let's see okay so here i am and if i click if there you go it places down the building exactly on the mouse position all right so far so good however the building is meant to place on the grid position and not on a random position right in the middle so let's solve that what we really want is to snap this mouse worm position into a position on the grid now on the grid collapse itself i've got a function that takes a wrong position and outputs a grid position so going to the grid and get the xz so this will return the grid position of a certain order position so we just pass this so by calling this one now i have the x and z for the grid position so in order to snap it i just convert those into the original grid position so just do a grid in order to get the worm position pass in these x and this z and this is the one that we're going to use here okay so now it should be placed exactly on the correct width position so if i put it let's say right down the middle there and i click any of the go placed perfectly exactly on the accurate position so wherever i put it it always snaps to the exact perfect position all right awesome so we have our grid and we can place buildings on it now the issue of course is that i can place multiple buildings on top of one another that is not intended so that means that on each grid object we need to store some data based on what building is already on there so over here on the grid object let's add another field let's name it a transform for the transform that is placed on top of this grid object then just some simple function to set it and get it so when to set transform and when to clear it which we're going to use later when we implement the mulch and let's also modify the two string to also include what transforms on there so just make a new line and then the transform and also the way that the debug visual is set up the grid class needs to know when something changes so just here just trigger a grid object change passing this one and this one just so that the red knows when something changes and then it updates the text okay so we have our transform and now let's make a simple function to test if we can build so just say public returns a boeing let's call it can build and here's very simple we just return if the transform is known so if transform is null then we can build if it is not known then we cannot build okay so now here on our update we get the grid position all right now before we instantiate let's go into the grid and we get the grid object on this x and this then so this returns our grid object and now here we go into the great object and we ask if we can build and if so then we are going to build it and after we instantiate let's do a bone transform to grab our reference then we go into the grid object and we set the transform to this one so we build and we set it and if not then that means that we cannot build so for this let's spawn a fun pop-up message so here i'm using this function from the utilities which you can grab for free from intake on monkey.com or of course you can make your own warning message like for example the tooltip that i covered previously just a bit of fun just telling the player why it cannot build okay so with this let's test okay so here i am and if i click yep there you go it gets placed and over there on the text we can indeed see that it worked so we can see that this grid position is now filled with a building clone all right and now if i click again nope there you go now i see the nice pop-up saying that i cannot film because that one is already occupied alright awesome so with this we already have a really good start we can place down buildings anywhere you want and we make sure that we only place them on clear ground so this is working but so far it's only working with this shape however in most games when you have a building system you don't usually have just a single one by one shape some things you want to place will occupy more than one grid position so let's handle that and by the way if you find the video helpful consider subscribing and hitting the like button it really helps out the channel for that one the first thing that you need is some way of defining each building type and for that over here i've got some building type scriptable objects i covered scriptable objects in detail in another video so go watch that if you're not familiar with them essentially they are a great way to make some objects to store some data and also this is the same method that i use to define the building types in my complete builder defender course so this is an excellent way to define various types here is the script with the definition as you can see it's pretty simple just a handful of fields over here we have a name a transform for the final prefab a transform for just the visual then an integer for the width another one for the height now here we're working on the xz plane so really the height is really more of the length but personally i prefer the word height then the other thing we have is also some functions running the direction we're going to see those in a bit so essentially we have the scriptable object where we can so our name and our prefab and we width and the height and here in the editor we've got all various scriptable objects again go watch that other video if you're not familiar with this so we have multiple building types and some of them like for example the town hall occupies more than one grid position here it uses a width and height of two by two so let's test our current code using this first let's refactor our code to work with the scriptable object instead of the transform directly so here for the serialized field let's make it of type placed object type scriptable object and then down here for transform just go inside that and instantiate the prefab okay so that's it that's all it takes now in the editor let's select the object and over here let's drag the town hall reference okay let's test okay so here i am if i place it in here and yep so far so good it worked perfectly and if i try to place on this position again nope i cannot build however right away i'm looking at the text you can see the issue that we have so you can see that only this value on this grid position was set so this one on 32 is not being occupied by anything same thing for that one and that one so if i go ahead and click on this one yep there you go now they are overlapping so this is the issue that we want to solve for that essentially we're going to need to fill in multiple positions on the grid with the same reference over here we instantiate our prefab on right then we go into the grid object and set the transform however we also need to know what other grid positions will this building occupy so when working with the building with the size of 2 2 if i click here on t1 i want to occupy 2 1 3 1 2 2 and 3 2. so what that really means is we start on the origin position then we're going to fill everything along side the width and also everything alongside the height and here on the scriptable object i've got a function to do exactly that again for now we ignore the references to the direction all that matters is really just this so this function takes an offset so that's the base offset like for example the position where we're clicking on and then simply cycles through the whole width cycles through the whole height and then adds it to the list so just the offset plus the x and the y it just does that and then returns a list of vector2 ends now we can go back into the grid building system and here let's call in that function so we're going to go into the placed object typescript object and call on that function to get the grid position list and now we need to pass in the offset which is going to be our xz and this requires a vector to in so let's press in the x and these n and then for direction again right now we're not worrying about it so let's use the default direction which is down we're going to see the direction in a bit all right so this returns a list of vector 2 ends so this is the grid position list so all the grid positions that this building will occupy and now we need to do is over here when we set the transform instead of setting just on one grid object let's cycle through all of those so for each vector to int for each grid position in the grid position list for each of those we're going to go into the grid and get the grid object on this grid position and then we do our set transform all right so now we are setting the transform on all of those so let's see okay so here i am as i click yep there you go now it does fill in all the positions so if i click here nope can't nope nope and no all right great so placing is working i cannot place any more on any of these positions however we still have one issue if i click over here on the left side and i click and if there you go now we've got another overlap so the issue is that right now we're only testing if we can build on the clickable position the answer is we need to apply the exact same logic that we just saw we need to test if we can build not just on where we click but on all the positions that will be occupied by the new building so here we've got the word position list we just need to call kin build on every single one of those all right so here's the logic so we just go through every grid position on the grid position list all right and then we test for every single one of them if we can build and if there is just a single one that we cannot build then we said this can build boolean into phones we break out of the cycle and then down here either it passed all of them or it didn't pass at least one of them so if we test so over here let's build in here all right and if i click here here here or here nope all right everything works and now if i click here and nope cannot do it if i click here can't do it can't do it and can't do it but if i go here and click yep now it does work all right awesome so we now have the ability to place on buildings with different sizes now next let's handle rotation for that let's test with the gas station which has a width of two and height of one so here if i place the gas station yep there you go it's like that so let's rotate this one as we saw previously on the scribble object i already defined an enum for the direction so just down left up and right now on the grid building system let's store the current direction so let's go into the placed object type and store a direction so the current direction that we want to build and let's default it to down all right now for the rotation we need to swap this so let's go into our update and in here let's listen let's say input get key down when we press the r key then we want to rotate the direction so over here on the scriptable object i've got this function to swap out and get the next direction so down goes into left left into up and so on so here we just get the direction go into that function and pass in the current direction okay so now we've got a direction that should always be swapping let's do a quick test so just print out the direction let's see by default it should be on down so if i press the r it should say left yep left and then go into up then to right then down and so on all right everything works now as we saw on these functions on the building object type it already takes a direction so here instead of putting in the default one let's choose the one that we're currently using let's see how this function is set up so here on the grid position list as you can see the only difference is swapping the width and height for height and width so on the down and up we've got some horizontal values and on the left and right we've got some vertical values then we've got the grid position list so all very simple just like this we should already be filling in the correct grid positions let's see all right so by default we have direction down so if i click here it should occupy this one and this one click and yep it did work all right now i press the r now i'm looking left so now if i click here it should occupy this one and this one let's see click and yep it did work now i press again now it's pointing up so it should place here and here so click yep it did work now again for the right it should place if i click here here and here so click any up it did work all right so all the grid positions are all correctly being rotated now let's actually rotate the visual over here on the scriptable object there's another function to get the rotation angle of a certain direction so if it sounds rotated by zero if it's in the left by 90 all right so let's use this and apply it to the instantiated prefab over here on the grid building system when we instantiate it we're calling instantiate passing in this prefab then we pass in this grid one position and then the quaternion identity this is what we need to change let's go into quaternion and we're going to create a new euler angle and we're working in 3d so we just want rotation on the y axis so 0 on the x then for the y let's go into the placed object type and we get the rotation angle pass in the current direction okay and then on the z also zero rotation okay so like this it should be rotated so first the default rotation click yep it works now press the r and i'm going to click here and yep it did work it did rotate 90 degrees so it worked but you can obviously see the issue we're rotating around the transform pivot which is right here on the corner so as we rotate 90 degrees yep it occupies down here so after we rotate we essentially need to offset it by a certain amount so in this case we need to offset by the width over here on the z axis so here is another function of the script on logic to do just that it calculates a rotation offset of a certain direction so if it's pointing down there's no offset so it gets placed exactly where it is if we rotate to the left then it gets offset on the y which is running the z by as much as the width and same thing for the up and right so here we just need to apply that over here to the spawn position okay so here it is we go into the scripting object we get the rotation offset for the current direction and we get a vector2 end then we complete the final placed object one position so we get the grid worm position so the same one that we've been using previously and then we simply apply our offset to it so the offset is on the x and on these end and we just multiply the offset by the grid cell size so now everything should be correct so here first of all place down click here yep it worked now press the r so now on the left now if i place here yep correct place again up let's place here yep now it's pointing up place again now for the right click here and yep it is pointing to the right all right so we've got all four of our rotations now let's just add some logic to swap the building type so for that we can just go up here instead of exposing the placed object type let's expose the array of it so here i'm exposing a list and then let's hide the private one so it's the selected one and all of them then on awaken let's see found it to the first one on the list and then here just listening to some outfit inputs and swapping this one out for a different one on the list now finally just need to populate that array okay let's test now by default we should be placing the building yep there it is now i press the two key now place a fire station press the three you know gas station and now that one non-five that one and on six yep i've got to mention and i can rotate it and there you go it rotates everything and everything is indeed working correctly all right so we can already build anywhere now let's handle our demolish so this is pretty simple but requires a bit more data for example let's say i want to demolish this mansion and i put my mouse here and i do a demolish so this grid position contains a reference to the transform so i can easily destroy it but then let's think of what happens with the grid position i can clear this one since i clicked directly on it but right now i don't know what other position should also be cleared so that means that we need to keep track of what positions each building is occupying so in order to handle that let's refactor our code to work with a proper class instead of just a simple transform so let's make a new script for this one let's name it the placed object and we're going to attach this script onto all of our objects so for example this is why i separated the prefabs from the visuals so the visuals just have the visuals whereas the prefabs have the visuals and any other knowledge that you want to add so this is always a great way to do things now in this script let's store all the data that we need for this building object so essentially the data that we're going to need is going to be the object type then we're also going to need the origin and the direction okay so these are all the fields that we need now we're going to need to receive this data so on the grid building system over here we are instantiating our build transform and instead of doing it in here let's make a function on the placed object that is responsible to create it and we pass in all the data that we need all right so here i made this nice static function it returns a placed object and if we've got to create we receive an input for the world position then for the origin another one for direction and then for the building type that we want to place then we simply do instantiate the exact same thing that we saw previously and then we simply do a get component in order to get the placed object component and we fill in all the data and finally we return it so now this class is responsible for creating the transform and holds all the data related to the building so now on the grid building system instead of manually instantiating it let's go into the placed object and we're going to call create and pass in all the data okay so we have this and this function returns a placed object for the created placed object and over here on the grid position instead of the transform let's refactor a code to receive a placed object instead so here let's replace the transform with a placed object and logic for this one is always pretty much the same thing okay so i really just swapped out the transform for a placed object and then down here the sept placed object and passed pressing the placed object okay so now we can finally handle our demolish let's do that on the right mouse click so you get the grid object on the mouse run position then we need to see if there's something on it so let's make a function to return the placed object okay now we can call this to see where we clicked on and if this one is not known then we do have something that we can mulch so first for demolishing the visual let's go into the placed object and let's make a function let's call it destroy self to destroy this object and here we just do a game object and we're going to destroy it so destroy this game object okay so this will destroy the visual so now here go into the placed object and we call this ryself all right that won't destroy it now we need to cycle through all the grid positions for this object and clear all of them so for that we're going to use pretty much the same watch that we use here so first we grab the grid position list and then we grab go into that one and we're going to clear it so we get the grid object and we're going to clear the grid object the placed object all right except here for the grid position list instead of grabbing it from the current xn we're going to grab it from the xz on this placed object so let's actually make a function there to expose this all right so that's really it since this placed object already has all the data it needs it knows what object it is where the origin is in the direction so using all of this data we can get all the grid positions that this object is occupying so now back on the grid building system for the grid position list we just go into this place object and we ask it to give us the grid position all right so that's it now everything should be working let's see so here i am and i place down normal building and yep there you go now i right click and yep there it is it did clear it and it did clear the object so if i click again yep now i can build again all right so that is working now let's see for one with an irregular shape so let's see place down the gas station occupying both of these now if i right click on this one yep it did clear both of them now if i click here place on both these now right click on this one and yep it did clear both them and same thing for dimension there it is occupying all four and i'm going to do it on this one and yep deletes all them all right so our demolish and build is all working perfectly awesome and finally just to complete the building system over here i have a visual codes so it snaps to the positions as i move the mouse and when i click if there you go it places down the building and i can see all of them i can rotate it and looks great so here's how it works just an empty game object with this script and this script is listening to when the selected type changes just so it can destroy and create new visual so it refreshes the visual and on late update it simply grabs the mouse where on position snap to the grid position and same thing for the rotation and just applies the lerp just to make it slightly animated so using that everything gets snapped into the grid position and for the visual you can also see that it's slightly different for that essentially i put the ghost on a different layer and then over here on the renderer i add the next render objects for the ghost this one is only rendering things on the ghost and it's using a different material so over here is how it looks without all of the debug so i've got a building as you can see it snaps onto the underlying grid and if i move and i can rotate and everything looks pretty great so i can place on any building of any type and if i place on top then of course doesn't work but like this yep all of them occupying the correct grid positions all right so here is our system working fully in 3d and then over here i've got a working 2d version now the logic for this one is pretty much exactly the same except for the grid class that i'm using is the 2d grid class then the mouse position is also calculated differently and the prefabs they have sprites instead of having 3d meshes so there you go i can place any of them anywhere and yep all the logics don't work so if i place this in there nope cannot build place in there and it does work so as you can see it's very simple to make this same system work exactly in 2d you can download the project files and see how the class works for yourself alright so there you have it a really awesome grid building system like i said this can be used for anything it doesn't have to be buildings you can use it to place down enemy positions in an rpg maybe some resource nodes in a survival game maybe some plots in a farming game or maybe some traps in an action game this is an extremely versatile very useful system as i said definitely look forward to me making some more complex tutorials using this one as a base for example i'm currently building a factory automation game that uses this system as the underlying base if you're watching this in the future check the playlist link in the description where i will be adding systems built on top of this one as always you can download the project files and utilities from unitycodemunk.com if you found this video helpful consider liking and subscribing this video is made possible thanks to these awesome supporters go to patreon.com unitycodemonkey to get some perks and help keep the videos free for everyone post any question comes and i'll see you next time [Music] you
Info
Channel: Code Monkey
Views: 116,275
Rating: undefined out of 5
Keywords: building system unity, grid building system, grid map unity, unity factory game, unity city builder, unity rts, unity factorio, building system, grid system, code monkey, factorio, dyson sphere program, unity tutorial, unity game tutorial, unity tutorial for beginners, unity 2d tutorial, unity 3d, unity, game design, game development, game dev, game development unity, unity 2d, unity 3d tutorial, programming, coding, c#, code, learn to code, learn programming, unity tutorials
Id: dulosHPl82A
Channel Id: undefined
Length: 29min 17sec (1757 seconds)
Published: Sat Feb 06 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.