Join Dan Violet Sagmiller for an in-depth discussion in this video Generating city data, part of Unity 5 2D: Generate Procedural Terrain.
- We have this infinite and rich terrain, but nothing's happening on it. Let's add some buildings, or at least get some of the ground work in place. We'll load some graphics and get a couple functions ready to go that will be used by the cities. To start out with, let's add some graphics for this. We know we're going to need some city textures. So, I'm going to minimize this terrain, right click on textures and create a new folder called City. This will manage our city graphics. Also, you'll find in the resource files for File Seven that we have a variety of house images to select from.
So I'm going to grab these and drag them into City. We'll go through our usual due dilgence with the pictures, check out their sizes. They're 300 by 300 each. I'll select all of them, and set their pixels per unit to 300. Now the basic idea of where these graphics came from was simply taking some images that I had for aerial photos that I happened to have purchased the rights for and then just use the alpha shading to cut out parts of the images and make them invisible so they'll blend into the surrounding terrain.
But now that we have this and it's applied we can start making a few changes. We're going to need our terrain manager to actually hold onto the cities for us. So, I'm going to go into our terrain manager code and in this terrain manager, I'm going to create a new variable type, Public Sprite, so another array of sprites, and we're going to call it Buildings. Because this is going to need to know about where those buildings are. It's not actually going to be drawing anything yet, but for now, we'll just hold onto them.
So I'm going to save this and return to buildings, I'll return to Unity. I'm going to select the terrain, and inside it we see this buildings list at the bottom. I'm going to minimize terrain type so that we have more space to work with that. Now that we have this, I'm going to lock the inspector and grab all six of these house images and drag it onto buildings. Now we've got six images there. Alright. Now of course, nothing actually happens in our code because nothing draws yet. So I'm going to go back into our code and make another preparation for this.
I'm going to go into the marker code. So the marker code has certain pieces of information that we'd like to know. Like, if it's a city or not. I remember before I said this marker can hold lots of different types of information. So, let's call out if it's a city. Public Pool is City Get Set. Now that we have that, we're going to need to know some more information. Like, for instance, is the terrain type water. But right now, all we know is that we're just passing in, it's one of the four random terrain types, or however many terrains we have.
That doesn't work for us. We're going to need to know the actual terrains themselves so we can figure this out. So, to do that, I'm going to switch from Int Terrain Type Length and just pass in the terrain types. And I'll just call it terrains for short. So now we have this marker we're generating. It wants to know whether or not it's a city. And in order to figure that out, we need to know about the terrain type. So before we can even generate this marker I'm going to copy out this random helper we have.
Oh, forgot the R. And let's get the terrain. So I'm going to say our terrain equals and that's going to come from terrains. And then I'm going to paste back in that random range, which I forgot the R, so I'll type that first. Paste in the code, and this is going to select a random terrain. However, this is not their correct item anymore because we no longer have this variable. Instead, it is terrains dot length. So now, we have a terrain, and we could just see about passing back the terrain index, but we've got the entire terrain now, so why waste it.
We could just hold onto it. It's very minute for how much memory it's going to take. It's just a reference. So instead of being an integer, it's going to be the actual terrain type. And instead of calling it terrain type, I'm just going to call it terrain. So now we have this variable called terrain, and it's going to equal the terrain we just loaded. Now we need to decide, is this a city or not? So, in between the markers and the terrain, I'm going to say boole is city equals not terrain dot not walkable.
Now, what does that mean? It's a little confusing to read. Well, not and not, it's a double negative. So it will be a city if the terrain is walkable. We basically just get rid of both the nots. So if it's walkable, but that's not the only thing that will define it. Otherwise, everything but water will have a city on it. So that doesn't work too well. We need a little bit more. I want a chance, a percentage of a chance for these cities to show up. So to get that chance I'm going to need to pass in a variable for that. We'll call it float city chance.
And I'll use that city chance here. So if city chance, let's say we have a 30% chance at this, if city chance is greater than a random percent, then that means that we've hit that 30% marker we wanted. So random helper dot percent. And it's looking for int x and int y, or float x and float y. Very useful. So I'm going to pass in the same variables I did here for the location.
Of course, it also needs the key, I should have copied that as well. So now, as long as the terrain is walkable, it will have a 30% chance of becoming a city. So, let's mark it as such. It is city equals is city comma. So now, every marker contains all this information. We're going to need to fix a couple things on how we're using get markers. So, let's go find some of the code that has the errors. I'll hit F8 to build it, and we've got five errors.
Scrolling into this, best overload for random helper percent doesn't have int int int, or is expecting int int int. And we've got two floats, so that's the first three errors. I'm going into random helper and I can see that percent does not have floats. So I'm going to create an overload, Public static float percent float x float y, and again, int key. And I'll just say return percent and I'm going to pass in the int form of x and the int form of y, and key.
So that's going to take care of the first three bugs. Now, let's go hit F8 and see what other errors we got. Alright, it all looks like they're both on this screen. So the first one is we're getting the marker and then we're returning the terrain type. Well, the marker now has the terrain type. So I'm actually going to get rid of a lot of this code and I'm just going to say return marker dot closest and get the particular markers, dot terrain. So this other return method below it, not even needed.
Alright, next we have the get markers method which right now we change that method so it's expecting different information. So after the key it's expecting the actual terrains not just the count of them. I'll pass in terrain types. And I'm also going to pass in city chance. Now, city chance doesn't exist so I'm going to scroll up to my fields and add it. Public float city chance equals 0.30F. A 30% chance of having a city.
I'm going to scroll back down to my errored code and where I was looking for that city chance from the markers, I'm going to pass in city chance. I'll hit F8 again, see if there are any errors. It's a successful build. Our game should still run but there won't be any cities yet. And that's okay, we'll get to that. The key thing is, we've just made an architectural shift and there's no errors. Great.
- Tiling images
- Controlling randomization and noise
- Moving and redrawing maps
- Creating smooth tile connections
- Animating terrain
- Adding a building layer
- Adding cities
- Making buildings
- Generating multi-part structures
- Saving and loading terrain