- Home /
C# List Reported as Empty when called in method
I'm using some dictionaries to put monsters into my grid map. I'm trying to call a method that will choose a random key from the dictionary of monster types (AvailableMonsters) by converting my dictionary keys to a list (MonsterKeys) and choosing by random index. However, whenever I reference the list of keys in the method that places the objects, I get an index out of range error on the MonsterKeys list. I have tried hard-coding the value to 0 or 1 and still get the same error, or a NullReferenceException on the list. I added a debug.log to the start() method and it reports that MonsterKeys.Count == 2, then I Debug.Log the same thing from inside the PlaceItem function and the result is 0. Code for the entire class is below:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MonsterManager : MonoBehaviour {
private List<RogueMonster> Monsters;
private List<RogueMonster> ActiveMonsters;
private List<string> MonsterKeys;
public GameObject Slime;
public GameObject Alien;
private Dictionary<string, GameObject> AvailableMonsters;
private Dictionary<string, Dictionary<string, int>> MonsterStats;
private void Awake()
{
DontDestroyOnLoad(gameObject);
Monsters = new List<RogueMonster>();
AvailableMonsters = GetAvailableMonsters();
MonsterKeys = new List<string>();
foreach (KeyValuePair<string, GameObject> kvp in AvailableMonsters)
{
MonsterKeys.Add(kvp.Key);
}
Debug.Log(MonsterKeys.Count.ToString());
Debug.Log(MonsterKeys.Count.ToString());
MonsterStats = SetMonsterStats();
}
private void Update()
{
Debug.Log(MonsterKeys.Count.ToString());
}
private Dictionary<string, GameObject> GetAvailableMonsters()
{
return new Dictionary<string, GameObject>
{
{ "Alien", Alien },
{ "Slime", Slime }
};
}
private Dictionary<string, Dictionary<string, int>> SetMonsterStats()
{
return new Dictionary<string, Dictionary<string, int>>
{
{ "Slime", new Dictionary<string,int>
{
{"hp", 2 },
{"melee", 1 },
{"magic", 0 },
{"ranged", 0 },
{"def", 0 }
}
},
{ "Alien", new Dictionary<string, int>
{
{"hp", 2 },
{"melee", 1 },
{"magic", 0 },
{"ranged", 0 },
{"def", 0 }
}
},
};
}
public void GenerateMonsters(List<Rect> rooms, int mCount)
{
foreach(Rect r in rooms)
{
PlaceMonsters(r, mCount);
}
}
private void PlaceMonsters(Rect r, int mCount)
{
int monsterCount = Random.Range(1, mCount);
for(int m = 0; m < monsterCount; m++)
{
bool placed = false;
while (!placed)
{
int x = Random.Range(r.x1, r.x2);
int y = Random.Range(r.y1, r.y2);
int i = Random.Range(0, MonsterKeys.Count);
string mType = MonsterKeys[i];
GameObject monster = Instantiate(AvailableMonsters[mType], new Vector3(x, y, 3), Quaternion.identity);
RogueMonster rm = monster.GetComponent<RogueMonster>();
Combatant c = monster.GetComponent<Combatant>();
rm.SetCombatValues(MonsterStats[mType]);
rm.Name = mType;
Monsters.Add(rm);
CombatManager.AddCombatantToList(c);
}
}
}
}
EDIT: When I move my Dictionary and List initialization the GenerateMonsters method, everything works fine. For some reason, initializing in the Awake method isn't persisting.
@hexagonius I get the error on line 89: string mType = $$anonymous$$onster$$anonymous$$eys[i];
$$anonymous$$y first thought is you are trying to call Place$$anonymous$$onsters/Generate$$anonymous$$onsters before Awake happens. I assume that you are calling it form another script? If you call it from another script in Awake method it will be called in random order until you will change Script Execution Order.
If that is the case, first you should see error in the log and then $$anonymous$$onster$$anonymous$$eys.Count == 2
@Sonky108 Generate$$anonymous$$onsters is called by the map function after the tiles are placed. The map function is called by the game manager using the Scene$$anonymous$$anager.sceneloaded event. The game loads to a main menu, with a 'start game' button, I click that to load the next scene which calls the map generation function.
Can you confirm that Awake (from referenced instance) is called before that map function? Also you marked $$anonymous$$onster$$anonymous$$anager as DonDestroyOnLoad, so are you sure that you are referencing to correct instance of the object?
See reference, you can't treat Dictionary like ordered array. See if fallowing q&a helps your issue. https://stackoverflow.com/questions/4227/accessing-a-dictionary-keys-key-through-a-numeric-index tl:dr: "Dictionary.$$anonymous$$eys does not implement a []-indexer", "mydict.$$anonymous$$eys.ElementAt(mydict.Count -1)"
@Shameness Yup, I know it's not possible to access the dictionary by index, so I am trying to get a List of the keys and accessing that list by index. The problem is that on Awake, the program reports that keys.count == 2, then when I access the same list on line 89, with keys[0] or keys[1], the index is out of range.
Answer by ShadyProductions · Jul 02, 2018 at 11:49 AM
You receive "Index out of range" so where you're trying to access MonsterKeys[i], i is actually out of the range of the list.
Over here you're missing index 0, so if you only have 1 monster you will already trigger this error. At:
Random.Range(1, MonsterKeys.Count);
In the case you have more than one monster and receive this error, your list is simply not initialized before the method is called.
On a possible fix, you could lazy load it (which isn't as great for performance because this means it will load your data during the first time it accesses the property, but it should fix this issue.). I would advise to rather find the core problem where the list is not being initialized, and fix it there.
private List<string> _monsterKeys;
private List<string> MonsterKeys
{
get { return _monsterKeys ?? (_monsterKeys = AvailableMonsters.Keys.ToList()); }
}
I changed the list initialization in the Awake() function to: $$anonymous$$onster$$anonymous$$eys = new List<string> {"Alien", "Slime"};
and still get the IndexError when calling it, even if I put in $$anonymous$$onster$$anonymous$$eys[0];
That is because you call your method BEFORE awake gets called. Add your script to the script execution order settings, and make sure this script is called before the script that calls your method. Or use the lazy method. If you are certain that awake is called before then, Perhaps it might help to post the script where you call the method. So we can see how you get the reference to your monstermanager script.
$$anonymous$$onster$$anonymous$$anager is Awake before the scene is loaded, and the scene loading is what triggers the call to Generate$$anonymous$$onsters. Changing Script Execution Order to put $$anonymous$$onster$$anonymous$$anager first doesn't fix the problem. If I initialize my dictionaries and lists in the Generate$$anonymous$$onsters method, there are no errors.
Your answer
Follow this Question
Related Questions
Sorting a List of Dictionaries in C#? 4 Answers
A node in a childnode? 1 Answer
Runtime instantiation based on XML 1 Answer
IDictionary 1 Answer
Can be list stored in dictionary? 1 Answer