2

I'm having trouble figuring out how why this script won't generate chunks seamlessly next to each over based on a Perlin noise

I am quite new to procedural generation but not new to Unity and C#.

I have made this script here, It will generate chunks of terrains around the main camera and have each chunk randomly be affected by a procedurally generated height map, a similar system that can be seen in the likes of Minecraft.

Obviously, I made it this way in hopes of a system where the player could move around the world forever and generate the world around them depending on where they were with a chunk system to reduce lag and such.

The problem is that each different chunk doesn't seem to tile seamlessly the adjacent chunks. I assume this is because of my lack of understanding of Perlin noise but I can't crack down on the issue even though I swear that it should work properly when I go through the code myself.

It DOES however correctly generate the same height map with the same seed and for the same chunks in the same positions.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Generator : MonoBehaviour
{
    [SerializeField] private int seed; // Seed for random generation
    [SerializeField] private int chunkSize; // Size of chunks in Unity units
    [SerializeField] private int chunkLoadRadius; // Number of chunks to load around target
    [SerializeField] private GameObject[] objectPrefabs; // Prefabs of objects to generate
    [SerializeField] private int maxItemsPerChunk;

    private Dictionary<Vector2Int, GameObject[]> loadedChunks = new Dictionary<Vector2Int, GameObject[]>(); // Dictionary to store loaded chunks
    private Vector2Int lastChunkCoord; // Coordinates of last loaded chunk
    private Transform targetTransform; // Transform of target
    [SerializeField] private Transform cityParent;
    [SerializeField] private float depth;
    [SerializeField] private float scale;
    private void Start()
    {
        targetTransform = Camera.main.transform;
        lastChunkCoord = GetChunkCoord(targetTransform.position);
        GenerateChunksAroundTarget();
    }

    private void Update()
    {
        // Check if target has moved into a new chunk
        Vector2Int currentChunkCoord = GetChunkCoord(targetTransform.position);
        if (currentChunkCoord != lastChunkCoord)
        {
            lastChunkCoord = currentChunkCoord;
            GenerateChunksAroundTarget();
        }
    }

    private void GenerateChunksAroundTarget()
    {
        // Unload any previously loaded chunks that are outside of the chunk load radius
        List<Vector2Int> chunksToUnload = new List<Vector2Int>();
        foreach (KeyValuePair<Vector2Int, GameObject[]> loadedChunk in loadedChunks)
        {
            if (Vector2Int.Distance(loadedChunk.Key, lastChunkCoord) > chunkLoadRadius)
            {
                chunksToUnload.Add(loadedChunk.Key);
                foreach (GameObject obj in loadedChunk.Value)
                {
                    Destroy(obj);
                }
            }
        }
        foreach (Vector2Int chunkCoord in chunksToUnload)
        {
            loadedChunks.Remove(chunkCoord);
        }

        // Load any new chunks that are within the chunk load radius
        for (int x = -chunkLoadRadius; x <= chunkLoadRadius; x++)
        {
            for (int y = -chunkLoadRadius; y <= chunkLoadRadius; y++)
            {
                Vector2Int chunkCoord = lastChunkCoord + new Vector2Int(x, y);
                if (!loadedChunks.ContainsKey(chunkCoord))
                {
                    loadedChunks[chunkCoord] = GenerateChunk(chunkCoord);
                }
            }
        }
    }

    private Vector2Int GetChunkCoord(Vector3 position)
    {
        return new Vector2Int(Mathf.FloorToInt(position.x / chunkSize), Mathf.FloorToInt(position.z / chunkSize));
    }

    private GameObject[] GenerateChunk(Vector2Int chunkCoord)
    {
       
        Random.InitState(seed + chunkCoord.GetHashCode());
        GameObject[] objects = new GameObject[Random.Range(1, maxItemsPerChunk)]; // Generate between 1 and 10 objects per chunk

        Vector3 BasePos = new Vector3(chunkCoord.x * chunkSize, 0, chunkCoord.y * chunkSize);

       
        TerrainData terrainData = new TerrainData();

        terrainData.heightmapResolution = chunkSize;
        terrainData.size = new Vector3(chunkSize, depth, chunkSize);

        terrainData.SetHeights(0, 0, GenerateHeights(chunkCoord.x * chunkSize, chunkCoord.y * chunkSize, chunkCoord.GetHashCode()));

        objects[0] = Terrain.CreateTerrainGameObject(terrainData);
        objects[0].transform.position = BasePos;
        objects[0].transform.parent = cityParent;

        for (int i = 1; i < objects.Length; i++)
        {
            Vector3 objectPosition = new Vector3(
                Random.Range(chunkCoord.x * chunkSize, (chunkCoord.x + 1) * chunkSize),
                0,
                Random.Range(chunkCoord.y * chunkSize, (chunkCoord.y + 1) * chunkSize)
            );
            GameObject objectPrefab = objectPrefabs[Random.Range(0, objectPrefabs.Length)];
            objects[i] = Instantiate(objectPrefab, objectPosition, Quaternion.identity, cityParent);
        }
        return objects;
    }

    float[,] GenerateHeights(float offsetX, float offsetY, int hash)
    {
        float[,] heights = new float[chunkSize, chunkSize];
        for (int x = 0; x < chunkSize; x++)
        {
            for (int y = 0; y < chunkSize; y++)
            {
                heights[x, y] = CalculateHeight(x,  y, offsetX, offsetY, hash);

            }
        }

        return heights;
    }

    float CalculateHeight(int x, int y, float offsetX, float offsetY, int hash)
    {
        float xCoord =  (float) x / chunkSize * scale;
        float yCoord =  (float) y / chunkSize * scale;

        //Random.InitState(seed + hash);
        return Mathf.PerlinNoise(offsetX + xCoord, offsetY + yCoord);
    }
}

In order to use this script

  1. Open a new unity3d project
  2. apply this script to an empty
  3. depth 10, scale, 10, chunk size 129, radius 2
  4. add a "cityParent" transform
  5. press play and move the camera around to see how the chunks load and unload properly but the Perlin noise in each doesn't quite match up at all

I was hoping somebody who's probably much better at this than me could give me some insight on what it is that I'm doing wrong? and ideally with a modified script or a re-write of the area I had screwed up.

TyNAnd
  • 21
  • 2

0 Answers0