Skip to content

Instantly share code, notes, and snippets.

@cmcintosh
Created February 23, 2023 14:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cmcintosh/294e5115049d14d10db6da22b8828a36 to your computer and use it in GitHub Desktop.
Save cmcintosh/294e5115049d14d10db6da22b8828a36 to your computer and use it in GitHub Desktop.
// Available on the Unity Asset Store https://www.assetstore.unity.com/publishers/49504
// Copyright © 2020 Shamil Bikmullin
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CityGenerator : MonoBehaviour
{
[Range(11,5000)]
//Number of tiles on the side city
public int CitySize = 30;
//Tile size
public float CellSize = 9;
// Arrays of tiles
public GameObject[] Building, Terrain, Road;
// Store the data to export, for recreation.
private string[][] _cityData;
private List<CityTileData> _cityTiles;
//It works in edit mode and play mode
// Use it for generate new city
// City is CitySize x CitySize array of tiles. Where each tile is a GameObject from Building, Terrain, Road arrays
public void GenerateCity()
{
Debug.Log("Generating new city layout");
ClearCity();
_cityData = GenerateData(CitySize);
buildCityFromData();
Debug.Log("City generation complete");
}
/**
* Allows us to regenerated the city from saved data.
*/
public void GenerateCity(List<CityTileData> cityTileData) {
Debug.Log("Generating city layout from saved data.");
ClearCity();
_cityTiles = cityTileData;
buildCityFromCityData();
Debug.Log("City generation complete");
}
public List<CityTileData> getCityData() {
return _cityTiles;
}
[System.Serializable]
public class CityTileData {
public int x;
public int y;
public string type;
public int terrainId;
public int terrainRot;
}
// Builds a new city.
private void buildCityFromData() {
_cityTiles = new List<CityTileData>();
for(int j=0;j<_cityData.Length;j++)
{
for(int i=0;i<_cityData[j].Length;i++)
{
if(_cityData[j][i]==" ") {
CityTileData cityTile = AddTerrain(i,j,_cityData);
_cityTiles.Add(cityTile);
}
if(_cityData[j][i]=="_"){
CityTileData cityTile = AddRoad(i,j,_cityData);
_cityTiles.Add(cityTile);
}
if(_cityData[j][i]=="a") {
CityTileData cityTile = AddHouse(i,j,_cityData);
_cityTiles.Add(cityTile);
}
}
}
}
// Recreates a saved city.
private void buildCityFromCityData() {
foreach(CityTileData cityTile in _cityTiles) {
switch(cityTile.type) {
case " ":
// Terrain
AddCell(Terrain[cityTile.terrainId],cityTile.x,cityTile.y,cityTile.terrainRot);
break;
case "a":
// Building
AddCell(Building[cityTile.terrainId],cityTile.x,cityTile.y,cityTile.terrainRot);
break;
case "_":
// Road
AddCell(Road[cityTile.terrainId],cityTile.x,cityTile.y,cityTile.terrainRot);
break;
}
}
}
// remove all tiles in container of the city
public void ClearCity()
{
Debug.Log("Clearing old city");
for(int i = this.transform.childCount-1;i>=0;i--)
{
Destroy(this.transform.GetChild(i).gameObject);
}
}
// Adding a random terrain tile
CityTileData AddTerrain(int i, int j, string[][] cityData) {
int terrainId = Random.Range(0, Terrain.Length);
int terrainRot = Random.Range(0, 4)*90;
AddCell(Terrain[terrainId],i,j,terrainRot);
CityTileData cityTile = new CityTileData();
cityTile.type = " ";
cityTile.terrainId = terrainId;
cityTile.terrainRot = terrainRot;
cityTile.x = i;
cityTile.y = j;
_cityTiles.Add(cityTile);
return cityTile;
}
// Adding a random House Tile
CityTileData AddHouse(int x, int y, string[][] cityData)
{
CityTileData cityTile = new CityTileData();
cityTile.type = "a";
cityTile.x = x;
cityTile.y = y;
List<int> d = new List<int>{0,1,2,3};
string[][] g = cityData;
int i;
for(i=0;i<d.Count;i++)
{
int j = Random.Range(0, 4);
int temp = d[j];
d[j] = d[i];
d[i] = temp;
}
int[] p = new[]{1,0,0,1,-1,0,0,-1};
for(i=0;i<d.Count;i++)
{
int dy = y+p[d[i]*2+1];
if(dy<0||dy>=g.Length) break;
int dx = x+p[d[i]*2+0];
if(dx<0||dx>=g[dy].Length) break;
if(g[dy][dx]=="_")
{
int id = Random.Range(0, Building.Length);
int rot = d[i]*90;
AddCell(Building[id],x,y,rot);
cityTile.terrainId = id;
cityTile.terrainRot = rot;
return cityTile;
}
}
return cityTile;
}
// Adding a Road Tile
CityTileData AddRoad(int x, int y, string[][] g)
{
CityTileData cityTile = new CityTileData();
cityTile.type = "_";
cityTile.x = x;
cityTile.y = y;
int[][] d = new[]{
new[]{4,0,0,1,2,3},
new[]{3,3,0,2,3},
new[]{3,2,1,2,3},
new[]{3,1,0,1,2},
new[]{3,0,0,1,3},
new[]{2,3,3,0},
new[]{2,2,2,3},
new[]{2,1,1,2},
new[]{2,0,0,1},
new[]{1,1,1,3},
new[]{1,0,0,2}
};
int[] p = new[]{1,0,0,1,-1,0,0,-1};
for(int j=0;j<d.Length;j++)
{
int i=2;
for(i=2;i<d[j].Length;i++)
{
int w = d[j][i];
int dy = y+p[w*2+1];
if(dy<0||dy>=g.Length) break;
int dx = x+p[w*2+0];
if(dx<0||dx>=g[dy].Length) break;
if(g[dy][dx]!="_") break;
}
if(i==d[j].Length)
{
int roadIndex = d[j][0] - 1;
int rotation = d[j][1]*90;
cityTile.terrainId = roadIndex;
cityTile.terrainRot = rotation;
AddCell(Road[roadIndex],x,y,rotation);
return cityTile;
}
}
return cityTile;
}
// Adding the GameObject
public void AddCell(GameObject cell,int x,int y,float deg)
{
Quaternion rotation = Quaternion.Euler(0,-deg,0);
Matrix4x4 m = Matrix4x4.Translate(new Vector3(x*CellSize,0,y*CellSize)) * Matrix4x4.Rotate(rotation) * Matrix4x4.Translate(new Vector3(-CellSize/2,0,-CellSize/2));
GameObject a = Instantiate(cell) as GameObject;
a.name = "tile_"+x.ToString()+"x"+y.ToString();
a.transform.position = new Vector3(m[0,3], m[1,3], m[2,3]);
a.transform.rotation = m.rotation;
a.transform.SetParent(this.transform);
}
// Generate CitySize x CitySize Array of cells
// where cell is Symbol:
// "_" is Road Cell
// "a" is Building Cell
// " " is Terrain Cell
string[][] GenerateData(int n)
{
string[][][] squareData = GetSquareData();
int i;
int j;
string[][] g = new string[n][];
for(i=0;i<n;i++)
{
g[i] = new string[n];
for(j=0;j<n;j++)g[i][j]=" ";
}
string[][] p = squareData[Random.Range(0, squareData.Length)];
SetRegion(p, g, Mathf.FloorToInt(n/2-p[0].Length/2), Mathf.FloorToInt(n/2-p.Length/2), false);
int changesLeft = 400;
for (int k = 5000; k > 0 && changesLeft > 0; k--)
{
p = squareData[Random.Range(0, squareData.Length)];
//var p = SquareData[0];
int w = p[0].Length;
int h = p.Length;
int d = 1;
int x = Mathf.RoundToInt(Random.value * (g.Length - w - d*2))+d;
int y = Mathf.RoundToInt(Random.value * (g.Length - h - d*2))+d;
if (IsEmpty(g, x, y, w, h)) continue;
if (!IsEmpty(g, x+1, y+1, w-2, h-2)) continue;
string[][] r = GetRegion(g, x, y, w, h);
SetRegion(p, g, x, y, false);
var revert = false;
for (j = y - 2; !revert&&j <= y + h; j++)
for (i = x - 2; !revert&&i <= x + w; i++)
{
if(GetCell(g,i,j)=="_"&&
GetCell(g,i+1,j)=="_"&&
GetCell(g,i+1,j+1)=="_"&&
GetCell(g, i, j + 1) == "_") revert = true;
if(GetCell(g,i,j)!="_"&&
GetCell(g,i+1,j)=="_"&&
GetCell(g,i+1,j+1)!="_"&&
GetCell(g, i, j + 1) == "_") revert = true;
if(GetCell(g,i,j)=="_"&&
GetCell(g,i+1,j)!="_"&&
GetCell(g,i+1,j+1)=="_"&&
GetCell(g, i, j + 1) != "_") revert = true;
if (
GetCell(g,i,j)=="_"&&
GetCell(g,i+1,j)==" "&&
GetCell(g,i+2,j)=="_") revert = true;
if (
GetCell(g,i,j)=="_"&&
GetCell(g,i,j+1)==" "&&
GetCell(g,i,j+2)=="_") revert = true;
}
if (revert) SetRegion(r, g, x, y, true);
else changesLeft--;
}
return g;
}
void SetRegion(string[][] pattern,string[][] g, int x, int y,bool replace)
{
for (var j = 0; j < pattern.Length; j++)
for (var i = 0; i < pattern[j].Length; i++)
{
if (!replace&&GetCell(g, x+i, y+j) != " ") continue;
SetCell(g, x + i, y + j, GetCell(pattern, i, j));
}
}
string GetCell(string[][] pattern, int i, int j)
{
if (j<0||j>=pattern.Length) return null;
if (i<0||i>=pattern[j].Length) return null;
return pattern[j][i];
}
void SetCell(string[][] pattern, int i, int j, string s)
{
pattern[j][i]=s;
}
bool IsEmpty(string[][] g, int x, int y, int w, int h)
{
for (int j = 0; j < h; j++)
for (int i = 0; i < w; i++)
{
string s = GetCell(g, x + i, y + j);
if (s!=null && s != " ") return false;
}
return true;
}
string[][] GetRegion(string[][] g, int x, int y, int w, int h)
{
string[][] p = new string[h][];
for (int j = 0; j < h; j++)
for (int i = 0; i < w; i++)
{
if (i==0) p[j] = new string[w];
string s = GetCell(g, x + i, y + j);
if (s==null) s = " ";
SetCell(p, i, j, s);
}
return p;
}
// initialize array of Insert Template
// "_" is Road Cell
// "a" is Building Cell
// " " is Terrain Cell
string[][][] GetSquareData()
{
return new[]{
new[]{
new[]{"_","_","_","_","_","_","_","_","_"},
new[]{"_","a","a","a","_","a","a","a","_"},
new[]{"_","a","a","a","_","a","a","a","_"},
new[]{"_","_","_","_","_","_","_","_","_"},
new[]{"_","a","a","a","_","a","a","a","_"},
new[]{"_","a","a","a","_","a","a","a","_"},
new[]{"_","_","_","_","_","_","_","_","_"}
},new[]{
new[]{"_","_","_","_","_","_","_"},
new[]{"_","a","a","_","a","a","_"},
new[]{"_","a","a","_","a","a","_"},
new[]{"_","_","_","_","_","_","_"}
},new[]{
new[]{"_","_","_","_"},
new[]{"_","a","a","_"},
new[]{"_","_","_","_"}
},new[]{
new[]{"_","_","_","_"},
new[]{"_","a","a","_"},
new[]{"_","a","a","_"},
new[]{"_","a","a","_"},
new[]{"_","_","_","_"}
},new[]{
new[]{"_","_","_","_","_","_","_","_","_"},
new[]{"_","a","a","a","_","a","a","a","_"},
new[]{"_","a","a","a","_","a","a","a","_"},
new[]{"_","_","_","_","_","_","_","_","_"}
}
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment