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
相关产品推荐
相关产品推荐

