1

I am currently programming a game in which an infinite procedural city is generated. so far everything works but because it leads to laggs if there are too many objects in the scene I wanted to make a script in which objects only appear near the player. I watched this video for help:https://www.youtube.com/watch?v=xlSkYjiE-Ck. When I tried to link this to my script (GenerateBuilding script) this error came:ArgumentException:

An item with the same key has already been added. Key: (0.0, 1.0) System.Collections.Generic.Dictionary...

I need help to make the script work in which the houses are generated as well as the planes do, they should only be showed when the player is nearby ---Relevant Lines--- (Endless City)

calling updateChunk function in update()(updateChunk/building function is in GenerateBuilding script)

public void UpdateBuildings()
{
    for (int i = 0; i < buildingObjects.Count; i++)
    {
        buildingObjects[i].SetVisible(false);
    }
    buildingObjects.Clear();
}

adding to dictionary line 68-80(UpdateVisibleChunks function)

if (building.cityChunkDictionary.ContainsKey(viewedChunkCoord))
{
    building.cityChunkDictionary[viewedChunkCoord].UpdateCityChunk(viewerPosition, viewedChunkCoord, chunkSize, maxViewDst);
    if (building.cityChunkDictionary[viewedChunkCoord].IsVisible())
    {
        building.buildingObjects.Add(building.cityChunkDictionary[viewedChunkCoord]);
    }
}
else
{
    building.AddTest(viewedChunkCoord, chunkSize);
}

EndlessCity, CityChunk class

CityChunk function, sending position to GenerateBuilding script to instantiate buildings in right position.

building.requestBuildingSquad(positionV3);

GenerateBuilding relevant lines builderH function, instantiates the buildings

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

AddTest function, adds instantiates objects from builderH to a dictionary

public void AddTest(Vector2 viewedChunkCoord, float chunkSize)
{
    for (int i = 0; i < objectsss.Count; i++)
    {
        cityChunkDictionary.Add(viewedChunkCoord, new Testing(objectsss[i]));
    }
}

Testing class, testing function, adds objects to class

public Testing(GameObject obj)
{
    MeshObject = obj;
}

that should be all relevant lines

full scripts(really similar)

EndlessCity Script(this scripts generates the planes and gives position for GenerateBuilding script)

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

public class EndlessCity : MonoBehaviour
{

    public const float maxViewDst = 10;
    public Transform viewer;

    private GenerateBuilding building;

    public static Vector2 viewerPosition;
    int chunkSize;
    int chunksVisibleInViewDst;

    Dictionary<Vector2, CityChunk> terrainChunkDictionary = new Dictionary<Vector2, CityChunk>();
    List<CityChunk> terrainChunksVisibleLastUpdate = new List<CityChunk>();

    void Start()
    {
        chunkSize = 8 - 1;
        chunksVisibleInViewDst = Mathf.RoundToInt(maxViewDst / chunkSize);
    }

    void Update()
    {
        
        viewerPosition = new Vector2(viewer.position.x, viewer.position.z);
        UpdateVisibleChunks();
    }

    void UpdateVisibleChunks()
    {
        building = FindObjectOfType<GenerateBuilding>();

        building.UpdateBuildings();

        for (int i = 0; i < terrainChunksVisibleLastUpdate.Count; i++)
        {
            terrainChunksVisibleLastUpdate[i].SetVisible(false);
        }
        terrainChunksVisibleLastUpdate.Clear();

        int currentChunkCoordX = Mathf.RoundToInt(viewerPosition.x / chunkSize);
        int currentChunkCoordY = Mathf.RoundToInt(viewerPosition.y / chunkSize);

        for (int yOffset = -chunksVisibleInViewDst; yOffset <= chunksVisibleInViewDst; yOffset++)
        {
            for (int xOffset = -chunksVisibleInViewDst; xOffset <= chunksVisibleInViewDst; xOffset++)
            {
                Vector2 viewedChunkCoord = new Vector2(currentChunkCoordX + xOffset, currentChunkCoordY + yOffset);

                if (terrainChunkDictionary.ContainsKey(viewedChunkCoord))
                {
                    terrainChunkDictionary[viewedChunkCoord].UpdateTerrainChunk();
                    if (terrainChunkDictionary[viewedChunkCoord].IsVisible())
                    {
                        terrainChunksVisibleLastUpdate.Add(terrainChunkDictionary[viewedChunkCoord]);
                    }
                }
                else
                {
                    terrainChunkDictionary.Add(viewedChunkCoord, new CityChunk(viewedChunkCoord, chunkSize, transform));
                }
                if (building.cityChunkDictionary.ContainsKey(viewedChunkCoord))
                {
                    building.cityChunkDictionary[viewedChunkCoord].UpdateCityChunk(viewerPosition, viewedChunkCoord, chunkSize, maxViewDst);
                    if (building.cityChunkDictionary[viewedChunkCoord].IsVisible())
                    {
                        building.buildingObjects.Add(building.cityChunkDictionary[viewedChunkCoord]);

                    }
                }
                else
                {
                    building.AddTest(viewedChunkCoord, chunkSize);

                }

            }
        }
    }
    public class CityChunk
    {
        private GenerateBuilding building;

        public GameObject meshObject;

        public Vector3 positionV3;

