You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity中基于Perlin噪声的GameObject集群生成异常问题

问题:Unity中Perlin噪声偏移后GameObject生成模式不匹配

我正在制作一款程序化生成游戏,尝试在Unity中使用Perlin噪声作为模板生成2D预制件的森林/集群。核心方案是生成Perlin噪声,利用噪声的暗色调区域生成类似森林的GameObject集群。

噪声生成时带有offsetX和offsetY参数,取值为0到999999之间的随机数,用于在范围内随机偏移噪声图案。当前噪声显示在Quad上,仅用于观察生成点与噪声的关联,最终会隐藏噪声只保留生成的GameObject。

核心问题:技术上功能可运行,但GameObject始终基于offsetX=0、offsetY=0时的噪声图案生成;每次噪声图案是随机变化的,但生成的GameObject模式却未随之改变——RandomObjectSpawner脚本未正确使用offsetX和offsetY来匹配对应的噪声“帧”(例如当offsetX=28904、offsetY=98201时,生成点仍和offset为0时的模式完全一致)。

现象说明

  • offsetX和offsetY为0时:噪声纹理与生成的植物预制件位置完全对应
  • offsetX和offsetY随机时:噪声纹理已呈现随机图案,但植物预制件的生成模式几乎和offset为0时一致

Perlin噪声生成代码

public class PerlinNoise : MonoBehaviour
{
// 纹理尺寸
public int width = 256;
public int height = 256;

// 控制Perlin噪声的缩放与偏移参数
public float scale = 20f;
public float offsetX = 0f;
public float offsetY = 0f;

void Start()
{
    // 随机化偏移值以生成不同噪声图案
    offsetX = Random.Range(0f, 999999f);
    offsetY = Random.Range(0f, 999999f);
}

void Update()
{
    // 获取挂载对象的Renderer组件
    Renderer renderer = GetComponent<Renderer>();
    // 将生成的纹理应用到对象材质
    renderer.material.mainTexture = GenerateTexture();
}

// 将GenerateTexture方法设为公开
public Texture2D GenerateTexture()
{
    // 创建指定尺寸的新2D纹理
    Texture2D texture = new Texture2D(width, height);

    // 为纹理生成Perlin噪声图
    for (int x = 0; x < width; x++)
    {
        for (int y = 0; y < height; y++)
        {
            // 使用Perlin噪声计算当前像素颜色
            Color color = CalculateColor(x, y);
            // 设置纹理中该像素的颜色
            texture.SetPixel(x, y, color);
        }
    }
    // 应用纹理修改
    texture.Apply();
    return texture;
}

Color CalculateColor(int x, int y)
{
    // 计算纹理内的归一化坐标
    float xCoord = (float)x / width * scale + offsetX;
    float yCoord = (float)y / height * scale + offsetY;

    // 在指定坐标生成Perlin噪声值
    float sample = Mathf.PerlinNoise(xCoord, yCoord);

    // 使用噪声值创建灰度颜色
    return new Color(sample, sample, sample);
}
}

GameObject生成代码

public class RandomObjectSpawner : MonoBehaviour
{
// 在暗色调区域实例化的预制件
public GameObject prefabToInstantiate;

// PerlinNoise脚本引用
public PerlinNoise perlinNoiseScript;

// 判断暗色调的阈值
public float threshold = 0.4f;

// 生成对象的数量
public int numberOfObjects = 10;

void Start() {
    // 确保Inspector中已赋值PerlinNoise脚本
    if (perlinNoiseScript == null) {
        Debug.LogError("PerlinNoise script is not assigned.");
        return;
    }

    // 从PerlinNoise脚本获取offsetX和offsetY值
    float offsetX = perlinNoiseScript.offsetX;
    float offsetY = perlinNoiseScript.offsetY;

    // 生成Perlin噪声纹理
    Texture2D noiseTexture = perlinNoiseScript.GenerateTexture();

    // 计算生成纹理的宽高
    int width = noiseTexture.width;
    int height = noiseTexture.height;

    // 创建存储可用生成位置的列表
    List<Vector2Int> availableSpawnPositions = new List<Vector2Int>();

    // 遍历Perlin噪声纹理的像素,从offsetX和offsetY开始
    for (int x = (int)perlinNoiseScript.offsetX; x < width; x++) {
        for (int y = (int)perlinNoiseScript.offsetY; y < height; y++) {
            // 获取当前像素的颜色
            Color color = noiseTexture.GetPixel(x, y);

            // 检查灰度值是否低于阈值
            if (color.grayscale <= threshold) {
                // 将位置添加到可用生成位置列表
                availableSpawnPositions.Add(new Vector2Int(x, y));
            }
        }
    }

    // 打乱可用生成位置列表以实现随机性
    ShuffleList(availableSpawnPositions);

    // 从打乱后的列表中随机生成指定数量的对象
    int spawnedObjectCount = 0;
    foreach (Vector2Int spawnPosition in availableSpawnPositions) {
        if (spawnedObjectCount >= numberOfObjects)
            break;

        // 根据纹理坐标计算对象的世界位置
        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;

        // 在计算出的位置生成对象
        Instantiate(prefabToInstantiate, new Vector3(worldX, worldY, worldZ), Quaternion.identity);

        spawnedObjectCount++;
    }
}

// 打乱列表的函数
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;
    }
}
}

内容的提问来源于stack exchange,提问作者Tobias Thy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 10:56:01