I am currently following a Brackeys tutorial on procedural terrain generation colors. I got to a point where it gives me this error:
IndexOutOfRangeException: Index was outside the bounds of the array. mapgeneration.CreateShape () (at Assets/mapgeneration.cs:108
mapgeneration.Update () (at Assets/mapgeneration.cs:131)
I am using gradients to display color on line 108 in CreateShape. This is the line:
colors[iloopedforvertecy] = gradient.Evaluate(height);
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(MeshFilter))]
public class mapgeneration : MonoBehaviour
{
Mesh mesh;
Color[] colors;
Vector3[] vertices;
int[] triangles;
public int xsize = 20;
public int zsize = 20;
[Range(1, 100)]
public float smooth = 1.0f;
public MeshCollider _mesh;
public Transform water;
public float scale;
public float smoothfactor;
public float xoffset = 0.0f;
public float zoffset = 0.0f;
public float minwaterheight;
public float maxwaterheight;
public float minterainheight;
public float maxterainheight;
public Gradient gradient;
// Start is called before the first frame update
void Start()
{
mesh = new Mesh();
GetComponent<MeshFilter>().mesh = mesh;
CreateShape();
_mesh = GetComponent<MeshCollider>();
water.transform.position = new Vector3(0, Random.Range(minwaterheight, maxwaterheight), 0);
}
void CreateShape()
{
vertices = new Vector3[(xsize + 1) * (zsize + 1)];
water.transform.localScale = new Vector3(xsize, 0, zsize);
int iloopedforvertecy = 0;
triangles = new int[xsize * zsize * 6];
int vert = 0;
int tris = 0;
for(int z = 0; z < zsize; z++)
{
for(int x = 0; x < xsize; x++)
{
triangles[tris + 0] = vert + 0;
triangles[tris + 1] = vert + xsize + 1;
triangles[tris + 2] = vert + 1;
triangles[tris + 3] = vert + 1;
triangles[tris + 4] = vert + xsize + 1;
triangles[tris + 5] = vert + xsize + 2;
vert++;
tris += 6;
}
vert++;
}
colors = new Color[vertices.Length];
for (int z = 0; z <= zsize; z++)
{
for(int x = 0; x <= xsize; x++)
{
float xCoord = (float)x / xsize * scale + xoffset;
float zCoord = (float)z / zsize * scale + zoffset;
float y = Mathf.PerlinNoise(xCoord * smooth, zCoord * smooth) * smoothfactor;
vertices[iloopedforvertecy] = new Vector3(x, y, z);
iloopedforvertecy += 1;
if(y > maxterainheight)
{
maxterainheight = y;
}
if(y < minterainheight)
{
minterainheight = y;
}
float height = Mathf.InverseLerp(minterainheight, maxterainheight, 0.5f); //vertices[iloopedforvertecy].z
Debug.LogWarning(height);
colors[iloopedforvertecy] = gradient.Evaluate(height);
}
}
}
void UpdateMsh()
{
mesh.Clear();
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.RecalculateNormals();
mesh.colors = colors;
}
void Update()
{
CreateShape();
UpdateMsh();
}
}
I know that my code is messy. Still new to coding and unity in general.
Oh PS. Can somebody please help me add a collider to code generated object as you can see in the code above?
In your for-loop you increase the value of iloopedforvertecy before you evaluate your gradient value.
Because of this the last value of iloopedforvertecy will be greater than the array length of your colors array.
Try to move the line which increases the value at the end of the loop
for(int x = 0; x <= xsize; x++)
{
float xCoord = (float)x / xsize * scale + xoffset;
float zCoord = (float)z / zsize * scale + zoffset;
float y = Mathf.PerlinNoise(xCoord * smooth, zCoord * smooth) * smoothfactor;
vertices[iloopedforvertecy] = new Vector3(x, y, z);
// iloopedforvertecy += 1; // HERE THE VALUE WAS INCREASED
if(y > maxterainheight)
{
maxterainheight = y;
}
if(y < minterainheight)
{
minterainheight = y;
}
float height = Mathf.InverseLerp(minterainheight, maxterainheight, 0.5f); //vertices[iloopedforvertecy].z
Debug.LogWarning(height);
colors[iloopedforvertecy] = gradient.Evaluate(height);
iloopedforvertecy += 1; // HERE SHOULD THE VALUE BE INCREASED
}
In the example here I commented the two lines.
Related
I'm making procedural terrain generation for my Unity game, and I want to add octaves to my terrain to make it look more like in the image. How would I go about doing this?
Here is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(MeshFilter))]
public class MapGenerator : MonoBehaviour
{
Mesh mesh;
Vector3[] vertices;
int[] triangles;
public int xSize = 20;
public int zSize = 20;
void Start()
{
mesh = new Mesh();
GetComponent<MeshFilter>().mesh = mesh;
CreateShape();
UpdateMesh();
}
void CreateShape()
{
vertices = new Vector3[(xSize + 1) * (zSize + 1)];
for (int i = 0, z = 0; z <= zSize; z++)
{
for (int x = 0; x <= xSize; x++)
{
float y = Mathf.PerlinNoise(x * .3f, z * .3f) * 2f;
vertices[i] = new Vector3(x, y, z);
i++;
}
}
triangles = new int[xSize * zSize * 6];
int vert = 0;
int tris = 0;
for (int z = 0; z < zSize; z++)
{
for (int x = 0; x < xSize; x++)
{
triangles[tris + 0] = vert + 0;
triangles[tris + 1] = vert + xSize + 1;
triangles[tris + 2] = vert + 1;
triangles[tris + 3] = vert + 1;
triangles[tris + 4] = vert + xSize + 1;
triangles[tris + 5] = vert + xSize + 2;
vert++;
tris += 6;
}
vert++;
}
}
void UpdateMesh()
{
mesh.Clear();
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.RecalculateNormals();
}
}
I tried this code below, but it looked more like spikes than a landscape. I would use this tutorial here if I were even able understand it at all.
float[] octaveFrequencies=new float() {1,1.5f,2,2.5f} ;
float[] octaveAmplitudes=new float() {1,0.9f,0.7f,0.f} ;
float y=0;
for(int i=0;i<octaveFrequencies.Length;i++)
y += octaveAmplitudes[i]* Mathf.PerlinNoise(
octaveFrequencies[i]*x + .3f,
octaveFrequencies[i]* z + .3f) * 2f ;
I'm assuming you are implementing this code correctly. It seems like your amplitudes are pretty high. I find that a frequency that doubles each iteration and an amplitude that halves each generation looks nice. The values you choose depend on what kind of terrain you want.
float[] octaveFrequencies=new float() {1,2f,4f,8f} ;
float[] octaveAmplitudes=new float() {1,5f,0.25f,0.125f} ;
Try using these values. These are the settings I commonly use. If you want different-looking terrain, try to change them around a little bit. Changing these values can drastically change the look of the terrain.
Alternatively, you could do this automatically:
const int numOctaves = 4;
float y = 0;
float frequency = 1;
float amplitude = 1;
const float frequencyMult = 2f;
const float amplitudeMult = 0.5f;
for(int i = 0; i < numOctaves; i++) {
y += Mathf.PerlinNoise(x * frequency, z * frequency) * amplitude;
frequency *= frequencyMult;
amplitude *= amplitudeMult;
}
This code does the same thing as before, except you can arbitrarily set the amount of octaves. Increasing the numOctaves increases the detail. Higher frequencyMult increases detail at the cost of smooth terrain, and higher amplitudeMult increases the influence of the detail at the cost of smooth terrain. Change the constants to get a different result. To change the max height, just multiply "y" after this loop.
Some variables and syntax might be wrong, because I just typed this up.
If you haven't noticed, the frequency increases exponentially and the amplitude decreases exponentially. If you decrease amplitude linearly, then there might be too much influence on detail, causing large spikes. Also, if you increase frequency linearly, then similar frequencies will add up, causing the same high hills and valleys.
What it spits out
My raycast spits out a position way off from what it's supposed to be. I'm trying to place objects procedurally on a procedural mesh. I've been scratching my head at this for a while. Please help.
Sorry for the long script.
The start of the code is just some declares and stuff. GenObjects is run once in FixedUpdate after Start has finished. I'm using a marching cubes library by Scrawk and a noise library by Auburn
void GenMesh()
{
Marching marching = new MarchingCubes();
marching.Surface = 0.0f;
voxels = new float[width * height * length];
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
for (int z = 0; z < length; z++)
{
float fx = x / (width - 1.0f);
float fy = y / (height - 1.0f);
float fz = z / (length - 1.0f);
int idx = x + y * width + z * width * height;
float surfaceHeight = noise2.GetNoise(x,z) * amplitude + offset;
float currentHeight = Mathf.Clamp(y, surfaceHeight - threshold, surfaceHeight + threshold);
float t = Mathf.Abs(currentHeight - surfaceHeight) / threshold;
voxels[idx] = Mathf.Lerp(Mathf.Clamp(noise.GetNoise(x,y,z), 0.65f, 1), -1f, t);
}
}
}
List<Vector3> verts = new List<Vector3>();
List<int> indices = new List<int>();
marching.Generate(voxels, width, height, length, verts, indices);
int maxVertsPerMesh = 30000;
int numMeshes = verts.Count / maxVertsPerMesh + 1;
for (int i = 0; i < numMeshes; i++)
{
List<Vector3> splitVerts = new List<Vector3>();
List<int> splitIndices = new List<int>();
for (int j = 0; j < maxVertsPerMesh; j++)
{
int idx = i * maxVertsPerMesh + j;
if (idx < verts.Count)
{
splitVerts.Add(verts[idx]);
splitIndices.Add(j);
}
}
if (splitVerts.Count == 0) continue;
Mesh mesh = new Mesh();
mesh.SetVertices(splitVerts);
mesh.SetTriangles(splitIndices, 0);
mesh.RecalculateBounds();
mesh.RecalculateNormals();
MeshWelder meshWelder = new MeshWelder(mesh);
meshWelder.Weld();
GameObject go = new GameObject("Mesh");
go.layer = LayerMask.NameToLayer("Ground");
go.transform.parent = transform;
go.transform.localScale = new Vector3(100f, 100f, 100f);
go.AddComponent<MeshFilter>();
go.AddComponent<MeshRenderer>();
go.AddComponent<MeshCollider>();
go.GetComponent<Renderer>().material = m_material;
go.GetComponent<MeshFilter>().mesh = mesh;
go.GetComponent<MeshCollider>().sharedMesh = mesh;
go.GetComponent<MeshCollider>().contactOffset = 0f;
go.transform.localPosition = new Vector3(-width * 100 / 2, -height * 100 / 4, -length * 100 / 2);
meshes.Add(go);
}
}
void GenObjects(GameObject prefab, float radius, Vector2 sampleRegionSize, Vector2 origin, int seed)
{
List<Vector2> points = PoissonDiscSampling.GeneratePoints(radius, sampleRegionSize, seed);
Physics.queriesHitBackfaces = true;
foreach (Vector2 point in points)
{
RaycastHit hit;
Vector3 objPos = new Vector3(0,0,0);
bool validPosFound = false;
if (Physics.Raycast(new Vector3(point.x + origin.x, 0, point.y + origin.y), Vector3.down, out hit, height * 100, layerMask))
{
objPos = hit.point;
validPosFound = true;
} else if (Physics.Raycast(new Vector3(point.x + origin.x, 0, point.y + origin.y), Vector3.up, out hit, height * 100, layerMask))
{
objPos = hit.point;
validPosFound = true;
}
if (validPosFound)
{
GameObject newObject = Instantiate(prefab, objPos, Quaternion.Euler(0, 0, 0));
}
}
Physics.queriesHitBackfaces = false;
}
}
Fixed! My mistake was really stupid. I wasn't assigning the welded mesh, leaving a filthy mesh with lots of empty verts floating about. The raycast was hitting them.
The fixed lines for anyone who cares:
Mesh mesh = new Mesh();
Mesh mesh_temp = new Mesh();
mesh_temp.SetVertices(splitVerts);
mesh_temp.SetTriangles(splitIndices, 0);
mesh_temp.RecalculateBounds();
mesh_temp.RecalculateNormals();
MeshWelder meshWelder = new MeshWelder();
meshWelder.customMesh = new CustomMesh();
meshWelder.customMesh.vertices = splitVerts.ToArray();
meshWelder.customMesh.triangles = splitIndices.ToArray();
meshWelder.customMesh.normals = mesh_temp.normals;
meshWelder.Weld();
mesh.SetVertices(meshWelder.customMesh.vertices);
mesh.SetTriangles(meshWelder.customMesh.triangles, 0);
mesh.SetNormals(meshWelder.customMesh.normals);
mesh.RecalculateBounds();
What I am trying to achieve is something like this:
What I have so far is the edges for the circles.
I know this would involve a nested for loop. This is what I have so far:
public GameObject player;
private GameObject playerGrid;
public int numOfObjects;
private Vector3 centerPos;
public int size = 2;
public Vector2 speed = new Vector2(50, 50);
private float smoothTime = 0.25f;
void Start()
{
playerGrid = new GameObject();
centerPos = transform.position;
for (int i = 0; i < numOfObjects; i++)
{
float pointNum = (i * 1.0f) / numOfObjects;
float angle = pointNum * Mathf.PI * 2;
float r = size / 2 * (Mathf.PI);
float x = Mathf.Sin(angle) * r;
float y = Mathf.Cos(angle) * r;
Vector3 pointPos = new Vector3(x, y, 0) + centerPos;
GameObject obj = Instantiate(player, pointPos, Quaternion.identity);
obj.transform.SetParent(playerGrid.transform);
}
}
I am stuck on how to implement the conditional for the nested for loop. Also, I have trouble understanding the calculations of column positions in the nested for loop. I believe the conditional would be the start and end of I for that column or row: for(int j = i + 1; j < i - 1, j++)
For the col positions, I would think it would be incrementing the angle enough to give the square its space for that column: float x = (Mathf.Sin(angle) + somethingHere) * r;
I just not sure how to progress from here.
Here's a simple way to draw a circle:
public float circleRadius = 5f;
public float objectSize = 1f;
void OnDrawGizmos()
{
for (var x = -circleRadius; x <= circleRadius; x++)
{
for (var y = -circleRadius; y <= circleRadius; y++)
{
var pos = new Vector3(x, 0f, y);
if (pos.magnitude >= circleRadius) continue;
Gizmos.DrawSphere(pos * (objectSize * 2f), objectSize);
}
}
}
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!
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!