I’m fresh to the custom inspector but I would like to save different templates of my units I’m going to spawn for my game in Prefabs instead of JSON.
Essentially I want to be able to translate a Double Array or Dictionary & List to the grid view below to be able to modify the indexes of the collection before runtime. Each index holds a custom enum type for each of my different type of units. No need to worry about the bool and int field on the end.
I’ve tried to access the List (for rows, I used a dictionary for columns) through SerializedProperties but it returns index out of range or instance is null. Any help would be greatly appreciate, this has been bugging me for days now.
[CustomEditor (typeof (SpawnTemplate))]
public class EditorSpawnTemplate : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
base.OnInspectorGUI();
SpawnTemplate script = (SpawnTemplate)target;
EditorGUIUtility.labelWidth = 100;
for (int i = 0; i < size.intValue; i++)
{
EditorGUILayout.BeginHorizontal();
for (int x = 0; x < 6; x++)
{
EditorGUIUtility.labelWidth = 1f;
EditorGUILayout.EnumPopup(Unit.Type.BigIdleCloud);
}
EditorGUILayout.Space();
EditorGUILayout.Toggle("Instant Spawn", false);
EditorGUILayout.IntField(1);
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
serializedObject.ApplyModifiedProperties();
}
}
}
I left my many variables in the code to display what I’ve dabbled with to get this working.
public class SpawnTemplate : MonoBehaviour
{
public static SpawnTemplate Instance;
//[SerializeField] public Dictionary<int, List<Unit.Type>> templateColumn = new Dictionary<int, List<Unit.Type>>(6);
[SerializeField] public List<List<Unit.Type>> templateColumn = new List<List<Unit.Type>>(6);
[SerializeField] public List<Unit.Type> templateRow = new List<Unit.Type>(6);
[SerializeField] internal List<int> instantSpawn = new List<int>();
[SerializeField] public int size = 3;
[SerializeField] public Unit.Type[,] types = new Unit.Type[6,6];
[SerializeField] public Row[] column = new Row[6];
[SerializeField]
public class Row
{
public Unit.Type[] row = new Unit.Type[6];
}
public struct UnitInfo
{
Unit.Type type;
}
public enum Test { Test1, Test2, Test3 }
private void Awake()
{
Instance = this;
}
private void Start()
{
InitializeTemplateDictionary();
}
private void InitializeTemplateDictionary()
{
for (int i = 0; i < 6; i++)
{
List<Unit.Type> newTemplateRow = new List<Unit.Type>(6);
}
}
}
One issue is that you can’t serialize 2D arrays, so (as you have done) you can use an array of class, which in turn contains array of enum.
Alternatively, you could use a 1D array, and index by array[y* width + x]
As for accessing things in the editor, just cast target to your script type and use their reference directly.
Heres a basic setup that also handles resizing the array(s)
//MyScript.cs
public class MyScript : MonoBehaviour
{
public enum Things { a, b, c }
//Default control allows negative values = bad
[HideInInspector]
public int width = 6, height = 6;
[System.Serializable]
public class Row
{
public Things[] entries;
}
//Hide default array drawing = ugly
[HideInInspector]
public Row[] myThings;
}
//Editor/DrawScript.cs
[CustomEditor(typeof(MyScript))]
public class DrawScript : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
//Cast target to MyScript
MyScript m = (MyScript)target;
//Never let user go below 1 w/h
m.height = Mathf.Max(1, EditorGUILayout.IntField("Height:", m.height));
m.width = Mathf.Max(1, EditorGUILayout.IntField("Width:", m.width));
//Check that the array sizes match w/h values
CheckArraySizes(m);
//Draw popups
for (int i = 0; i < m.myThings.Length; i++)
{
GUILayout.BeginHorizontal();
for (int j = 0; j < m.myThings[i].entries.Length; j++)
{
m.myThings[i].entries[j] = (MyScript.Things)EditorGUILayout.EnumPopup(m.myThings[i].entries[j]);
}
GUILayout.EndHorizontal();
}
}
void CheckArraySizes(MyScript m)
{
if (m.myThings == null ||
m.myThings.Length == 0 ||
m.myThings[0] == null ||
m.myThings[0].entries.Length == 0)
{
//Create/init new array when there isn't one
m.myThings = new MyScript.Row[m.height];
for (int i = 0; i < m.myThings.Length; i++)
{
m.myThings[i] = new MyScript.Row();
m.myThings[i].entries = new MyScript.Things[m.width];
}
}
else if (m.myThings.Length != m.height)
{
//resizing number of rows
int oldHeight = m.myThings.Length;
bool growing = m.height > m.myThings.Length;
System.Array.Resize(ref m.myThings, m.height);
if (growing)
{
//Add new rows to array when growing array
for (int i = oldHeight; i < m.height; i++)
{
m.myThings[i] = new MyScript.Row();
m.myThings[i].entries = new MyScript.Things[m.width];
}
}
}
else if (m.myThings[0].entries.Length != m.width)
{
//resizing number of entries per row
for (int i = 0; i < m.myThings.Length; i++)
{
System.Array.Resize(ref m.myThings[i].entries, m.width);
}
}
}
}
Thanks for your time and the help! I plugged it into Unity and it functions exactly how I wanted it to.
Is there a way that would allow for me to not reset the enums to default in the rows that have not been removed/added when resizing the array?
edit - It saves the variables as intended, but I have to work around the input of a new number for the height. If the Array is 8 but I want to change it to 10. The amount of rows will drop to “1” because of the 1 in 10. Therefore is deleted a steady chunk of the saved enums by just changing the number. Is there any way I can delay the resizing of the array until I hit enter to confirm it?
Ah i see what you mean. Could move the resizing portion of the code (68 to 92) into a button that you have to click. Reprogramming an input field to do nothing until you press enter takes quite a bit more work
Worked perfectly hpjohn. Once again thanks for your time. You’ve helped me a great deal. I’m now going to break down your code to fuse it with my project