I am currently working on a procedural terrain generator and I have come to an issue when procedurally spawning trees in my scene. Unity gives me one error but I don't really know how to go about fixing this issue.
Here is the code for my world generator and tree Generator.
World Generator
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TerrainGenerator : MonoBehaviour
{
[SerializeField]
private int terrainWidthInChunks, terrainLengthInChunks;
[SerializeField]
private GameObject ChunkPrefab;
[SerializeField]
private float centreVertex, maxDistance;
[SerializeField]
private TreeGenerator treeGenerator;
[SerializeField]
private RiverGenerator riverGenerator;
// Start is called before the first frame update
void Start()
{
GenerateWorld ();
}
void GenerateWorld()
{
//gets the chunk dimensions from the chunk prefab
Vector3 chunkSize = ChunkPrefab.GetComponent<MeshRenderer>().bounds.size;
int terrainHeight = (int)chunkSize.z;
int terrainWidth = (int)chunkSize.x;
//Calculates the number of vertices of the chunk in each axis of the mesh
Vector3[] terrainMeshVertices = ChunkPrefab.GetComponent<MeshFilter>().sharedMesh.vertices;
int terrainHeightInVertices = (int)Mathf.Sqrt(terrainMeshVertices.Length);
int terrainWidthInVertices = terrainHeightInVertices;
float distanceBetweenVertices = (float)terrainHeight / (float)terrainHeightInVertices;
//builds an empty object to be filled with the terrain chunks that are generated on run-time
WorldData worldData = new WorldData(terrainHeightInVertices, terrainWidthInVertices, this.terrainLengthInChunks, this.terrainWidthInChunks);
//for each chunk we will want to instantiate a chunk in the correct position
for (int x = 0; x < terrainWidthInChunks; x++)
{
for (int z = 0; z < terrainLengthInChunks; z++)
{
// calculates the chunk position based on x and z indices
Vector3 chunkPosition = new Vector3(this.gameObject.transform.position.x + x * terrainWidth, this.gameObject.transform.position.y, this.gameObject.transform.position.z + z * terrainHeight);
//instantiates new chunk
GameObject Chunk = Instantiate(ChunkPrefab, chunkPosition, Quaternion.identity) as GameObject;
//generates the chunk texture and stores it in ChunkData
ChunkData chunkData = Chunk.GetComponent<TerrainchunkGenerator>().GenerateChunk(centreVertex, maxDistance);
worldData.AddChunkData(chunkData, z, x);
}
}
//generates trees for the world
treeGenerator.GenerateTrees(this.terrainLengthInChunks * terrainHeightInVertices, this.terrainWidthInChunks * terrainWidthInVertices, distanceBetweenVertices, worldData);
//generates rivers for the world
riverGenerator.GenerateRivers(this.terrainLengthInChunks * terrainHeightInVertices, this.terrainWidthInChunks * terrainWidthInVertices, worldData);
}
}
//class to store merged chunk data
public class WorldData
{
private int terrainHeightInVertices, terrainWidthInVertices;
public ChunkData[,] chunksData;
public WorldData(int terrainHeightInVertices, int terrainWidthInVertices, int terrainLengthInChunks, int terrainWidthInChunks)
{
//builds the chunkdata matrix based on terrain depth and width in verts and chunks
chunksData = new ChunkData[terrainHeightInVertices * terrainLengthInChunks, terrainWidthInVertices * terrainWidthInChunks];
this.terrainHeightInVertices = terrainHeightInVertices;
this.terrainWidthInVertices = terrainWidthInVertices;
}
public void AddChunkData(ChunkData chunkData, int chunkZIndex, int chunkXIndex)
{
//saves the chunk data in the correct coordinates
chunksData[chunkZIndex, chunkXIndex] = chunkData;
}
public ChunkCoordinate ConvertToChunkCoordinate(int z, int x)
{
//the chunk index is calculates by dividing the index by yhr number of chunks in that axis
int chunkZIndex = (int)Mathf.Floor((float)z / (float)this.terrainHeightInVertices);
int chunkXIndex = (int)Mathf.Floor((float)x / (float)this.terrainWidthInVertices);
//the coord index is calculated by aquiring the remainder of the division operation above
//this translates te origin to the bottom left corner
int coordinateZIndex = this.terrainHeightInVertices - (z % this.terrainHeightInVertices) - 1;
int coordinateXIndex = this.terrainWidthInVertices - (x % this.terrainWidthInVertices) - 1;
ChunkCoordinate chunkCoordinate = new ChunkCoordinate(chunkZIndex, chunkXIndex, coordinateZIndex, coordinateXIndex);
return chunkCoordinate;
}
}
//class that represents a coordinate in the chunk coordinate System
public class ChunkCoordinate
{
public int chunkZIndex;
public int chunkXIndex;
public int coordinateZIndex;
public int coordinateXIndex;
public ChunkCoordinate(int chunkZIndex, int chunkXIndex, int coordinateZIndex, int coordinateXIndex)
{
this.chunkZIndex = chunkZIndex;
this.chunkXIndex = chunkXIndex;
this.coordinateZIndex = coordinateZIndex;
this.coordinateXIndex = coordinateXIndex;
}
}
*tree generator*
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TreeGenerator : MonoBehaviour
{
[SerializeField]
private NoisemapGenerator noisemapGenerator;
[SerializeField]
private Octave[] octaves;
[SerializeField]
private float terrainScale;
[SerializeField]
private float[] neighborRadius;
[SerializeField]
private GameObject[] treePrefab;
public void GenerateTrees(int height, int width, float distanceBetweenVertices, WorldData worldData)
{
//Generates a tree noise map using the perlin noise function from noise map generator
float[,] treeNoiseMap = this.noisemapGenerator.GeneratePerlinNoiseMap(height, width, this.terrainScale, 0, 0, this.octaves);
float chunkSizeX = width * distanceBetweenVertices;
float chunkSizeZ = height * distanceBetweenVertices;
for (int z = 0; z < height; z++)
{
for (int x = 0; x < width; x++)
{
//converts from the world coord system to the chunk coord system and retreieves the chunkdata
ChunkCoordinate chunkCoordinate = worldData.ConvertToChunkCoordinate(z, x);
ChunkData chunkData = worldData.chunksData[chunkCoordinate.chunkZIndex, chunkCoordinate.chunkXIndex];
int chunkWidth = chunkData.heightMap.GetLength(1);
//calculates the mesh vertex index
Vector3[] meshVertices = chunkData.mesh.vertices;
int vertexIndex = chunkCoordinate.coordinateZIndex * chunkWidth + chunkCoordinate.coordinateXIndex;
//gets the terrain region of this coord
TerrainRegion terrainRegion = chunkData.chosenHeightTerrainRegion[chunkCoordinate.coordinateZIndex, chunkCoordinate.coordinateXIndex];
//gets the current biome of the coord
Biome biome = chunkData.chosenBiomes[chunkCoordinate.coordinateZIndex, chunkCoordinate.coordinateXIndex];
//check if the coord is "water" terrain. We obviouslt don't want trees to be placed within the water
if (terrainRegion.name != "water")
{
float treeValue = treeNoiseMap[z, x];
int terrainRegionIndex = terrainRegion.index;
//compares the current noise value to the neighbouring values
int neighborZBegin = (int)Mathf.Max(0, z - this.neighborRadius[biome.index]);
int neighborZEnd = (int)Mathf.Min(height-1, z + this.neighborRadius[biome.index]);
int neighborXBegin = (int)Mathf.Max(0, x - this.neighborRadius[biome.index]);
int neighborXEnd = (int)Mathf.Min(width - 1, x + this.neighborRadius[biome.index]);
float maxValue = 0f;
for (int neighborZ = neighborZBegin; neighborZ <= neighborZEnd; neighborZ++)
{
for (int neighborX = neighborXBegin; neighborX <= neighborXEnd; neighborX++)
{
float neighborValue = treeNoiseMap[neighborZ, neighborX];
//saves the max tree noise values in the radius
if (neighborValue >= maxValue)
{
maxValue = neighborValue;
}
}
}
//if the current trees nise value is the max of one then place a tree in that location
if (treeValue == maxValue)
{
Vector3 treePosition = new Vector3(x * distanceBetweenVertices, meshVertices[vertexIndex].y, z * distanceBetweenVertices);
GameObject tree = Instantiate(this.treePrefab[biome.index], treePosition, Quaternion.identity) as GameObject;
tree.transform.localScale = new Vector3(0.05f, 0.05f, 0.05f);
}
}
}
}
}
}
In GenerateWorld() method, i see you are using treeGenerator.GenerateTrees method but i didn't see any initialization of the treeGenerator object. Is this where you are getting the error. then you need to initialize the class object before using it.
I think you also need to initialize the riverGenerator object as well.
Related
We are trying, with a friend, to modelise a tree on unity. To do so, we want to modelise a tree trunk. We are doing it with the following code :
`
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
public class Frustum : MonoBehaviour
{
/*====== PUBLIC ======*/
public float height = 1;
public float top_radius = 0.9F;
public float base_radius = 1F;
public int circle_subdivisions = 30;
/*====== PRIVATE ======*/
private Mesh mesh;
private Vector3[] vao;
private int[] ibo;
// Start is called before the first frame update
void Start()
{
mesh = new Mesh();
mesh.name = "Cone";
this.GetComponent<MeshFilter>().mesh = mesh;
draw();
}
// OnValidate is called at each public input change
private void OnValidate() {
draw();
}
private void draw() {
ComputeFrustumVao();
ComputeFrustumIbo();
mesh.Clear();
mesh.vertices = vao;
mesh.triangles = ibo;
mesh.RecalculateBounds();
mesh.RecalculateTangents();
mesh.RecalculateNormals();
// mesh.Optimize();
}
private void ComputeFrustumVao()
{
// COMPUTE BASE VERTICES
List<Vector3> vertices = ComputeCircleVertices(transform.position, base_radius, circle_subdivisions);
// ADD TOP VERTICES
Vector3 top_origin = transform.position + new Vector3(0, height, 0);
vertices.AddRange(ComputeCircleVertices(top_origin, top_radius, circle_subdivisions));
vao = vertices.ToArray();
}
private List<Vector3> ComputeCircleVertices(Vector3 origin, float radius, int nb_subdivisions) {
List<Vector3> vao = new List<Vector3>();
float step_angle = (2*Mathf.PI)/nb_subdivisions;
for(int i = 0; i < nb_subdivisions; ++i){
float current_angle = step_angle * i;
Vector3 translate_vector = new Vector3(
Mathf.Cos(current_angle) * radius, // x coordinate
0, // y coordinate
Mathf.Sin(current_angle) * radius // z coordinate
);
Vector3 new_vertex = origin + translate_vector;
vao.Add(new_vertex);
}
return vao;
}
private void ComputeFrustumIbo() {
List<int> indices = new List<int>();
int nb_vertices = vao.Length;
int nb_vertices_per_circle = nb_vertices/2;
int nb_triangles = nb_vertices_per_circle - 2;
// DRAW BASE
for(int i = 0; i < nb_triangles; ++i){
indices.Add(0);
indices.Add(i + 1);
indices.Add(i + 2);
}
// DRAW TOP
for(int i = nb_vertices_per_circle; i < nb_vertices_per_circle + nb_triangles; ++i){
indices.Add(nb_vertices_per_circle);
indices.Add(i + 2);
indices.Add(i + 1);
}
// DRAW SIDES
for(int i = 0; i < nb_vertices_per_circle; ++i){
int next_index = (i + 1) % nb_vertices_per_circle;
indices.Add(i); // BOTTOM LEFT
indices.Add( nb_vertices_per_circle + next_index); // TOP RIGHT
indices.Add(next_index); // BOTTOM RIGHT
indices.Add(i); // BOTTOM LEFT
indices.Add(i + nb_vertices_per_circle); // TOP LEFT
indices.Add(nb_vertices_per_circle + next_index); // TOP RIGHT
}
ibo = indices.ToArray();
}
void OnDrawGizmosSelected() //Draw the markers, if the object is selected
{
Gizmos.color = Color.red;
for (int i=0; i< mesh.vertices.Length; i++){
Gizmos.color= new Color(((float)i)/mesh.vertices.Length,0, 0);
Vector3 worldPos = transform.TransformPoint(mesh.vertices[i]);
Gizmos.DrawCube(worldPos, Vector3.one * 0.1f);
}
}
}
`
With this code we can create a 3D shape like a cube or a cylinder but when we create our top and our base ( in DRAW TOP and DRAW BASE) it makes the normal looks weird and I dont understand why. Can you help me to understand it please ?
our Normal problem
We know that ussually people create their models in 3D software like blender but we really need to do it in unity for our project.
I've seen that if my normals are pointing to the top the face are too illuminated but when they are facing down it's too shady. To create our base and top shape, we have followed this tutorial
Noise map is being called from a separate script called NoiseMapGeneration, and noiseMap method is public.
Please someone help me I have been reading this over and over for an hour; I have no idea what I am doing wrong. I am using these scripts in unity and testing this section.
public class TileGeneration : MonoBehaviour
{
[SerializeField]
NoiseMapGeneration noiseMapGeneration;
[SerializeField]
private MeshRenderer tileRenderer;
[SerializeField]
private MeshFilter meshFilter;
[SerializeField]
private MeshCollider meshCollider;
[SerializeField]
private float mapScale;
void Start()
{
GenerateTile();
}
void GenerateTile()
{
// vertices
Vector3[] meshVertices = this.meshFilter.mesh.vertices;
int tileDepth = (int)Mathf.Sqrt(meshVertices.Length);
int tileWidth = tileDepth;
float[,] heightMap = this.noiseMapGeneration.GenerateNoiseMap(tileDepth, tileWidth, this.mapScale);
Texture2D tileTexture = BuildTexture(heightMap);
this.tileRenderer.material.mainTexture = tileTexture;
}
private Texture2D BuildTexture(float[,] heightMap)
{
int tileDepth = noiseMap.GetLength(0);
int tileWidth = noiseMap.GetLength(1);
Color[] colorMap = new Color[tileDepth * tileWidth];
for (int zIndex = 0; zIndex < tileDepth; zIndex++)
{
for (int xIndex = 0; xIndex < tileWidth; xIndex++)
{
// transform the 2D map index is an Array index
int colorIndex = zIndex * tileWidth + xIndex;
float height = heightMap[zIndex, xIndex];
colorMap[colorIndex] = Color.Lerp(Color.black, Color.white, height);
}
}
Texture2D tileTexture = new Texture2D(tileWidth, tileDepth);
tileTexture.wrapMode = TextureWrapMode.Clamp;
tileTexture.SetPixels(colorMap);
tileTexture.Apply();
return tileTexture;
}
}
For future people who find this. The issue in the tutorial is they changed the name between the two functions. change noiseMap to heightMap
This is where it is set
float[,] heightMap = this.noiseMapGeneration.GenerateNoiseMap(tileDepth, tileWidth, this.mapScale);
And this is where they call it
int tileDepth = noiseMap.GetLength(0);
int tileWidth = noiseMap.GetLength(1);
So make it
int tileDepth = heightMap.GetLength(0);
int tileWidth = heightMap.GetLength(1);
In this script I'm creating a circle with specific radius size and get the radius size :
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(LineRenderer))]
public class DrawRadiusAroundTurret : MonoBehaviour
{
[Range(0, 50)]
public int segments = 50;
[Range(0, 5)]
public float xradius = 5;
[Range(0, 5)]
public float yradius = 5;
[Range(0.1f, 5f)]
public float width = 0.1f;
LineRenderer line;
void Start()
{
line = gameObject.GetComponent<LineRenderer>();
line.enabled = true;
line.positionCount = segments + 1;
line.widthMultiplier = width;
line.useWorldSpace = false;
CreatePoints();
}
private void Update()
{
CreatePoints();
}
public Vector3[] CreatePoints()
{
line.widthMultiplier = width;
float x;
float y;
float z;
float angle = 20f;
for (int i = 0; i < (segments + 1); i++)
{
x = Mathf.Sin(Mathf.Deg2Rad * angle) * xradius;
y = Mathf.Cos(Mathf.Deg2Rad * angle) * yradius;
line.SetPosition(i, new Vector3(x, 0f, y));
angle += (380f / segments);
}
var positions = new Vector3[line.positionCount];
return positions;
}
}
And in this script I have this method and I want the last point/s to be set randomly on the radius edge positions :
private void GeneratePointsInTracks()
{
var startPoints = GameObject.FindGameObjectsWithTag("Start Point");
var curvedLines = GameObject.FindGameObjectsWithTag("Curved Line");
for (int i = 0; i < startPoints.Length; i++)
{
for (int x = 0; x < numberOfPointsInTrack; x++)
{
GameObject go = Instantiate(tracksPrefab, curvedLines[i].transform);
go.name = "Point In Track";
go.transform.position = turrent.position + new Vector3(Random.Range(-100f, 100f), Random.Range(-100f, 100f), Random.Range(-100f, 100f));
if(x == numberOfPointsInTrack - 1)
{
go.name = "Last Point In Track";
for(int y = 0; y < drawRadius.CreatePoints().Length; y++)
{
go.transform.position = new Vector3(Random.Range(0,1)[y].x,
drawRadius.CreatePoints()[y].y,
drawRadius.CreatePoints()[y].z);
}
}
}
}
}
I tried this :
go.transform.position = new Vector3(Random.Range(0,1)[y].x,
drawRadius.CreatePoints()[y].y,
drawRadius.CreatePoints()[y].z);
but the random on the x give error :
Cannot apply indexing with [] to an expression of type 'int'
The first script create a circle like this :
And this is an example I drawed in paint just to show what I mean that I said I want the endPoints in the second script to be position randomly on the circle edges :
So each "Last Point In Track" object should be position randomly on the circle edge like in the second screenshot.
If you know the center and radius it is fairly easy to get random points on the circle:
go.name = "Last Point In Track";
Vector2 p = Random.insideUnitCircle.normalized * radius;
go.transform.position = center + new Vector3(p.x, 0, p.y);
To get rid of the edge case where Random.insideUnitCircle is to small to be normalized you should use:
Vector2 RandomOnUnitCircle(){
Vector2 result = Vector2.zero;
do{
result = Random.insideUnitCircle.normalized;
}while(result == Vector2.zero);
return result;
}
I think what you want is this:
Vector3[] points = drawRadius.CreatePoints(); //get all edge points
Vector3 randomPoint = points[Random.Range(0, points.Length)]; //pick a random one
go.transform.position = randomPoint; //set go.transform.position to position of random point
Sorry if I am misunderstanding your intentions, but I hope this works for you!
So I want to make a map generator and have a prefab for a tile. But I want to make more tiles which it randomly chooses from.
How can I make it that tilePrefab is chosen randomly every time from an array of prefabs?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MapGenerator : MonoBehaviour
{
public Transform tilePrefab; //This is the part which I want to be random
public Vector2 mapSize;
private void Start()
{
GenerateMap();
}
public void GenerateMap()
{
for (int x = 0; x < mapSize.x; x++)
{
for (int y = 0; y < mapSize.y; y++)
{
Vector3 tilePosition = new Vector3(-mapSize.x / 2 + 0.5f + x, 0,-mapSize.y / 2 + 0.5f + y);
Transform newTile = Instantiate(tilePrefab, tilePosition, Quaternion.Euler(Vector3.right * 360)) as Transform;
}
}
}
}
I want that tilePrefab is chosen randomly every time a tile gets generated.
Have an array that holds all the tile prefabs. Randomly choose an index for your new tile.
Transform newTile = Instantiate(tilePrefabs[Random.Range(0, tilePrefabs.Length - 1), tilePosition, Quaternion.Euler(Vector3.right * 360)) as Transform;
I wrote a infinite terrain script which works! Saddly everytime the player moves a chunk it lags for a moment. I know my code isn't great but I'm here to learn why :D
I'm unsure of what else to do. I've looked online and found no simple or understandable solution to me because I just don't know enough so I tried to write it on my own and it works but barley.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GEN_InfiniteTerrain : MonoBehaviour
{
public GameObject targetObject;
public GameObject chunkObject;
public int chunkSize;
public float unitSize;
public int renderDistance;
Dictionary<Vector2, GameObject> gridOfChunks = new Dictionary<Vector2, GameObject>();
List<Vector2> expectedChunkGridPositions = new List<Vector2>();
public float noiseScale;
// Infinite terrain values
float absoluteChunkSize;
private void Start()
{
// Calculate absolute chunk size
GetAbsoluteChunkSize();
// Generate base world
GenerateBase();
}
Vector2 lastTargetGridPosition = Vector2.zero;
private void LateUpdate()
{
// Get the targets position in world space
Vector3 targetAbsolutePosition = targetObject.transform.position;
// Convert the targets world position to grid position (/ 10 * 10 is just rounding to 10)
Vector2 targetGridPosition = new Vector2();
targetGridPosition.x = Mathf.RoundToInt(targetAbsolutePosition.x / 10) * 10 / absoluteChunkSize;
targetGridPosition.y = Mathf.RoundToInt(targetAbsolutePosition.z / 10) * 10 / absoluteChunkSize;
if (targetGridPosition - lastTargetGridPosition != Vector2.zero)
{
GenerateExpectedChunkAreas(targetGridPosition);
UpdateChunkPositions(targetGridPosition);
}
lastTargetGridPosition = targetGridPosition;
}
void GenerateBase()
{
for (int x = -renderDistance / 2; x < renderDistance / 2; x++)
{
for (int z = -renderDistance / 2; z < renderDistance / 2; z++)
{
Vector2 gridPosition = new Vector2(x, z);
Vector3 worldPosition = new Vector3(x * (unitSize * chunkSize), 0, z * (unitSize * chunkSize));
GameObject chunk = Instantiate(chunkObject, worldPosition, Quaternion.identity);
chunk.GetComponent<GEN_Chunk>().gridPosition = gridPosition;
gridOfChunks.Add(gridPosition, chunk);
}
}
GenerateExpectedChunkAreas(Vector2.zero);
}
void GenerateExpectedChunkAreas(Vector2 targetGridPosition)
{
expectedChunkGridPositions.Clear();
for (int x = -renderDistance / 2; x < renderDistance / 2; x++)
{
for (int z = -renderDistance / 2; z < renderDistance / 2; z++)
{
Vector2 gridPosition = new Vector2(x, z) + targetGridPosition;
expectedChunkGridPositions.Add(gridPosition);
}
}
}
void UpdateChunkPositions(Vector2 targetGridPosition)
{
List<Vector2> positionsWithoutChunks = new List<Vector2>();
List<Vector2> positionsWithOldChunks = new List<Vector2>();
for (int chunkCount = 0, x = -renderDistance / 2; x < renderDistance / 2; x++)
{
for (int z = -renderDistance / 2; z < renderDistance / 2; z++)
{
Vector2 gridPosition = new Vector2(x, z) + targetGridPosition;
if(!gridOfChunks.ContainsKey(gridPosition))
{
positionsWithoutChunks.Add(gridPosition);
}
chunkCount++;
}
}
foreach (GameObject chunk in gridOfChunks.Values)
{
if(!expectedChunkGridPositions.Contains(chunk.GetComponent<GEN_Chunk>().gridPosition))
{
positionsWithOldChunks.Add(chunk.GetComponent<GEN_Chunk>().gridPosition);
}
}
for (int i = 0; i < positionsWithOldChunks.Count; i++)
{
Vector3 worldPosition = new Vector3(positionsWithoutChunks[i].x * absoluteChunkSize, 0, positionsWithoutChunks[i].y * absoluteChunkSize);
gridOfChunks[positionsWithOldChunks[i]].transform.position = worldPosition;
// Recalculating noise for chunk based on its new position does lag more but even WITHOUT this it still stutters when player moves around. ( plan to learn threading just to calculate noise on seperate threads )
// gridOfChunks[positionsWithOldChunks[i]].GetComponent<GEN_Chunk>().ApplyNoise();
}
}
void GetAbsoluteChunkSize()
{
absoluteChunkSize = unitSize * chunkSize;
}
}
I need some smooth working infinite terrain (in quotes 'infinite')
And I'd like to learn too!