Trying to add an array with two values in a Custom Editor.

Hey, how would I make it so that in the Custom Editor both i and I are a single array that has two input fields?

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

[CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/SpawnManagerScriptableObject", order = 6)]
public class Testing : ScriptableObject
{
    public int[] i;
    public int[] I;
}

[CustomEditor(typeof(Testing))]
public class MyScriptEditor : Editor
{
    public override void OnInspectorGUI()
    {
        Testing test = (Testing)target;

    }
}

7877836–1001806–Testing.cs (480 Bytes)

When asking for help - you’re generally better off trying to articulate what problem you’re trying to solve instead of what you think the solution needs to be.

Why do you need two seperate arrays? Would a single array containing an object that references two ints work?

Cause unity already has one - Vector2Int. - but I’d still be keen to know what the actual problem is you’re trying to solve with this data structure? What’s the relationship between the data in little i and big I. What use case supports their need to be presented as a uniform structure but stored completely independently?

1 Like

I did not know Vector2Int was a thing and that will actually be helpful in the future.

But this is not actually meant to solve anything, I am just attempting to implement various things in custom editor to get a better understanding of how it works. Which is why my variables are just i and I. I was having a lot of trouble getting my head around how I would make a double array like this and figured it would be something worth learning.

Though a use case that originally inspired me looking into this is a Monster Taming Game I am working on. With this data structure being used to store int[ ] LV and Move[ ] move. With LV being the level a move can be learned at and move being the move itself.

You wouldn’t do it like this. You would make a struct with two fields, and a custom property drawer to make such data inspectable. Then you would make an array of such and such data type, like you should.

I recommend you avoid custom inspectors if at all possible unless you’re making editor store content or you’re 100% certain the shape of what you’re decorating with an inspector is not ever going to change. Otherwise you’re often just adding extra classes to maintain and keep in sync with your most likely constant changing game classes and anything that makes you not want to adjust the composition of your game code simply because you’ve built editor tools against it’s current shape should be avoided.

You might want to do some reading on data structures and relational databases. A very large part of programming is modelling relationships like MonsterLevel >> AvaliableMoveSet where you might want to model different monsters having access to some moves at earlier levels than others etc. What your describing is actually a fairly simple set of relationships to model.

a) A monster’s state can change to represent its strength as a level
b) Monster move definitions are shared among all monsters.
c) Access to moves is gated by a monsters current level.
d) Different monsters have different restrictions as to what moves they can access at what level.

That might look like this in code:

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Assertions;

public class Monster : MonoBehaviour
{
    public int level = 1;

    public Dictionary<int,HashSet<Move>> moves;
}

public class Move
{
    // declarative move model
}

If you wanted to serialize that move data and make it editable in the inspector you can wrap it up in a list and model the relationship declaratively like so:

public class Monster : MonoBehaviour
{
    public int level = 1;
    public Dictionary<int,HashSet<Move>> moves;
    [SerializeField] List<MoveGroup> moveGroup;

   // etc...
}

[Serializable] // this makes unity save the data to the scene/prefab when exposed in the inspector.
public class MoveGroup
{
    public int level;
    public List<Move> moves;
}

Then add some code in awake to map that into the dictionary:

void Awake()
{
    moves.Clear();
    foreach (var item in moveGroup)
    {
        if (moves.ContainsKey(item.level))
        {
            Debug.LogWarning($"Monster {name} has multiple move sets for level {item.level}, moves will be merged.");
            moves[item.level].UnionWith(item.moves);
        }
        else
        {
            moves.Add(item.level, new HashSet<Move>(item.moves));
        }
    }
}

You could then add some methods for access the moves:

public List<Move> GetAllValidMoves()
{
    return moves.Where(x => x.Key <= level)
                .SelectMany(x => x.Value)
                .ToList();
}

