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 Import and Use Fonts

-Made by Chrome Fx Films

To use different fonts in your game, your going to have to acquire some fonts. You can go somewhere like 1001 free fonts and download ones you like and want to use.

When you download the file, you'll need to extract the file if its in  a .zip

The text file should have the extension .ttf. If you text files have a FFIL extension, simply rename the font file (yourname.ttf).
Drag the text file into unity to import. By default the Character variable should be labeled Dynamic.

The Dynamic setting means  that Unity won't pre-generate the texture, so if you look at your material containing your font, It probably will be blank. (read more here)
Now your over all objective should be getting your font to look like this (unless you prefer dynamic):
Where the characters of the font are not jumbled up and visible.
Now usually all you have to do to achieve this is change the Character variable from Dynamic to Unicode.
and that should work.
Now what some people over…

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 different situation like a …

Don't Destroy On Load..

So if you want to keep an object or script that keeps up variables (or for any other reason) when you go from scene to scene, you need to attach a don't destroy on load static function, which goes something like this:

function Awake () {
    DontDestroyOnLoad (transform.gameObject);
}

The Awake function is call only once, when all the objects in the scene have been created. Read more about it here.

DontDestroyOnLoad has what ever is in the ( ) to not be destroyed when creating a new scene.

(transform.gameObject) is what will not be destroyed when the new scene is loaded, in this case, it will be the game object and all it's children the script is attached to.