- Home /
Need help fixing procedural generation bug
I've been trying to fix this bug for a week and I still haven't come across a solution. I'm working on a procedural generation system where I have a couple of GameObjects:
A player which controls the camera (with the Player.cs script)
A "Chunk handler" which updates all of the chunks in the render distance each frame (with the ChunkHandler.cs script)
The more the player moves, the further away the chunks load, this goes on to the point where the chunks are being generated 200 units away from the actual player's position.
Player.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
public class Player : MonoBehaviour {
private float movementSpeed = 10f;
private float fastMovementSpeed = 100f;
void Start () {
LockCursor ();
}
// Updates the player position and rotation each frame
void Update () {
// Determines the movement speed based on whether or not the user is pressing the shift key
var fastMode = Input.GetKey ( KeyCode.LeftShift ) || Input.GetKey ( KeyCode.RightShift );
var movementSpeed = fastMode ? this.fastMovementSpeed : this.movementSpeed;
// Left movement
if ( Input.GetKey ( KeyCode.A ) || Input.GetKey ( KeyCode.LeftArrow ) ) {
transform.position = transform.position + ( -transform.right * movementSpeed * Time.deltaTime );
}
// Right movement
if ( Input.GetKey ( KeyCode.D ) || Input.GetKey ( KeyCode.RightArrow ) ) {
transform.position = transform.position + ( transform.right * movementSpeed * Time.deltaTime );
}
// Forward movement
if ( Input.GetKey ( KeyCode.W ) || Input.GetKey ( KeyCode.UpArrow ) ) {
transform.position = transform.position + ( transform.forward * movementSpeed * Time.deltaTime );
}
// Backwards movement
if ( Input.GetKey ( KeyCode.S ) || Input.GetKey ( KeyCode.DownArrow ) ) {
transform.position = transform.position + ( -transform.forward * movementSpeed * Time.deltaTime );
}
// Up movement
if ( Input.GetKey ( KeyCode.Q ) ) {
transform.position = transform.position + ( transform.up * movementSpeed * Time.deltaTime );
}
if ( Input.GetKey ( KeyCode.E ) ) {
transform.position = transform.position + ( -transform.up * movementSpeed * Time.deltaTime );
}
if ( Input.GetKey ( KeyCode.R ) || Input.GetKey ( KeyCode.PageUp ) ) {
transform.position = transform.position + ( Vector3.up * movementSpeed * Time.deltaTime );
}
if (Input.GetKey ( KeyCode.F ) || Input.GetKey ( KeyCode.PageDown ) ) {
transform.position = transform.position + ( -Vector3.up * movementSpeed * Time.deltaTime );
}
}
// Hides the cursor
public void LockCursor () {
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
}
// Makes the cursor position visible on the screen
public void UnlockCursor () {
Cursor.visible = true;
Cursor.lockState = CursorLockMode.None;
}
// Returns the current position of the player
public Vector3 GetPosition () {
return transform.position;
}
}
ChunkHandler.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ChunkHandler : MonoBehaviour {
private int renderDistance;
private int chunkSize;
public Player player;
private Dictionary<Vector3, Chunk> chunks = new Dictionary<Vector3, Chunk> ();
void Start () {
// Configures the terrain generation settings
chunkSize = 24;
renderDistance = 2;
// Gets the player object
player = GameObject.Find ( "Player" ).GetComponent<Player> ();
}
// Updates the visible chunks each frame
void Update () {
UpdateChunks ();
}
// Updates the visible chunks
private void UpdateChunks () {
// Gets the chunk the player is currently in
Vector3 playerChunk = GetPlayerChunk ();
// Gets the player's position
Vector3 playerPosition = player.GetPosition ();
// Loops through each possible chunk in the render distance
for ( int x = -renderDistance; x < renderDistance; x++ ) {
for ( int z = -renderDistance; z < renderDistance; z++ ) {
// Calculates the chunk's position relative to the player's current chunk
Vector3 chunkPosition = new Vector3 ( playerChunk.x + x * chunkSize / 2, playerChunk.y, playerChunk.z + z * chunkSize / 2 );
// Checks if the chunk already exists
if ( chunks.ContainsKey ( chunkPosition ) ) {
// Updates the chunk
chunks[chunkPosition].UpdateChunk ( renderDistance, chunkSize, playerPosition );
} else {
// Creates a new chunk
Chunk newChunk = new Chunk ( chunkPosition, chunkSize );
newChunk.UpdateChunk ( renderDistance, chunkSize, playerPosition );
chunks.Add ( chunkPosition, newChunk );
}
}
}
}
// Returns the position of the chunk the player is currently in
private Vector3 GetPlayerChunk () {
// Loops through each chunk
foreach ( Chunk chunk in chunks.Values ) {
// Terribly inefficient i know I'll fix it right after the chunk loading problem
if ( chunk.IsPlayerInChunk ( player.GetPosition () ) ) {
return chunk.GetPosition ();
}
}
return new Vector3 ( 0, 0, 0 );
}
}
Chunk.cs (this script defines the chunk gameobject
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Chunk {
private Vector3 position;
private GameObject chunkObject;
private Bounds chunkBounds;
private int size;
private MeshFilter chunkFilter;
private MeshRenderer chunkRenderer;
private Mesh chunkMesh;
public Chunk ( Vector3 position_, int size_ ) {
position = position_;
size = size_;
// Creates the new "Chunk" gameobject
chunkObject = new GameObject ( "Chunk" );
chunkObject.transform.position = position;
// Gets the mesh components
chunkFilter = chunkObject.AddComponent<MeshFilter> ();
chunkRenderer = chunkObject.AddComponent<MeshRenderer> ();
// Generates and displays the chunk's mesh
chunkMesh = GenerateMesh ();
DisplayMesh ();
// Creates the chunk bounds
// Used to tell whether or not the chunk should be rendered
chunkBounds = new Bounds ( chunkFilter.mesh.bounds.center, new Vector3 ( chunkFilter.mesh.bounds.size.x, 300, chunkFilter.mesh.bounds.size.z ) );
SetVisibility ( false );
}
// Generates the chunk mesh
private Mesh GenerateMesh () {
MeshData meshData = new MeshData ( size );
// Loops through each vertex
int vertexIndex = 0;
for ( int x = 0; x < size; x++ ) {
for ( int z = 0; z < size; z++ ) {
// Adds a vertex to the mesh
meshData.vertices[vertexIndex] = new Vector3 ( position.x + x, 0, position.z + z );
// Adds a UV value so that a texture can later be applied
meshData.uvs[vertexIndex] = new Vector2 ( x / (float) size, z / (float) size );
// Ignores the bottom and right vertices
if ( x < size - 1 && z < size - 1 ) {
meshData.CreateTriangle ( vertexIndex, vertexIndex + size + 1, vertexIndex + size );
meshData.CreateTriangle ( vertexIndex + size + 1, vertexIndex, vertexIndex + 1 );
}
vertexIndex++;
}
}
return meshData.CreateMesh ();
}
// Updates the mesh normals and bounds
private void UpdateMesh () {
chunkMesh.RecalculateBounds ();
chunkMesh.RecalculateNormals ();
}
// Displays the mesh
private void DisplayMesh () {
// Applies mesh to filter
chunkFilter.sharedMesh = chunkMesh;
// Generates mesh texture
Texture2D meshTexture = TextureHandler.TextureFromHeightMap ( size );
// Applies texture to renderer
Material chunkMaterial = new Material ( Shader.Find ( "Standard" ) );
chunkMaterial.mainTexture = meshTexture;
chunkRenderer.material = chunkMaterial;
// Updates the mesh normals to render lighting correctly
UpdateMesh ();
}
// Sets whether or not the chunk should be rendered
public void SetVisibility ( bool visible ) {
chunkObject.SetActive (visible);
}
// Updates whether or not the chunk will be rendered based on the given render distance
public void UpdateChunk ( int renderDistance, int chunkSize, Vector3 playerPosition ) {
float playerDistance = Mathf.Sqrt ( chunkBounds.SqrDistance ( playerPosition ) );
SetVisibility ( playerDistance <= renderDistance * chunkSize );
}
// Returns whether or not the player is currently in the chunk
public bool IsPlayerInChunk ( Vector3 playerPosition ) {
return chunkBounds.Contains ( playerPosition );
}
// Returns whether or not the chunk will be rendered
public bool IsVisible () {
return chunkObject.activeSelf;
}
// Returns a vector with the chunk's position
public Vector3 GetPosition () {
return chunkObject.transform.position;
}
}
// This class contains all the data to generate the chunk mesh
public class MeshData {
public Vector3[] vertices;
public int[] triangles;
public Vector2[] uvs;
int triangleIndex;
public MeshData ( int size ) {
size += 1;
vertices = new Vector3[size * size];
triangles = new int[( size - 1 ) * ( size - 1 ) * 6];
uvs = new Vector2[size * size];
}
// Adds a triangle to the mesh
public void CreateTriangle ( int x, int y, int z ) {
triangles[triangleIndex] = x;
triangles[triangleIndex + 1] = y;
triangles[triangleIndex + 2] = z;
triangleIndex += 3;
}
// Returns the current mesh
public Mesh CreateMesh () {
Mesh mesh = new Mesh ();
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.uv = uvs;
mesh.RecalculateBounds ();
mesh.RecalculateNormals ();
return mesh;
}
}
Answer by andrew-lukasik · Jul 06, 2021 at 10:37 PM
Add these lines in your ChunkHandler
and you will immediately see what is wrong:
#if UNITY_EDITOR
void OnDrawGizmos ()
{
foreach( Chunk chunk in chunks.Values )
{
Gizmos.color = Color.HSVToRGB( Mathf.Abs((float)chunk.GetHashCode()%17f)/17f , 1 , 1 );
Gizmos.DrawLine( chunk.position , chunk.chunkBounds.center );
Gizmos.DrawWireCube( chunk.chunkBounds.center , chunk.chunkBounds.size );
}
}
#endif
Your GetPlayerChunk()
method rely on these bounds:
Your answer
Follow this Question
Related Questions
Multiple Cars not working 1 Answer
Big problem on chunk generation (Unity Tilemaps) 1 Answer
Click and drag box not working 1 Answer
Help me finish my room generator 0 Answers
Can someone help me with procedural voxel terrain generation? 0 Answers