[SOLVED] C# - Help Referencing Dictionaries vs Lists

So I’ve written a script that scatters objects around the terrain for me and that part works great. But I want even better! I want it to look at the type of terrain (for simple testing I’m using ‘grass’, ‘dirt’, and ‘path’) and determine what kinds of scattered objects might appear, and then scatter a random assortment of those randomly across the map.

That was going well, I got the right list of ‘scatterables’ to the function that spawns them. But then I got lost trying to fetch the right prefabs. Here are the two scripts involved, both attached to the same singleton object in my game. The first one is a wrapper that lets me store the prefabs I’ll want to spawn in later. The second one is the spawner that should be scattering those objects.

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

public class AllObjects : MonoBehaviour {

    // Here we store all possible objects, so we can attach them via the editor
    public GameObject[] cubes;
    public GameObject[] spheres;

    public Dictionary <string, GameObject[]> items = new Dictionary <string, GameObject[]> ();

    void Awake ()
    {
        items.Add ("cubes", cubes);
        items.Add ("spheres", spheres);
    }
}
using UnityEngine;
using System.Collections;

public class SpawnObjects : MonoBehaviour
{
    public Terrain terrain; // add current terrain
    public string[] objectToPlace; // list of objects to be placed on terrain
    public GameObject placingObject; // the individual object we're currently placing
    public int numberOfObjects; // number of how many objects will be created
    public int posMin; // minimum y position
    public int posMax; // maximum x position
    public bool posMaxIsTerrainHeight; // the maximum height is the terrain height
    public int numberOfPlacedObjects; // number of the plaed objects
    private int terrainWidth; // terrain size x axis
    private int terrainLength; // terrain size z axis
    private int terrainPosX; // terrain position x axis
    private int terrainPosZ; // terrain position z axis
    private float yDisplace; // how far to move the object above the terrain

    AllObjects items;

    // Create objects on the terrain with random positions
    public void PlaceObject()
    {
        items = GetComponent<AllObjects> ();
        terrainWidth = 500; // get terrain size x
        terrainLength = 500; // get terrain size z
        terrainPosX = 0; // get terrain position x
        terrainPosZ = 0; // get terrain position z
        if(posMaxIsTerrainHeight == true)
        {
            posMax = (int)terrain.terrainData.size.y;
        }

        int posx = Random.Range(terrainPosX, terrainPosX + terrainWidth); // generate random x position
        int posz = Random.Range(terrainPosZ, terrainPosZ + terrainLength); // generate random z position
        float posy = Terrain.activeTerrain.SampleHeight(new Vector3(posx, 0, posz)); // get the terrain height at the random position
        int r = Random.Range(0, objectToPlace.Length); // pick object type from scatterables list
        int i = Random.Range (0, items [objectToPlace [r]].Length); // select prefab from list of items of that type
        placingObject = items[objectToPlace[r]][i]); // set prefab as obj we're working with
        if(posy <= posMax && posy >= posMin)
        {
            yDisplace = (placingObject.GetComponent<Renderer>().bounds.size.y)/2; // get height of object so it doesn't spawn half-buried
            Instantiate(placingObject, new Vector3(posx, posy+yDisplace, posz), Quaternion.identity); // create object
            numberOfPlacedObjects++;
        }

        // numberOfPlacedObjects is smaller than numberOfObjects
        if(numberOfPlacedObjects < numberOfObjects)
        {
            PlaceObject(); // place another one
        }
    }
}

The issue is around lines 38-39 in the spawner. In this sccript, ‘objectsToPlace’ is a list of strings (it might be “cubes” or “cubes” and “spheres”). I want to randomly pick one of those object types (select “cubes”) in line 38, then randomly select from the possible prefabs of that type (items.cubes could have multiple cube prefabs, I want to pick one of them) in line 39. Line 38 works, I believe. But I’m having trouble referencing the right list in line 39. I think it might be looking for a list called “objectToPlace[r]” instead of a list called “cubes” or “spheres”.

Sorry if this isn’t super clear, I’ve only started learning unity and C# this weekend so I’m not positive I’m using the right terms for everything. But any pointers in the right direction here would be great thank you!

I think you’d be better of with a different structure of your code.
It would be much more convenient to have a container class that represents such an ‘item pool’ / ‘item provider’ e.g. for grass prefabs (I call these item provider in my projects, but those serve many different purposes depending on the implementation).
That conatiner can maintain a list (or any kind of collection - it’s really an implementation detail) of these items internally. The container might then expose a method ‘GetRandom’ so that the outside world of your class wouldn’t need to care about details, it’ll just call the method.
If you still want to be able to determine the process of randomly generating an appropriate key (e.g. for a mapping or a simple index for a list), I’d provide such generator with an overload of the method. But that’s more advanced and fancy stuff.

Either way, the current structure appears to get you into troubles sooner or later, since you define strings in multiple places. They’re defined in the objectsToPlace lists (probably in the inspector) and those strings are probably also defned in your current item container. You’d always have to make sure that everything matches perfectly, since the details are not encapsulated at the moment.

I remember that I posted in your other thread about multi-array-population in the inspector, if you head back to that thread and check my post in which I suggested another approach to the other ones, you might be able to extend the container for your needs. Perhaps that might be a good starting point to encapsulate the logic a little more.

Thanks for your help! While I didn’t use your recommendation, taking a fresh look at that thread did point me to the solution I was hoping for. I had forgotten to reference the dictionary within the AllObjects script, so it was just looking for the lists in the script, instead of in the dictionary within the script. The lines I needed were:

        int i = Random.Range (0, items.items[objectToPlace [r]].Length); // select prefab from list of items of that type
        placingObject = items.items[objectToPlace[r]][i]; // set prefab as obj we're working with

Which does exactly what I wanted! :slight_smile: