Skip to main content

How To Instantiate Along A Grid (Simple City Generator Method)

A city created at run time that was instantiated along a grid.


The reason for this post is to allow people to understand how to make something like this easy and simple. The city is instantiated along a grid using the restraints of the length and width perimeters passed through into the generator. Tiles are incremented through a constant that defines the width/length of the tile square. Here's the code I've constructed for the current explanation :

var tileConstant:float = so and so number;

var tileObject:GameObject;

function BuildCity(desiredLength:int, desiredWidth:int){
var length:int=1;
var width:int=1;



while(length<=desiredLength){

                Instantiate(tileObject, Vector3(width*tileConstant,0,length*tileConstant), Quaternion.identity);


//incrementing
if(length ==(desiredLength)&&width <desiredWidth){
length = 1;
width +=1;
}else{
length+=1;
}
}

}

This code it attached to an empty gameobject inside the scene!

So with this code now, we have a tile constant. Keep in mind the constant is assuming both the width and length of the tile are the same. When the function BuildCity is called and the parameters are passed in, the generator starts a while statement that starts with a length and width of 1. The while statement instantiates a tileObject and puts it in the right location. Location is determined by taking the current length and multiplying by the constant. Therefor if the tile constant was a 5, tile length of 1 would be created at 5, tile length of 2 created at 10, so on and so on. Incrementing from getting to one tile to the next deals with just two logical statements, the while statement and the if-else statement.  In the method shown here, the while statement runs through all the tiles on the length side before jumping to the next incrementation of width, and begins creating all the tiles of length again. Since the method is set up so that it instantiates along the length first, the while statement runs until length is greater than desiredLength. Here is an example of what we have in the code.

The width = 4 and height = 3. The constant is 10. The tile object is Unity's plane mesh with a point light parented to it. The function is being called by the start function.


Now with the code we have now, we can have a grid of any dimensions we want created. Now what if we want to build a border around the city? The next step we're going to take is edge detection so we know when we're at the end of the grid. With this comes two method options for creating edges. My city generator created buildings on top of the tile as a border, but for the sake of simplicity of the blog post, we're just going to use a separate tile that has borders attached to it. Not only are we going to need border tiles, but corner tiles also. Here is the new code compensating for our needs:


var createCityOnStart:boolean = true;

var width:int;
var height:int;

enum KindOfTile{plain, leftBorder, rightBorder, topBorder, bottomBorder, topLeftCorner, topRightCorner, bottomLeftCorner, bottomRightCorner}

var tileConstant:float = 10;

var tileObject:GameObject;
var tileCornerObject:GameObject;
var tileEdgeObject:GameObject;

function Start () {
if(createCityOnStart == true){
BuildCity(height,width);
}
}

function BuildCity(desiredLength:int, desiredWidth:int){

var length:int=1;
var width:int=1;
var location;
  while(length<=desiredLength){
     
        location = Vector3(width*tileConstant,0,length*tileConstant);
     
//getting edges
if(length == 1 &&width == 1){//bottomleftCorner
BuildBase(KindOfTile.bottomLeftCorner,location);

}else if(length == 1 && width == desiredWidth){//bottomrightCorner
BuildBase(KindOfTile.bottomRightCorner,location);

}else if(length == (desiredLength) && width == desiredWidth){//top right corner
BuildBase(KindOfTile.topRightCorner,location);

}else if(length == (desiredLength) && width == 1){//top left corner
BuildBase(KindOfTile.topLeftCorner,location);

} else if(length == 1&&width !=0){//bottom edge
BuildBase(KindOfTile.bottomBorder,location);

} else if(length == desiredLength) {//top edge
BuildBase(KindOfTile.topBorder,location);

}else if(width ==1&& length !=0){//left edge
BuildBase(KindOfTile.leftBorder,location);

}else if(width ==desiredWidth){//right edge
BuildBase(KindOfTile.rightBorder,location);

} else {//not an edge

BuildBase(KindOfTile.plain, location);

}

  //incrementing
if(length ==(desiredLength)&&width <desiredWidth){
length = 1;
width +=1;
}else{
length+=1;
}

}

}

