I’m working on randomly generating some things and would like to keep it as neat as possible. The problem I’ve run into right now is that I have a list of possible objects of a given type, and a list of types of those objects. That’s not super clear, here, let me try some code explanation.
I started with this:
public GameObject grassterr;
public GameObject dirtterr;
public GameObject pathterr;
Obviously this would get really clunky really fast if I added more options (like flat grass and hilly grass). So I moved to this:
public GameObject[] grasses;
public GameObject[] dirts;
public GameObject[] paths;
Which is great! But I want to go further still if that’s possible. Something like this:
public GameObject[][] biomes;
Except that this last one doesn’t actually work how I was hoping it would. While example #2 lets me set the size in the editor and drag as many objects as I like into the array, option #3 in fact gives me nothing at all to work with. I basically would like to be able to fetch these stored prefabs (GameObjects) from a single biomes array that has lists of possible prefabs for each biome type (grass, dirt, path, whatever).
Anyone able to point me in the right direction here?
Thanks, that’s helpful! I guess where I’m stuck at now is how do I then ‘name’ the second-tier arrays? So like, I want to be able to access biomes[“grass”] and get the list of possible grass biomes (hilly-grass, flat-grass, etc). Is there a way to do this or am I stuck with using numeric index?
Thank you for the video and all the suggestions! Using the magic words provided, this is what I’ve come up with so far:
public Dictionary<string, List<GameObject>> biomes;
Which I believe will do what I want. The only thing I would still like to know is if there is a way for me to add new biomes via the editor instead of initializing them in script? Just declaring it this way doesn’t make it show up in the editor yet in a way similar to my example #2 in the original post. Is that possible?
If that’s not possible, that would be okay. But I do want to be able to associate GameObjects with the lists. I tried this:
public Dictionary<string, List<GameObject>> biomes = new Dictionary<string, List<GameObject>>() {
{"grass", new List<GameObject>()}
};
This does not allow me to drag and drop GameObjects into the ‘grass’ list in the editor, which is what I was hoping it would do.
I admit I don’t really understand the wrapper. Is it a new class I have to place in the script I have that doesn’t delete on load? How will I access it later (ie biomes[“grass”] giving me a list of possible grass objects)? And will it allow me to create new biomes from the editor, or require a line (public GameObject[ ] forests; ) for each one?
Create a new script with the content above and put it on a suitable gameobject. The script is basically just holding your biomes data.
You can access it like you would access any other scripts. e.g, it could be as simple as having a public field and dragging the instance of the script in it.
public class IDoThingsWithBiomes : MonoBehaviour
{
public Biomes biomes;
void DoStuff ()
{
for (int i = 0; i < biomes.grasses.Length; i++)
{
biomes.grasses [i].DoSomething ();
}
}
}
It’ll require a line of code, I missed the part where you asked that, sorry.
Okay, thank you! I think I understand, and this should do everything I wanted except that one point of convenience (which is a perfectly acceptable casualty weighed against a working feature).
Sorry, one last question regarding the wrapper class.
I have everything set up and working as hoped. Now I want to reference this class. So, I’ve pulled the biome type I’m dealing with and stored it in a variable (newTerr). I then want to grab a random possibility from the list of terrain objects of that particular type, and store it as makeTerr (the terrain I’m about to create).
How do I reference the right list from the biomes now? I’ve set up an instance of random, and this is my best guess:
makeTerr = rnd.Next(biomes.newTerr.Count);
But of course, this is searching for a biome called ‘newTerr’, not the value of that variable.
What you basically wrote is that you want to use “newTerr” to refer to that instance of the class “Biomes”.
So you should refer to the class like this :
makeTerr = newTerr.biomeName[the index goes here];
//This would work
makeTerr = newTerr.grasses[Random.Range (0, newTerr.grasses.Length)
Also, note that .Count is for lists, while arrays use .Length
string[][] wilderness = new string[][] { // Set up terrain-type grid
new string[] {"grass", "dirt", "path"},
new string[] {"dirt", "path", "grass"},
new string[] {"path", "grass", "dirt"}
};
...
void OnSceneLoaded(Scene scene, LoadSceneMode mode) {
if (scene.name == "mainmenu") {
} else {
curName = scene.name;
string[] coords = curName.Split('.'); // Convert the scene name to its x and y coordinates
newx = int.Parse (coords [0]);
newy = int.Parse (coords [1]);
newTerr = wilderness[newx][newy]; // Determine the necessary biome
makeTerr = rnd.Next(biomes.newTerr.Count); // Pick a terrain object associated with our terrain type
Instantiate (makeTerr, new Vector3(0,0,0), Quaternion.identity); // Place the terrain in the new scene
}
}
So the newTerr is the biome type at the necessary coordinates in our wilderness array. It’ll be set to something like ‘grass’ or ‘dirt’ or ‘path’. So in the second to last line there, what I need it to do is pick from biomes.grass instead of biomes.newTerr, for example.
Alright I get you now.
Sucks, dictionaries would have been great here, indeed, but like @BlackPete stated, by default, they don’t show up in the inspector.
You could hardcode it. If you’re trying to fetch them by strings, you could add these lines to your Biomes class
public class Biomes : MonoBehaviour
{
public GameObject[] grasses;
public GameObject[] dirts;
public GameObject[] paths;
public Dictionary <string, GameObject[]> biomes = new Dictionary <string, GameObject[]> ();
void Start ()
{
biomes.Add ("grass", grasses);
biomes.Add ("dirt", dirts);
}
}
Then you can ref it likemakeTerr = rnd.Next(biomes.biomes[newTerr].Length);
If you want to avoid the serialization of the dictionary and try to work around it while still trying to keep the convenience you’d like to achieve, you can try the following:
In your Biomes class, which is the MonoBehaviour in this case, create a serializable class or struct. This might look like this:
public class Biomes : MonoBehaviour
{
[System.Serializable]
private struct Biome
{
// make sure the name comes first, it'll serve a special purpose (explained below)
[SerializeField]
private string _name;
[SerializeField]
private GameObject[] _prefabs;
public string Name { get { return _name; } }
public GameObject[] Prefabs { get { return _prefabs; } }
}
[SerializeField]
private Biome[] _biomes;
}
Basically, this allows to display the biome itself as a foldable item in the inspector which allows you to specify 1) the name (which you’d like to use for lookups) and 2) the array of variations for the specific biome.
Since you’ve got a serializable container now (Biome), you’re free to create an array as well(I called it _biomes) which will show up in the inspector without any issues.
Additionally, the good thing about the name being the first serializable field in the container is that whatever you’re going to set this to, the _biomes array-elements will be named accordingly, which also allows to quickly find them once the list gets longer. (The other container/wrapper achieves this by adding new members with the appropiate names).
It looks something like:
If you want to access the biomes by string now, as you originally requested, you have several options:
implement the indexer which allows to retreive the array of prefabs for a biome (syntax: … = biomes[“grass”]
a get/try, for instance biomes.GetPrefabs(“grass”) etc.
The lookup can either be done by iterating the _biomes array and comparing the name of each or using a helper-dictionary that’ll be populated in awake, whereas the key would be the name and the value the biome itself.
Pro:
No members have to be added/removed once you feel like adding/removing new biome types (pretty much what you’ve asked for).
Contra:
little more error-prone due to the use of strings (typos & you have to apply all name-changes to your code as well, though this can be easily done in one place using constants)
How often are you really going to be adding and removing biome types?
Static typing is good. Dictionaries, dynamic typing and string lookup are necessary evils in certain unfortunate situations (like runtime versioning with backward compatibility, interactions with other languages, and being surrounded by programmers who think that javascript is a decent language)
Hard code your biome types in your class. It is less error-prone, more maintainable and more efficient.
If you want to iterate over them all create a custom iterator or cache a gameObject[ ][ ].