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

Unity无限程序化城市开发Dictionary重复添加同键报错求助

问题原因

直接原因是AddTest方法中,多次向字典cityChunkDictionary添加同一个viewedChunkCoord键,C#的Dictionary不允许重复键,因此抛出重复键异常。
更深层的逻辑问题有三个:

  • 全局的objectsss列表没有按chunk隔离,每次生成新chunk的建筑都会追加到这个列表里,调用AddTest时会把所有历史生成的建筑都绑定到当前新chunk的键上,完全不符合分块管理的逻辑
  • 你在CityChunk的构造函数里就调用了requestBuildingSquad生成当前chunk的建筑,但是此时还没把这些建筑和当前chunk的坐标绑定,后续AddTest时已经混进了其他chunk的建筑
  • 单个chunk坐标对应的应该是该chunk下所有建筑的集合,而不是单个建筑对象,你的Testing类现在只存单个GameObject,逻辑不对
修复方案

第一步:修改数据结构

修改GenerateBuilding脚本中的定义,把单个Testing对应单个建筑,改成字典值存该chunk所有建筑的Testing列表,同时删掉全局的objectsss,改为在生成chunk建筑时临时存储:

// 删掉原来的public List<GameObject> objectsss;
// 修改字典定义,值为该chunk下所有建筑的Testing列表
public Dictionary<Vector2, List<Testing>> cityChunkDictionary = new Dictionary<Vector2, List<Testing>>();
public List<Testing> buildingObjects = new List<Testing>();

第二步:修改建筑生成逻辑,把生成建筑和绑定chunk的逻辑合并

删掉原来在CityChunk构造里调用requestBuildingSquad的逻辑,改到AddTest里执行,生成当前chunk的建筑同时绑定到当前chunk坐标:

// 修改后的AddTest方法
public void AddTest(Vector2 viewedChunkCoord, float chunkSize, Vector3 chunkPosition)
{
    // 临时存当前chunk生成的所有建筑
    List<GameObject> currentChunkBuildings = new List<GameObject>();
    // 生成当前chunk的建筑,把结果存到currentChunkBuildings
    requestBuildingSquad(chunkPosition, currentChunkBuildings);
    // 把当前chunk的建筑列表转成Testing列表存入字典
    List<Testing> chunkTests = new List<Testing>();
    foreach(var obj in currentChunkBuildings)
    {
        chunkTests.Add(new Testing(obj));
    }
    cityChunkDictionary.Add(viewedChunkCoord, chunkTests);
}

同时修改requestBuildingSquad和builderH方法,接收临时列表存当前chunk的建筑,不要用全局列表:

public void requestBuildingSquad(Vector3 position, List<GameObject> currentChunkBuildings)
{
    int xPosition = xLength / 2;
    int zPosition = zLength / 2;
    xOfsset = 0;
    zOfsset = 0;
    for (int z = 0; z < zLength; z++)
    {
        for (int x = 0; x < xLength; x++)
        {
            GenerateBuildings(position, currentChunkBuildings);
            xOfsset++;
        }
        xOfsset = 0;
        zOfsset++;
    }
}

public void GenerateBuildings(Vector3 position, List<GameObject> currentChunkBuildings)
{
    int bHeight = Random.Range(minHeight, maxHeight);
    float bOfsset = 0;
    bOfsset += builderH(Base, bOfsset, position, currentChunkBuildings);
    for (int i = 0; i < bHeight; i++)
    {
        bOfsset += builderH(secondB, bOfsset, position, currentChunkBuildings);
    }
    bOfsset += builderH(roof, bOfsset, position, currentChunkBuildings);
}

public float builderH(GameObject[] obj, float Height, Vector3 position, List<GameObject> currentChunkBuildings)
{
    Transform objTrans = obj[Random.Range(0, obj.Length)].transform;
    GameObject objekt = Instantiate(objTrans.gameObject, position + new Vector3(xOfsset * spaceBetween, Height, zOfsset * spaceBetween), transform.rotation);
    float height = Test.transform.localScale.y;
    currentChunkBuildings.Add(objekt);
    return height;
}

第三步:修改调用逻辑

修改EndlessCity脚本中调用AddTest的地方,传入当前chunk的位置:

else
{
    // 计算当前chunk的世界坐标
    Vector3 chunkPos = new Vector3(viewedChunkCoord.x * chunkSize, 0, viewedChunkCoord.y * chunkSize);
    building.AddTest(viewedChunkCoord, chunkSize, chunkPos);
}

同时修改CityChunk构造函数,删掉里面的building.requestBuildingSquad(positionV3);这行,避免重复生成建筑。

第四步:修改显隐逻辑

因为现在字典存的是每个chunk的建筑列表,所以更新显隐的时候要遍历列表:

if (building.cityChunkDictionary.ContainsKey(viewedChunkCoord))
{
    var chunkBuildings = building.cityChunkDictionary[viewedChunkCoord];
    foreach(var test in chunkBuildings)
    {
        test.UpdateCityChunk(viewerPosition, viewedChunkCoord, chunkSize, maxViewDst);
        if (test.IsVisible())
        {
            building.buildingObjects.Add(test);
        }
    }
}

原来的UpdateBuildings方法逻辑无需调整,保持原有遍历隐藏的逻辑即可。

额外优化建议
  • 可以对超出视野的chunk做销毁或者对象池复用,避免内存持续上涨
  • 字典写入前可以增加键存在判断,避免极端场景下重复添加报错

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 03:15:03