function BuildBase(tile:KindOfTile, location:Vector3){
var tileInst:GameObject;
switch(tile){
case KindOfTile.plain:
tileInst = Instantiate(tileObject, location, Quaternion.identity);
break;

case KindOfTile.bottomBorder:
tileInst = Instantiate(tileEdgeObject, location, Quaternion.identity);
tileInst.transform.eulerAngles.y = 0;
break;

case KindOfTile.topBorder:
tileInst = Instantiate(tileEdgeObject, location, Quaternion.identity);
tileInst.transform.eulerAngles.y = 180;
break;

case KindOfTile.leftBorder:
tileInst = Instantiate(tileEdgeObject, location, Quaternion.identity);
tileInst.transform.eulerAngles.y = 90;
break;

case KindOfTile.rightBorder:
tileInst = Instantiate(tileEdgeObject, location, Quaternion.identity);
tileInst.transform.eulerAngles.y = 270;
break;

case KindOfTile.topLeftCorner:
tileInst = Instantiate(tileCornerObject, location, Quaternion.identity);
tileInst.transform.eulerAngles.y = 90;
break;
case KindOfTile.topRightCorner:
tileInst = Instantiate(tileCornerObject, location, Quaternion.identity);
tileInst.transform.eulerAngles.y = 180;
break;
case KindOfTile.bottomLeftCorner:
tileInst = Instantiate(tileCornerObject, location, Quaternion.identity);
tileInst.transform.eulerAngles.y = 0;
break;
case KindOfTile.bottomRightCorner:
tileInst = Instantiate(tileCornerObject, location, Quaternion.identity);
tileInst.transform.eulerAngles.y = 270;
break;
}
}


Now with this code we have made an addition to our while statement. This addition tells us whether or not the tile we are creating is an edge or a corner, then calls the BuildTile function to make the correct tile. Two new tile objects are needed. The edge tile is the original tile with an wall parented that is located towards the back of the tile. The corner tile is the original tile with two walls parented, one which is located towards the back of the tile, and the other to the most left of the tile. An enum called KinfOfTile has been created to keep up with all the different types a tile could be. When the while statement calls BuildTile function and passes in the KinfOfTile such as KinfOfTile.topLeftCorner, the BuildTile function rotates the object to the appropriate angle. Here's an example of what we have now.

Again, the width = 4 and height = 3. The constant is 10. The tile object is Unity's plane mesh with a point light parented to it. The function is being called by the start function.


But this still ins't a city, we need buildings. In this last part we're going to have two cases of buildings for each tile. One case will spawn multiple small buildings, where the other case will spawn one big building. Each case will go through all our different available buildings and pick one to be created. Here is the updated code:


#pragma strict

var createCityOnStart:boolean = true;

var width:int;
var height:int;

//List of buildings for the building creator to choose from
var smallBuildings:GameObject[];
var bigBuildings:GameObject[];
enum KindOfBuilding{small, big}

//the Tiles Constant
var tileConstant:float = 10;

//Tile objects to instantiate along grid
var tileObject:GameObject;
var tileCornerObject:GameObject;
var tileEdgeObject:GameObject;
enum KindOfTile{plain, leftBorder, rightBorder, topBorder, bottomBorder, topLeftCorner, topRightCorner, bottomLeftCorner, bottomRightCorner}


function Start () {
if(createCityOnStart == true){
BuildCity(height,width);
}
}