public List<Move> GetMovesForLevel(int moveLevel)
{
    Assert.IsTrue(moveLevel <= level, $"Monster {name} attempted to access moves for level {moveLevel} when it is only level {level}");
    if (moves.TryGetValue(moveLevel, out var movesForLevel))
    {
        return new List<Move>(movesForLevel); // return a copy of the list so it can't be modified
    }
    return new List<Move>(); // empty list is no moves for give level.
}
1 Like
public class MyScriptEditor : Editor
    {
        // EditorUtility.

        public override void OnInspectorGUI()
        {
            Move editor = (Move)target;

            Basics(editor);

            EditorGUILayout.Space(15);

            AbilityArray(editor);

            for (int i = 0; i < editor.effects.Length; ++i)
                SetAbility(editor, i);
        }

        private void Basics(Move editor)
        {
            //Chose Element
            editor.move_element = (Elements)EditorGUILayout.EnumPopup("Damage Element", editor.move_element);

            //Chose Cost
            MoveCost cost = editor.move_cost;

            editor.move_cost = (MoveCost)EditorGUILayout.EnumPopup("Damage Cost", editor.move_cost);

            if (editor.cost.Length == 0 || cost != editor.move_cost)
                editor.cost = new int[3];

            if (editor.move_cost.ToString().Contains("H"))
                editor.cost[0] = EditorGUILayout.IntField("HP Cost", editor.cost[0]);
            if (editor.move_cost.ToString().Contains("S"))
                editor.cost[1] = EditorGUILayout.IntField("SP Cost", editor.cost[1]);
            if (editor.move_cost.ToString().Contains("M"))
                editor.cost[2] = EditorGUILayout.IntField("MP Cost", editor.cost[2]);
        }
        private void AbilityArray(Move editor)
        {
            if (editor.effects != null)
            {
                int tempvalue = EditorGUILayout.IntField("Number of abilites", editor.effects.Length);

                if (tempvalue != editor.effects.Length)
                {
                    MoveEffectTypes[] temp = editor.effects;
                    editor.effects = new MoveEffectTypes[tempvalue];

                    for (int i = 0; i < Mathf.Min(temp.Length, editor.effects.Length); ++i)
                        editor.effects[i] = temp[i];

                    for (int i = Mathf.Min(temp.Length, editor.effects.Length); i < editor.effects.Length; ++i)
                        editor.effects[i] = new MoveEffectTypes();
                }
            }
            else
            {
                editor.effects = new MoveEffectTypes[0];
            }
        }
        private void SetAbility(Move editor, int i)
        {
            MoveAbility temp = editor.effects[i].type;
            editor.effects[i].type = (MoveAbility)EditorGUILayout.EnumPopup("Type", editor.effects[i].type);

            if (editor.effects[i].type == MoveAbility.Damage)
            {
                if (temp != MoveAbility.Damage)
                    editor.effects[i].child = new MADamage();

                ((MADamage)editor.effects[i].child).damage_type = (MoveType)EditorGUILayout.EnumPopup("DamageType", ((MADamage)editor.effects[i].child).damage_type);
                ((MADamage)editor.effects[i].child).target = (Target)EditorGUILayout.EnumPopup("Target", ((MADamage)editor.effects[i].child).target);
                ((MADamage)editor.effects[i].child).damage = EditorGUILayout.IntField("Damage", ((MADamage)editor.effects[i].child).damage);
            }
            else if (editor.effects[i].type == MoveAbility.Status)
            {
                if (temp != MoveAbility.Status)
                    editor.effects[i].child = new MAStats();
                editor.effects[i].type = MoveAbility.Status;
                ((MAStats)editor.effects[i].child).stat = (Stat)EditorGUILayout.EnumPopup("Stat", ((MAStats)editor.effects[i].child).stat);
                ((MAStats)editor.effects[i].child).target = (Target)EditorGUILayout.EnumPopup("Target", ((MAStats)editor.effects[i].child).target);
                ((MAStats)editor.effects[i].child).change = EditorGUILayout.IntField("Change", ((MAStats)editor.effects[i].child).change);
            }

            EditorGUILayout.Space();
        }
    }

So I finished coding that mess, came back on the forms, saw your code, and am once more reminded that Dictionaries are a thing. I will take your advice and try and solve my problems with better data structures. Custom Editors are just too much work for what I am trying to make.

Thanks again for the help.