Search code examples
c#unity-game-engine2dprocedural-generationperlin-noise

Unity Procedurally generated GameObjects does not generate in the desired way


I am trying to use Perlin noise as a pattern for generating forests / clusters of 2D prefabs in unity, for my procedurally generated game. So my plan was to generate Perlin noise, and then use the dark tones of the perlin noise to generate forest-looking clusters of game objects. The noise i generated, and has an offsetX and offsetY value, which is a random number between 0 and 999999, to randomly offset the noise generated, within the frame. The noise is being displayed in a quad, but when its finished i will only use the GameObjects alone, and not the noise under them. Right now i only have the noise visible, so that I can see the correlation between the spawn-points and the noise. So the problem is that right now, it technically works, but the GameObjects are generated based on the noise in the frame at the offsetX 0, and offsetY 0, however the noise is random every time, but not the generated gameobjects, so for some reason, the RandomObjectSpawner script doesent use the offsetX and offsetY values, to calculate which tells it which "frame" of noise it is, (e.g.) at offsetX 28904, offsetY 98201 noise seed, it would still just generate the points in the pattern of the offset 0 noise position. Example screenshots will be provided.

Here is the image of the Noise and Generated GameObjects, at offsetX 0 and offsetY 0

and here it is when the offsetX and offsetY values are random: So as you can see on picture 2, the little placeholder test-plants spawn in pretty much the same pattern as in the first image. So i need them to always spawn at the pattern of the offsetX and offsetY values that are randomly generated.

The following is my code for generating the Perlin noise: `public class PerlinNoise : MonoBehaviour { // Texture dimensions public int width = 256; public int height = 256;

// Scale and offset parameters for controlling Perlin noise
public float scale = 20f;
public float offsetX = 0f;
public float offsetY = 0f;

void Start()
{
    // Randomize the offsets to create different noise patterns
    offsetX = Random.Range(0f, 999999f);
    offsetY = Random.Range(0f, 999999f);
}

void Update()
{
    // Get the renderer component of the attached GameObject
    Renderer renderer = GetComponent<Renderer>();
    // Apply the generated texture to the object's material
    renderer.material.mainTexture = GenerateTexture();
}

// Make the GenerateTexture method public
public Texture2D GenerateTexture()
{
    // Create a new 2D texture with specified dimensions
    Texture2D texture = new Texture2D(width, height);

    // Generate a Perlin noise map for the texture
    for (int x = 0; x < width; x++)
    {
        for (int y = 0; y < height; y++)
        {
            // Calculate the color for the current pixel using Perlin noise
            Color color = CalculateColor(x, y);
            // Set the pixel's color in the texture
            texture.SetPixel(x, y, color);
        }
    }
    // Apply changes to the texture
    texture.Apply();
    return texture;
}

Color CalculateColor(int x, int y)
{
    // Calculate the normalized coordinates within the texture
    float xCoord = (float)x / width * scale + offsetX;
    float yCoord = (float)y / height * scale + offsetY;

    // Generate a Perlin noise value at the given coordinates
    float sample = Mathf.PerlinNoise(xCoord, yCoord);

    // Create a grayscale color using the Perlin noise value
    return new Color(sample, sample, sample);
}
}`

And this is my code for spawning in the Game-Objects: `public class RandomObjectSpawner : MonoBehaviour { // The prefab to instantiate on dark tones public GameObject prefabToInstantiate;

// Reference to the PerlinNoise script
public PerlinNoise perlinNoiseScript;

// Threshold to determine dark tones
public float threshold = 0.4f;

// Number of objects to spawn
public int numberOfObjects = 10;

void Start() {
    // Ensure that the PerlinNoise script is assigned in the Inspector
    if (perlinNoiseScript == null) {
        Debug.LogError("PerlinNoise script is not assigned.");
        return;
    }

    // Get the offsetX and offsetY values from the PerlinNoise script
    float offsetX = perlinNoiseScript.offsetX;
    float offsetY = perlinNoiseScript.offsetY;

    // Generate the Perlin noise texture
    Texture2D noiseTexture = perlinNoiseScript.GenerateTexture();

    // Calculate the width and height of the generated texture
    int width = noiseTexture.width;
    int height = noiseTexture.height;

    // Create a list to store available spawn positions
    List<Vector2Int> availableSpawnPositions = new List<Vector2Int>();

    // Loop through the pixels of the Perlin noise texture, starting from offsetX and offsetY
    for (int x = (int)perlinNoiseScript.offsetX; x < width; x++) {
        for (int y = (int)perlinNoiseScript.offsetY; y < height; y++) {
            // Get the color at the current pixel
            Color color = noiseTexture.GetPixel(x, y);

            // Check if the grayscale value is below the threshold
            if (color.grayscale <= threshold) {
                // Add the position to the list of available spawn positions
                availableSpawnPositions.Add(new Vector2Int(x, y));
            }
        }
    }

    // Shuffle the list of available spawn positions for randomness
    ShuffleList(availableSpawnPositions);

    // Spawn the specified number of objects randomly from the shuffled list
    int spawnedObjectCount = 0;
    foreach (Vector2Int spawnPosition in availableSpawnPositions) {
        if (spawnedObjectCount >= numberOfObjects)
            break;

        // Calculate the world position for the object based on texture coordinates
        float worldX = (spawnPosition.x - (int)perlinNoiseScript.offsetX) *         perlinNoiseScript.transform.localScale.x / width + perlinNoiseScript.transform.position.x - (perlinNoiseScript.transform.localScale.x / 2.0f);
        float worldY = (spawnPosition.y - (int)perlinNoiseScript.offsetY) * perlinNoiseScript.transform.localScale.y / height + perlinNoiseScript.transform.position.y - (perlinNoiseScript.transform.localScale.y / 2.0f);
        float worldZ = perlinNoiseScript.transform.position.z;

        // Spawn the object at the calculated position
        Instantiate(prefabToInstantiate, new Vector3(worldX, worldY, worldZ), Quaternion.identity);

        spawnedObjectCount++;
    }
}

// Function to shuffle a list
void ShuffleList<T>(List<T> list) {
    int n = list.Count;
    while (n > 1) {
        n--;
        int k = Random.Range(0, n + 1);
        T value = list[k];
        list[k] = list[n];
        list[n] = value;
    }
}
}`

Thanks in advance. :)


Solution

  • Now that you've fixed the order of execution, randomising the values in Awake, I believe the second error is in the for loop here which is why you're not seeing anything.

    // Loop through the pixels of the Perlin noise texture, starting from offsetX and offsetY
        for (int x = (int)perlinNoiseScript.offsetX; x < width; x++) {
            for (int y = (int)perlinNoiseScript.offsetY; y < height; y++) 
    

    It should simply be...

    for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++)
    

    As you're sampling from the texture to get the values, that will start from 0. Good luck with the project. :)