function BuildCity(desiredLength:int, desiredWidth:int){

var length:int=1;
var width:int=1;
var location;
  while(length<=desiredLength){
     
        location = Vector3(width*tileConstant,0,length*tileConstant);
     
//getting edges
if(length == 1 &&width == 1){//bottomleftCorner
BuildTile(KindOfTile.bottomLeftCorner,location);

}else if(length == 1 && width == desiredWidth){//bottomrightCorner
BuildTile(KindOfTile.bottomRightCorner,location);

}else if(length == (desiredLength) && width == desiredWidth){//top right corner
BuildTile(KindOfTile.topRightCorner,location);

}else if(length == (desiredLength) && width == 1){//top left corner
BuildTile(KindOfTile.topLeftCorner,location);

} else if(length == 1&&width !=0){//bottom edge
BuildTile(KindOfTile.bottomBorder,location);

} else if(length == desiredLength) {//top edge
BuildTile(KindOfTile.topBorder,location);

}else if(width ==1&& length !=0){//left edge
BuildTile(KindOfTile.leftBorder,location);

}else if(width ==desiredWidth){//right edge
BuildTile(KindOfTile.rightBorder,location);

} else {//not an edge

BuildTile(KindOfTile.plain, location);

}

  //incrementing
if(length ==(desiredLength)&&width <desiredWidth){
length = 1;
width +=1;
}else{
length+=1;
}

}

}

function BuildTile(tile:KindOfTile, location:Vector3){

BuildBase(tile,location);

if(Random.Range(1,5) <3){//Build 4 of Small Buildings
BuildBuilding(KindOfBuilding.small, Vector3(location.x+1,location.y + 1, location.z +1));
BuildBuilding(KindOfBuilding.small, Vector3(location.x+1,location.y + 1, location.z -1));
BuildBuilding(KindOfBuilding.small, Vector3(location.x-1,location.y + 1, location.z +1));
BuildBuilding(KindOfBuilding.small, Vector3(location.x-1,location.y + 1, location.z -1));
} else {// build a big building

BuildBuilding(KindOfBuilding.big, Vector3(location.x,location.y + 2, location.z ));
}

}

function BuildBuilding(type:KindOfBuilding, location:Vector3){
var buildingInst:GameObject;

switch(type){
case KindOfBuilding.small:
buildingInst = smallBuildings[Random.Range(0,smallBuildings.length)];
Instantiate(buildingInst, location, Quaternion.identity);
break;
case KindOfBuilding.big:
buildingInst = bigBuildings[Random.Range(0,bigBuildings.length)];
Instantiate(buildingInst, location, Quaternion.identity);
break;
}

}

function BuildBase(tile:KindOfTile, location:Vector3){
var tileInst:GameObject;
switch(tile){
case KindOfTile.plain:
tileInst = Instantiate(tileObject, location, Quaternion.identity);
break;

case KindOfTile.bottomBorder:
tileInst = Instantiate(tileEdgeObject, location, Quaternion.identity);
tileInst.transform.eulerAngles.y = 0;
break;

case KindOfTile.topBorder:
tileInst = Instantiate(tileEdgeObject, location, Quaternion.identity);
tileInst.transform.eulerAngles.y = 180;
break;

case KindOfTile.leftBorder:
tileInst = Instantiate(tileEdgeObject, location, Quaternion.identity);
tileInst.transform.eulerAngles.y = 90;
break;

case KindOfTile.rightBorder:
tileInst = Instantiate(tileEdgeObject, location, Quaternion.identity);
tileInst.transform.eulerAngles.y = 270;
break;

case KindOfTile.topLeftCorner:
tileInst = Instantiate(tileCornerObject, location, Quaternion.identity);
tileInst.transform.eulerAngles.y = 90;
break;
case KindOfTile.topRightCorner:
tileInst = Instantiate(tileCornerObject, location, Quaternion.identity);
tileInst.transform.eulerAngles.y = 180;
break;
case KindOfTile.bottomLeftCorner:
tileInst = Instantiate(tileCornerObject, location, Quaternion.identity);
tileInst.transform.eulerAngles.y = 0;
break;
case KindOfTile.bottomRightCorner:
tileInst = Instantiate(tileCornerObject, location, Quaternion.identity);
tileInst.transform.eulerAngles.y = 270;
break;
}
}


