Infinite Procedural Terrain in Unity

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
so you're probably wondering how I made the infinitely generating terrain for my previous projects fears I'm just going to assume that's what you were wondering now this isn't a beginner tutorial I'm mostly going to be focusing on the concept of Turing generation rather than the actual syntax of how you do the thing this is pretty much what I had in spheres it's just I improved it slightly and I took out all the unnecessary clutter for your convenience now there's going to be a download link in the description so if you want to just kind of poke at it that's how I learned the best all right so the most basic form of this is when the player moves over the terrain it'll keep generating new tiles as the player moves that's what I'm gonna call these things by the way I think minecraft calls them chunks I like tiles better to be honest okay first I'm gonna show you how I set this up so I'm gonna create a new project I'm gonna title it thingy you know that's good enough all right so the first thing you want to do is actually go over to the asset store and download something called procedural toolkit it looks like this this helped me a lot in making it so you just want to import everything this is kind of what you use to generate the individual tiles rather than having them procedurally generate and continuously generate as you move across the terrain so this will be good if you just want like a little square of procedural terrain rather than a continuously generating one so we're gonna go into that and actually go into the examples and low-poly terrain and it looks like this and if you start it you get a thing and it looks like this so just click generate and you can see how it works adjust some of these values so if you want you can just look at these scripts and try to understand them I kind of don't but I think that's okay as long as you understand that these scripts are there to create a piece that looks like this alright so I'm going to create a new scene go into that and I'm just going to bring in two of the assets from the project so what you'll see is torreĆ³n controller simple and generate mesh simple now these are just the simplified versions there's a more complex version that adds like structure spawning and like water and basically all the features that I have in the game but we're just going to look at the simplified version for this part so what I'm going to do is create a new object and I'm just going to put generate mesh on it this is going to be the tile by the way and then I'm going to add mesh renderer and mesh Collider so make sure you leave the mesh and the mesh in the collider none because it's going to generate that automatically so now you want to create another object and I'm actually going to name these two controller and tile and then save tile as a prefab you can get rid of tile for now so in the controller we just want to drag this script in no not this one this one and then here where it says turning tile prefab we want to assign this and the last thing it needs is a player or game transforms and I'll explain those a bit later but okay I'm gonna create a player which is just going to be a cube and then in the controller I am going to assign that as the player so if we run that tada it works except it's pink oh yeah I didn't assign the material so we want to do is in tiles materials what we want to do is just select this and search for terrain which is included in the procedural toolkit and this is some sort of special material that can respond to this gradient and make it different colors based on how high up it is if you notice in my games it's kind of very subtle but the tops of the ground are always more of a light green rather than a dark green at the bottom otherwise it just looks very I don't know fake I mean I guess it's not very realistic to begin with but we're just going to set this to black and try again and as you can see it looks like it worked now to try actually moving wait no I have the wrong thing whoops this is the player so if you move the player it'll just procedurally generate the terrain around him I guess I'm assuming his gender sorry about that him or her and that's really all you have to do to get it up in where alright so now I'm going to go through each of these variables and actually explain what they do taurine tile prefab is pretty self-explanatory it's just a reference to the prefab and so it knows what to create terrain size is the XY and Z size of terrain if you increase the Y it'll get taller if you increase the X or Z I highly recommend keeping them the same so they're square but you don't have to but that determines how large each of the tiles are gradient is the color you already saw me play with that noise scale this has to do with Perlin noise I'm gonna explain that a bit later cell size is the size of each of these individual cells so as you can see there's all sorts of these little vertexes and if you increase the size of these you'll have less triangles to actually process and it'll be more of a low poly terrain rather than a smooth one radius to render this is pretty simple it's just five from the player not including the one the players on obviously the higher this is the more performance it'll SAP but the lower it is obviously you're not going to be able to see very far all right Game transforms I'm just gonna make a sphere here and set it as a game transform and what that's gonna do is when I start it now I can actually move this and it'll be separate from the players and it'll only render a radius of one and this is really useful if you have any physics based stuff because otherwise when you get too far away from it it will just fall through the ground because the ground would no longer be there and so for any physics enabled objects what you want to do is assign it to that list and they will no longer fall through the ground obviously though you will see this little chunk of terrain now in the middle of nowhere maybe you want to add some fog or something again like in Minecraft and so now I'm going to explain a little bit about how it works so this is the generate mesh simple script which is mostly from that asset and I can't really explain a lot about how this works it's just it does it's magic so what you need to know is when you call it a generate method it'll replace the mesh filter and mesh colliders mesh with the new mesh that's a lot of mesh but yeah the vast majority of this code is actually not even mine so I don't think I could explain it all right next up is the important one which is Turin controller simple here are the variables visible in editor which I already described so this is the start offset variable which basically says we're on the purlin noise it needs to start so what Perlin noise actually is is just like this image that's like a bunch of blurry sort of black-and-white and when you pick two coordinates on it'll give you the value from 0 to 1 in that place and what the tyrrhene is basically doing is just taking a little square of that and using as the height of that terrain at that specific place and this is the coordinates where it starts and the way you get it to generate infinitely is by just picking the square - you know any direction of it and using that as the height map so this is a dictionary of all the terrain tiles that starts as a vector - and when you input a vector - or coordinates where the tile is say you know one zero would be the tile directly to the right of zero zero it'll give you back at that tile and these two variables I'm gonna save for later because they're actually not completely necessary they're just there for performance reasons so in methods start it just calls initial load I'm pretty sure you could just make this start I think in my other game I was doing something else and start as well and so it needed to be separate but all it does in initial load is destroy the terrain just in case there already is some terrain and for whatever reason it didn't get cleaned up and then it chooses a random value between 0 and 256 on the X and the y to start in the purlin noise and Perlin noise actually loops after the number 256 and so if you get for example 1 it's going to be exactly the same as 257 so that's why it's this range so the first thing it does is get the tile of the player and this method right here basically you just give it a position in world space and it'll give you out the tile that you're currently on or that the position that you gave it is currently on and then it goes on to do the same thing with everything in the list game transforms which i covered earlier it's the little physics based objects that should have a radius of 1 just so they don't fall through the world and it actually includes the player tile as well and so in this if statement is saying if there are no tiles or the tiles changed from what they previously were or should change and the only reason I'm doing this is for performance reasons it's very expensive to just continuously enable the same object hundreds of times every frame because you're going to end up with hundreds of these tiles if you're moving around okay now remember those variables I talked about earlier the previous center tiles and previous tile objects while previous center tiles is pretty much the same as Center tiles it's just from the frame before and so you can actually tell if they changed or not and previous tile objects is all of the tile objects that should be enabled which is also this list just from the frame before and so what it does is it instantiates that list and just finds all of the tiles in a certain X or Y from the center tile which is done by these two four loops here which go from negative radius to radius which will give you a square around that one you are that's variable radius in radius and the variable radius is determined by radius to render if it is the player or one if it's not the player and the reason that I actually render nine tiles around physics objects instead of just the one it's on is because if it's on the edge of it it could just potentially just kind of fall through the ground if it's right on the edge of it for rendering a radius of one ensures that there is always some tile around it and it can't just fall through the ground and so then it calls this method activate or create tile which for now we're just going to assume that it does what it says it does and activates it if it's already created and if it's not created then it creates it and activates it so now that we're done activating the new tiles we actually have to deactivate the old ones that shouldn't be rendered anymore so what we do is we just go through the previous tile objects and if it's not also in the ones that we know should be rendered then it must be one of the ones that just went out of range and so we can just set those active to false and then of course we're done with this method and so we need to set previous tile objects and previous Center tiles here so below this are just the less important ones so I'm just going to go through them really quick this is activator create tile it takes a list of tile objects because it has to actually add to the list and so this is saying if this tile doesn't already exist in the dictionary then add it to the dictionary and call create tile which is another method that's right here but I'm gonna get to that after and if it already does exist just find it in the dictionary supply it with the right vector too and it'll give it back you just take it and you activate it and that's much less resource-intensive than just destroying it and creating it again this is the create tile method so what it basically does is instantiates this object that we gave it in the first variable which is the tile prefab that we just created at this position this is basically saying okay this is the terrain size plus the X index so if it's like the third tile to the right it's going to go three units to the right times the terrain size and so it'll go into the correct place and then this is just controlling the name it gets in the hierarchy so you can actually see which tile it is and then this just adds a to the dictionary so we can keep track of it with the x and y index as the vector two key and then the terrain object that we just created from the prefab as the value and then we want to get the generate a fit and then set the terrain size gradient noise scale cell size and noise offset which we've assigned in the editor these are just the variables that we were playing with earlier and then of course just returned the terrain and I'm noticing that this list tile objects and terrain tiles sound very similar terrain tiles is the dictionary that takes a key as a vector two which is an index and gives you an object terrain tiles is just the list of active game objects which is temporary and only exists in this update method unless it's passed into here so this is noise offset which we used here and what this does is it basically just picks a place on Perlin noise except it limits it to 0 to 256 rather than just any number and it does this by using the modulo operator here which if you don't know it just kind of loops it around so if you give it 257 it'll just give you back one because it loops it around 256 except it doesn't work with negative numbers and so what you have to do is add a special case like this where it just adds 256 because say negative 1 mod 256 is still negative 1 and so you have to add 1:56 to get the correct value without this it'll give you some weird mirroring effect at the zero zero point so this is tile from position I talked about a little bit earlier you just give it a position in world space it gives you back the tile that you're on as a form of a vector to which you can then use as a key to actually get the object and I honestly don't really understand how this works I wrote it almost a year ago and I cannot figure out why it's working honestly which makes me a little bit suspicious that there's a bug with it but it's decently simple maybe you can figure it out so this is 1/2 tiles changed which just basically compares an array and a list to see if they have the same length in the same value for every element this is destroy terrain which we called earlier not really very necessary but this is what you would do if you wanted to destroy the entire terrain basically clean up the entire level you have to just iterate through it and then destroy all of the values and then clear the dictionary and this is just a trim end method I made that just chops the end off of a string just to make it more readable in the hierarchy and then we're basically done with terrain controller simple I'm sorry if I went a little bit too fast I just want to kind of keep this short like I said before you'll probably learn more from just looking at it and trying to understand why it works than me explaining it to you so I'm done with the terrain generator part of the example I'm going to move on to how I actually got it to do everything it did in the game spheres so for that I am going to need to import some more scripts and what I'm going to do is actually just replace this with the regular terrain controller and then replace this with the regular generate mesh and then I have to reassign this and player and the cube weight not the cube sphere okay now you'll notice there's a lot more stuff going on here this is where I end it before and there's almost twice the amount of variables so first I'm going to show you water which is basically just a flat plane so I'm going to make one like that and I'm just going to make it a color create material and I'm just gonna change the color or a bit I have some red water so now we have seed and what that does is whenever there's a random element involved like picking a start place on the purlins noise or where all the trees are placed or something it's going to be consistent every single time you load up the game and so if you wanted to save the world then you would just have to save the seed as well as maybe the position of all of your objects that you have in there and it will generate the same world over and over again and you don't need to actually save the position of every tree so we're just going to leave that at zero actually there's really no need to change it so this is placeable objects and what this basically is is trees or whatever you want to just randomly generate along your world placeable object sizes that's a little bit of a difficult one it's basically the size of it and so it knows not to spawn two of them right next to each other but I'll show you that later objects per tile this will generate 0 to 20 objects per tile destroy distance what this will actually do is destroy tiles as they get really far away in this case a thousand units and you could just disable them and have woodwork it's just if they're disabled then they still take up memory and so when you have just a ton of them you could end up taking like gigabytes of memory just to store all of the tiles and that's something you kind of don't want and it won't actually affect tiles that are enabled and so if a physics object is still placed on top of it it'll be fine it'll just be all of the deactivated tiles around it that'll be destroyed so this is use Perlin noise and what this does is if you uncheck it it'll use this noise image down here instead of the purlins noise and I'll go over how to make a good Perlin noise image later but for now just know that you don't actually have to use unities default Perlin noise you can just use your own if you want maybe if you want to create some mountainous regions or something I didn't actually end up doing this in the game I implemented functionality because I thought I would but it never actually ended up doing it and on a somewhat related note the only actual difference between generate mesh and generate mesh simple is down here it has this if-else statement that says if it's using the Perlin noise do it as normal but if not then pick the pixel from this image that you just supplied and the reason that I actually needed to make a separate script was this terrain controller object didn't actually have the noise pixels variable and so I couldn't really have this in here and not have the terrain controller as well and I actually almost forgot there's also a placeable object script that you need to apply to the tile if you actually want to use the advanced version because it depends on this to actually place the objects as the title suggests and so what we're actually gonna do is create that object I'm gonna create a capsule and just make it very tall and it's too tall how about 10 yeah that looks fine and these are gonna be my trees and so just implacable objects I'm going to make this a size of 1 and this a size of 1 as well keep in mind these need to be the same size or else it won't work and I'm just gonna drag this in here and I'm just gonna enter one for all of these values I'll explain why I did that later but I'm going to start the game and that it is not right all right I see what I did though so what you actually have to do is first start it and then decide where the water is going to be so let's just say we want it here then we need to copy this and paste it in there and it'll actually just stay at the position that it is on the y-axis and so you can actually control the water level like that and I'm getting these errors which tells me that I do not have the proper tag so for the advanced version you need to tag this with the tag terrain apply that and let's try that again and as you can see we have a white terrain that is covered in blood and has a bunch of spikes everywhere so as you can see it's not really rendering the water for any additional objects that's because it doesn't really have any physics properties maybe you'd want to look into that if you maybe decided to have solid water as like an ice which I believe I did I think that's actually about in the game is just when you get too far away things will actually fall through the ice because it doesn't exist anymore so that's just something to note and I did not reset the gradient I'm going to change these colors to let's do let's do blue and let's see how about this color and that is quite the thing interesting alright now that's basically how it works one small thing to note is I didn't actually properly test this image noise in the game so it might not work I think it works to some degree but I don't know what that degree actually is so just disclaimer if you're actually going to use that alright so when you go here you see mostly the same variables these are just the variables that we assigned outside the only noteworthy one here is noise range which just takes into account that an image might not be 256 pixels and it'll just take the pixels length in the x and y directions and so it actually knows where it can loop because otherwise it would just take the first 256 pixels of the image and try to loop that now you'll see something different in the start you'll see this which just parents everything to a new level game object for organization and for a new feature that I'm going to show you later which is keeping the player towards the center and not just moving the player to as far away from the center as it wants but I'll show you that later but just know that it parents the water at the player and any game transform to this new level game object now this is how it determines how big the water should be it takes the radius to render multiplies it by two so that's two radii that's a diameter and then it actually adds the one in the middle as well and then you get the length of all of the tiles and then you just want to set the local scale to terrain size dot X divided by 10 now I think I divided it by 10 just because that's the size of the plane I think the plane is 10 units per one unit of plane and so I had to divide it by 10 times the waterside length which is this variable that I just calculated here and of course it does that for the X and Z not Y because it doesn't have a Y it's just flat and also this is how it sets the seed just random dot in its state and so when you choose these two random values to pick the starting place on the noise it'll pick the same one every time if it's the same seed and of course this is now the noise range variable instead of just a 256 because you might not be using that default Perlin noise now this update method is pretty similar the only thing I think I changed is this which just sets the water position whenever the player moves to the new tile that it should be on oh yes and I actually had to change everything to local position now instead of world position because since all the tiles are now a child of this level game object called level it needs the local position relative to the level rather than to the tiles and I'll explain why I did that whole parenting later but other than that I think it's pretty much exactly oh no this thing this is how it destroys tiles if they're too far away it's whenever you move tiles it only checks when you move titles just for performance reasons it just calculates the distance and if that's more than destroy distance and it's inactive then remove the key and destroy the object and I actually created a keys to remove lists so I just add the key to remove two keys to remove and destroy the value and then down here I actually just remove all the keys and this is because you can't actually remove keys while you're using a for each loop because that just messes everything up and I believe most of these helper methods are exactly the same I see this is different this is again using the random in its state to seed it and this is the logic that uses the place object script which is responsible for placing the objects I'll go over that later and to ensure that it places the objects in the same place every time it has to use a seed now the reason I used all of this instead of just putting seed there is because if I use the same seed for all of them what it would do is just generate the same pattern of trees on every single tile so what I did was I had to factor in the X index and the Y index and then I just multiplied the X index by 100 because otherwise it would sort of form this noticeable pattern where like every X and y it would be the same like if I just did X plus y then like two plus one would be the same as one plus two and so - 1 and 1/2 would have the same value and I'm not entirely sure what I was thinking when I casted this too long and then casted it back to an int but I'm pretty sure the reason I did that was so that it wouldn't ever exceed the value of integer Max value and the next thing I want to explain is this randomize in its state and obviously you can't set the initial state to random because it's the random State and just doing that you already set the random state here so that would be just setting it to another seeded random State that was a mouthful so what I'm doing is just taking this variable which is like your processor tics I understand to be but this value basically just changes so fast that you can't predict it and it might as well be random so just casting that to an int which will actually loop around it like you're using the modulo arithmetic that I explained I believe here on the previous script the way it will loop integer dot maximum if you go over maximum it'll go back to integer dot minimum originally I thought this would like clamp it and so if you went over integer maximum it would just kind of smush it back down to integer maximum and you would probably get the same number every single time but that's actually not the way it works which i think is kind of strange to be honest but it saved me having to do it myself I believe I just Google searched how to reset random dot in its state and I just got this I definitely didn't make this up I copied it from Stack Overflow somewhere and forgot about it so the only other new thing in this script is get grayscale pixels which is basically just getting the grayscale which is like changing the image to gray and then getting the pixel off of it from this noise image which you get as a texture 2d and what it does is basically just gets the pixels and then stores them into an array of arrays which is up here the variable annoys pixels and so whenever you want to use these pixels instead of the Perlin noise to determine height you can just pick from this two-dimensional array and it effectively works the same way it's just you have to sort of be careful that the image is high enough resolution where you're not picking from the same pixel twice or else you'll end up with pixelated ground so for every vertex there should be at least one pixel in the image I think you'll just have to experimentally determine that for yourself and so another feature I want to explain is this player zero script which is a pretty short script but I want to show you what it does first so I'm going to take the player which is the cube and just add this move forward script which is you know a very simple straightforward script and what that's gonna do is just move the player like this just constantly forward if you can see it down there so I know it's kind of behaving as you'd expect it just kind of moves forward and when new Tory needs to be generated it is generated and it just moves forward but if I add the player zero script to the player and set this reference now it's going to behave a little bit differently but it's going to do is sort of this sort of snap back motion where it stays at world origin where the terrain sort of moves around it and of course this game view looks very jerky right now but if I actually take the camera and parent it to the player it looks perfectly smooth and not jerky at all and the reason you'd want to do this is not just to keep the player zero but the lighting in unity will get less and less accurate and more flickery and jittery when you get further away from origin and wants to get thousands of units away you start getting this weird shadow artifacting I want to call it which is just a really huge pain and the way I found out to get rid of that is just to keep the player at origin and that's also why it's important to have this level game object which contains everything and so this can just move the entire level with just one assignment and whenever I click this it lags the game really badly just look at that it's so bad except I guess you could kind of argue that it's not completely infinite because you can get objects like this that can move away maybe they could get like out of range of floating-point numbers or something but I think it's really good enough unless you want to make like I don't know a truly endless game which I don't really know how to do I don't think you'd be able to really keep track of objects in the scene that are that far away besides maybe just saving them to a file or something but anyways let's have a look at this player zero script so there's really not much to it there's distance which is the distance away from origin it has to be in order to snap back to origin and it checks that here in update and then what it does is it just takes the level and subtracts transformed opposition in world space and if the player is parented to this object it'll basically cancel out the position of the child because position minus position is zero but in the case that the player is not parented to the level you'll need to do this which is assign it to zero but yeah this is pretty simple and now the last thing I want to talk about is this place object script which it looks kind of intimidating because there are gigantic block comments everywhere but this entire code down here I just got from this link and it's not necessary it's just sort of a visualization which I will show you after I go through it so this is basically just taking the minimum amount of objects per tile and the maximum and deciding what number to actually put there and remember that in terrain controller we called init state which is basically controlling the random seed and so it will spit out the same values every single time whenever you call random so long as you're between this init State and randomized init state and so for each number of objects we want to select a prefab type which as you can see here we just have the capsule right now so it's going to just pick 0 every single time index 0 which is capsule but maybe if you wanted to have 2 different types of trees you could have two of them and it would randomly pick between them so in this method it picks a random point above terrain which does some math which I'm not sure I could explain to be honest but what this does is it just picks a random point on the terrain from the minimum X to maximum X to minimum Z to maximum Z and so it gets the full square that is the terrain and then it just takes the terrain Y size and multiplies it by 2 and so it's a point above that terrain it doesn't really matter how far above the terrain it just needs to be above it you could make this 200 if you wanted to I think it also kind of depends on how tall your objects are maybe if they're really tall you want to make sure it's far above that because the entire point of it being above the terrain is so that it's not inside of anything okay so now we're gonna do a ray cast from start point which is random point above terrain we're gonna aim it down and that'll do that so you get the hit out of that and if the y is below water then just skip this entire thing and it'll go to the next iteration of the for loop and not place that object you can get rid of this if you actually want objects to spawn below the water level and lastly it compares hit da colliders tagged with terrain to see if it actually hit the terrain this is why we were getting errors earlier about it not being tagged properly and so after it determined that it theoretically can spawn there it'll pick an orientation again using random this is just a random on the y axis orientation and then it will do a box cast down which is where that second list comes into play which is where you specify the size of the object because before it was just a single ray going down which means objects could theoretically spawn close enough to each other where they would overlap but this will prevent those objects from overlapping entirely and then obviously if that box Collider hit the terrain then it's okay to instantiate this object at the position it hit which is start point on the X and Z and hit point on the Y because it already knows the x and z position it just needs to know how tall it should be and the orientation is selected earlier and also the transform which is a current tile because remember that the place object script is on the tile and so we want the new object say it's a tree or something to be a child of that tile and so what I'm going to do is actually uncomment all of this and we're gonna see what that looks like okay so that automatically pauses the editor and so you can take a look at this laggy mess and as you can see this is a visualization of the box that it uses to cast down words to see if it already hits something and there's also a blue ray in there it's kind of faded because it's actually inside of this thing which is the original position I checked and it's really low to the ground right now because it's actually twice as tall as the ground or is it I'm not entirely sure but I know that if we turn up the ground size to say 5 this should probably fix itself yeah and so now all of these squares are higher let's see if we can do better so if we do 20 then it'll be far above the ground and this is the original ray cast which determines the exact position and this is the box cast that determines how far around it things can spawn and say if you had a really big object you'd need to turn this size up by adjusting these three values and make sure that each index here corresponds to the other list here and so they actually match up so if I pick index one it's going to pick the capsule and this which is the size of the capsule if I added a second element say it was like I don't know a tree or a house then I would need to add the size of that house in the same index on this list and the terrain got really tall that's kind of strange-looking so that's really all I have to show you I think this only appeals to a select few amount of people I'm not sure if I'm gonna do this for any other games I make it's just some people expressed that they wanted to know how you did something like this and so here it is also I'm sorry if I just completely glossed over something I've kind of been looking at this for a very long time and so naturally I just see it where as I understand someone new to it probably doesn't really understand so if something needs clearing up please just ask so I think that's just about it let's end this by making it huge let's just mess up these values as much as possible okay we need this to be how about 10 and that's gonna make the game lag so we can't do that let's see what this does where is it oh um so this is what it looks like if you do that my computer is lagging this is quite an interesting biome and then there's also this wall of red above there cuz I didn't comment those lines of code again but yeah download link in the description hope you have fun hey guys future me here I realized that in the middle of the video I actually said that I would explain how to create your own Perlin noise image and then I never did so it's really actually pretty simple what you do is well first of all in you go to filters render clouds solid noise and you get this make sure that it's set to Thai label and X&Y size should probably be up pretty large also details should be down so you just click OK and then you get sort of this and this will actually work pretty well except what I would do in addition to this is maybe if it's too sharp you might want to blur it so you just add a little bit of this and maybe like 25 or so and that'll make it look a little bit blurrier you can also go up here and go to brightness and contrast and this will allow you to adjust how extreme the terrain is if the contrast is higher naturally the peaks are higher and the valleys are lower or maybe if you just want to really flat terrain you can turn it down and so it's all just sort of flat and you know obviously you could just like draw on it if you want a little bit of a landmark here and you know I think that's pretty much it
Info
Channel: Nova840
Views: 72,992
Rating: undefined out of 5
Keywords:
Id: f9uueg_AUZs
Channel Id: undefined
Length: 36min 57sec (2217 seconds)
Published: Thu Dec 28 2017
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.