        Vector2 position;
        Bounds bounds;


        public CityChunk(Vector2 coord, int size, Transform parent)
        {
            building = FindObjectOfType<GenerateBuilding>();
            position = coord * size;
            bounds = new Bounds(position, Vector2.one * size);
            positionV3 = new Vector3(position.x, 0, position.y);

            int xPosition = building.xLength / 2;
            int zPosition = building.zLength / 2;

            float xOfsset = building.xOfsset;
            float zOfsset = building.zOfsset;

            float spaceBetween = building.spaceBetween;
            //Instantiate plane
            meshObject = Instantiate(building.groundObject, positionV3 + new Vector3((xPosition + xOfsset) * spaceBetween, -.5f, (xPosition + 1 + zOfsset) * spaceBetween), Quaternion.identity);
            SetVisible(false);
            building.requestBuildingSquad(positionV3);
        }

        public void UpdateTerrainChunk()
        {
            float viewerDstFromNearestEdge = Mathf.Sqrt(bounds.SqrDistance(viewerPosition));
            bool visible = viewerDstFromNearestEdge <= maxViewDst;
            SetVisible(visible);
        }

        public void SetVisible(bool visible)
        {
            meshObject.SetActive(visible);
        }

        public bool IsVisible()
        {
            return meshObject.activeSelf;
        }

    }
}

GenerateBuilding(this script generates Buildings on the planes)

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

public class GenerateBuilding : MonoBehaviour
{
    public int minHeight = 2;
    public int maxHeight = 8;

    public int cubeTileX;
    public int cubeTileZ;

    public int xLength;
    public int zLength;

    public float spaceBetween;

    public float xOfsset;
    public float zOfsset;

    public GameObject TesObject;

    public GameObject[] Base;
    public GameObject[] secondB;
    public GameObject[] roof;

    public GameObject groundObject;

    public List<GameObject> objectsss;

    public Dictionary<Vector2, Testing> cityChunkDictionary = new Dictionary<Vector2, Testing>();
    public List<Testing> buildingObjects = new List<Testing>();

    public GameObject Test;
    void Start()
    {
        //requestBuildingSquad(this.transform.position);
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.I))
        {
            //
        }
    }
    public void requestBuildingSquad(Vector3 position)
    {
        //*getting the middle of the city squad
        int xPosition = xLength / 2;
        int zPosition = zLength / 2;
        //*
        for (int z = 0; z < zLength; z++)
        {
            zOfsset++;
            for (int x = 0; x < xLength; x++)
            {
                GenerateBuildings(position);
            }
            xOfsset = 0;
        }
        zOfsset = 0;
    }
    public void GenerateBuildings(Vector3 position)
    {
        int bHeight = Random.Range(minHeight, maxHeight);
        float bOfsset = 0;
        bOfsset += builderH(Base, bOfsset, position);

        for (int i = 0; i < bHeight; i++)
        {
            bOfsset += builderH(secondB, bOfsset, position);
        }

        bOfsset += builderH(roof, bOfsset, position);
        xOfsset++;
    }

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

    public void AddTest(Vector2 viewedChunkCoord, float chunkSize)
    {
        for (int i = 0; i < objectsss.Count; i++)
        {
            cityChunkDictionary.Add(viewedChunkCoord, new Testing(objectsss[i]));
        }
    }

    public void UpdateBuildings()
    {
        for (int i = 0; i < buildingObjects.Count; i++)
        {
            buildingObjects[i].SetVisible(false);
        }
        buildingObjects.Clear();
    }

    public class Testing
    {
        public GameObject MeshObject;

        Vector2 position;
        Bounds bounds;
        public Testing(GameObject obj)
        {
            MeshObject = obj;
        }
        public void SetVisible(bool visiblee)
        {
            MeshObject.SetActive(visiblee);
        }
        
        public bool IsVisible()
        {
            return MeshObject.activeSelf;
        }

        public void UpdateCityChunk(Vector3 viewerPosition, Vector2 coord, int size, float maxViewDst)
        {
            position = coord * size;
            bounds = new Bounds(position, Vector2.one * size);
            float viewerDstFromNearestEdge = Mathf.Sqrt(bounds.SqrDistance(viewerPosition));
            bool visible = viewerDstFromNearestEdge <= maxViewDst;
            SetVisible(visible);
        }
    }

}
Leo
  • 1,990
  • 1
  • 13
  • 20
Armandas
  • 11
  • 1
  • 4
  • That's a lot of code. Can you show us only the relevants parts? – Cid Aug 24 '21 at 16:17
  • 1
    More specific exception would be extremely helpful; file and line number are 90% of it, a full stack trace would make life a lot faster. – ConnieMnemonic Aug 24 '21 at 16:18
  • Well in AddTest you try adding chunks all with the same coord so whether you just wanted to add the whole array and not individual items or you need something else in that coord for each item to make the key unique – BugFinder Aug 24 '21 at 16:58

1 Answers1

0

The problem is that you are trying to add twice elements with the same key.

here is the documentation of the Add method for dictionaries, and as it states, trying to add an existing key throws an error.

You can either use the TryAdd method, which adds an item only if the key doesn't exist already in the dictionary, or update the value with the existing key as you can see here.

nir shahar
  • 328
  • 3
  • 13