With this code, the function the BuildCity function is calling has changed to now BuildTile. BuildTile calls both BuildBase (Building the tile base or "floor") and the new function BuildBuilding, which builds a building. An enumeration for building size has been created to indicate what kind of building to create. Along with the enum two new GameObject Arrays has been created so the user can drag and drop their building models in the inspector with ease.BuildTile passes in location(which is different for each small building) and type(whether it's small or big) to BuildBuilding. BuildBuilding goes through the arrays and picks out a random one to create when called. Here's a look at the final out come.

One last time, the width = 4 and height = 3. The constant is 10. The tile object is Unity's plane mesh with a point light parented to it. The BuildCity function is being called by the start function. The small buildings (4 different kinds) are 2x2x2 and have parented cubes with the same dimensions at a different height to make the buildings look bigger or smaller to one another. The bigger buildings (2 of them) are 4x4x4 and again have parented cubes with the same dimensions to create variation between heights. 

Here is a final look at the script atatched to the empty gameobject.

If you want a real look at teaching yourself some procedural generation techniques, you should consider reading this guys thesis.

If you have any questions or feed back please leave a comment!

Comments

  1. I very much enjoyed your post, but I'm slightly disappointed that you didn't include the script for the rest of the city generation as seen in the first image. Do you plan to release a second post detailing creation of roads, parking spots, etc?

    ReplyDelete
    Replies
    1. The only reason I didn't include the whole script is because it'd made up of multiple different ones that are intertwined with other aspects of the game. In the future I plan to do a more in depth post on getting down into detail with each street block, in which a script I will provide.

      Delete
  2. Thanks, great post. Any chance we can take a look at the complete script?

    ReplyDelete
    Replies
    1. I recently saw this post and was interested in seeing "part two" of this post. Is that still planned or is it scrapped?

      Delete

Post a Comment

Popular posts from this blog

How To Make a Hellish Looking Sky Box

I came across this problem while constructing my scene of Hell in a little project I've been working on, and could not find a reasonable sky box on the web for what I want. Maybe I was not looking hard enough, but I ended up making nice substitute. If you think the sky box looks familiar, then your right. The Sky box I'm using is already packaged with Unity3D! To import the sky boxes Unity has made for you,  simply go to Assets>Import Package>Skyboxes.  The sky boxes will appear in your projects tab under a folder named "Standard Assets". To make this sky box, first you must find the folder containing all the sky box materials and open it up. In it will be a list of sky boxes for your disposal. To get this skybox, I decided to tweak the "StarryNight Skybox" (But the "MoonShine Skybox" looks pretty cool also!).  Select the sky box and view it under the inspector tab. Underneath the properties there will be a tint color variable allowin...

How To Make A Gun Shot Sound (SFX On Unity 3D)

When it comes to audio in Unity, there are four components: Audio Clip , Audio Source , Audio Listener , and Audio Re-verb Zone . Audio Clips are the actual audio file imported into your game. Unity supports file formats: .aif, .wav, .mp3, and .ogg. When imported, you can compress them greatly, with the price of loosing some quality. You can do this by first selecting the audio clip, view it in the inspector. Under the Audio Importer component, you can switch the audio format from Native to the audio clip, to a compressed format applied by Unity. You can change how compressed the file is by dragging the bar at the bottom, then hitting apply. You can get plenty of free good SFX from a site called  freesound.org . All you have to do is create an account for free , and download all the sounds you want. I found a nice gun shot sound here . Simply download and load into your Project. Audio Source actually plays the audio clip in your scen...

Handling Music and Sound Effects In Your Games

Initiative  While developing Treva's Adventure I had to figure out a way to handle multiple music tracks and sound effects in a clean manner or suffer horribly.  What was going to help me achieve a simple solution was taking all the different sounds and centralizing them in a single class in order to black box them.   Any other code trying to play a sound wouldn't even know the sound file's name.   All code trying to play a music track would reference a enum that defines all the track names. Defining The Class Creating The Enum When I first started defining types in my enumeration,  I was naming the types to be exactly like the file name.  For a scary sound effect I had found a file named "ghost breath".  So around my code would be scattered lines like SoundManager.Play(SoundEffectType.GhostBreath);  This was fine until I found a sound that better fit the situation it was being used in,  and decided to use "ghost breath" for a...