How to make a dropdown of a list of database objects in a class?

Hi all, I did some searching and could not really find anything that helped me figure this out. I have a class called MoveDatabase that holds all of my moves inside a scriptable object. Moves are serialized in the inspector. Looks like this.

MoveDatabase

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "Moves", menuName = "Create Move Database/Add Database", order = 5)]
public class MoveDatabase : ScriptableObject
{
    public List<Move> Moves;
 
    //This is called when the object is created from menu
    private void Awake()
    {
        Debug.Log("We are awake");
    }

    private void OnEnable()
    {
        //Debug.Log("We are enabled");
    
        int addAmount = 0;

        foreach (Move thisMove in Moves)
        {
            //Auto increament the move ID when a new move is created
            thisMove.moveID = addAmount++;
        }   
    }
}

[System.Serializable]
public class Move
{
    public string moveName;
    public int moveID;
    public string moveDesc;
    public MoveType moveType;
    public AttackType attackType;
    public float movePower;
    public string bonusStat;
    public float moveCharges;
    public float currentMoveCharges;
    public float accuracy;
    public float statusEffectChance;
 

    public enum MoveType
    {
        Light,
        Dark,
        Psychic,
        Space,
        Time,
        Soul,
        Gravity,
        Fire,
        Grass,
        Water,
        Electric,
        Air,
        Earth,
        Ice,
        Normal,
        Metal,
        Toxic,
        None
    }

    public enum AttackType
    {
        PhysicalAttack,
        SpecialAttack,
        BoostMove
    }

 

    public Move(
        string name,
        int id,
        string desc,
        MoveType type,
        AttackType atype,
        float power,
        string bonus,
        float charges,
        float currentCharges,
        float accurate,
        float effectChance
  
        )
    {
        moveName = name;
        moveID = id;
        moveDesc = desc;
        moveType = type;
        attackType = atype;
        movePower = power;
        bonusStat = bonus;
        moveCharges = charges;
        currentMoveCharges = currentCharges;
        accuracy = accurate;
        statusEffectChance = effectChance;
   
    }

    public Move()
    {

    }

    public Move(Move i)
    {
        this.moveName = i.moveName;
        this.moveID = i.moveID;
        this.moveDesc = i.moveDesc;
        this.moveType = i.moveType;
        this.attackType = i.attackType;
        this.movePower = i.movePower;
        this.bonusStat = i.bonusStat;
        this.moveCharges = i.moveCharges;
        this.currentMoveCharges = i.currentMoveCharges;
        this.accuracy = i.accuracy;
        this.statusEffectChance = i.statusEffectChance;
    }

}

In the inspector it looks like this.

Now I have another class called Monster that’s setup the say way as the MoveDatabase. What im trying to do is add a dropdown of all the moves in the monster database that lets me pick the move I want for a specific monster in the same way a enum would allow me to. I know I can do this by making a enum inside the monster class that has all of the moves, but the problem is, if I add a move to the move database the enum in monster would need to be edited as well. Is there a way around this?

Example - MonsterMove lets me pick a move from move database

Why did you decide against making Move inherit from ScriptableObject? If Move was a ScriptableObject, you would not have this problem because you could just reference the moves themselves.

In your particular situation, I cannot think of another solution than auto-generating an enumeration definition based on your MoveDatabase and using that one in your Monster class definition.

If I made “move” inherit from “scriptableobject” wouldn’t I end up needing to make a new object per move? I figured it would be more efficient to just hold all the data inside of a single scriptableobject that I can just loop through to get the data I need. Is this not the right way?

When it comes to programming, there is rarely one best or right way. Often, one has to choose between different approaches and see what works best for one.

Yes, you would end up like that, however, you would get huge benefits from that. With ScriptableObject, you get a very nice solution for referencing non-instantiated objects like data configurations, in difference to Prefabs and other solutions requiring an instanced object.

You yourself, right now you have one database with moves.That is fine, but now you have the problem of having to reference individual moves to configurate your other database, the monster one. Effectively, you want to reference moves. ScriptableObjects allow you to do exactly that. They remove, in some instances, the need for a database because you can reference the individual moves themselves instead of looking them up in a database. They take a tad longer to create in the editor, but as you might see already, there is a huge gain from doing it this way.

If you have data that is independent from instanced objects, for instance the maximum health of a hero, with multiple heroes sharing said maximum health configuration, then ScriptableObjects are a highly usable solution for that. One configuration for maximum health of a hero, being shared by all heroes or in your case, moves, with certain moves being shared by certain monsters.

If you happen to play games by Blizzard Entertainment, their game Starcraft 2 uses a similar pattern as ScriptableObjects in their Galaxy Editor, their official modding tool for the game. It is very powerful once you get the hang out of it.

You could maybe hypothetically create a custom inspector that would grab the current list of Moves and display them in a dropdown, but that’s not your real problem. Your real problem is: when the user selects one of the Moves, how do you serialize their selection in such a way that you can save it and load it back later?

You can’t save a direct reference to a C# object, because when you quit Unity and then load your project again later, that object won’t survive the cycle, it will get destroyed and a replacement will (presumably) get created. You need some way of identifying which replacement you want.

To do that, you need to give each Move some sort of permanent unique identifier; for example, an ID number. Note that you can’t just use its position in your list, because if you add or remove stuff in your list then the position of a given Move might change.

If you change your Moves into ScriptableObjects like Ardenian suggests, then Unity will automatically create these IDs for you, so that you have a way of saving references to a specific Move. And the saving will happen automagically.

Now, you don’t have to do it Unity’s way. You could invent your own system for identifying Moves and linking them up again every time you load. You could give every Move some kind of permanent unique number or name, and your Monsters could save that number or name instead of the Move itself, and then you could have a function for looking up the appropriate Move based on that number or name.

But if you’re already using the Unity inspector to create and edit your Moves, it’s probably easier just to make them SerializableObjects. The main reason to roll your own system would be if you wanted to load your Moves from a custom data file at run time; e.g. if you have a spreadsheet of Moves that you edit outside Unity.

I actually prototyped your suggestion earlier today, and honestly prefered making the moves individual scriptable objects. Few issues I had with this.

I could not find a way in code to grab all the Moves and store them into a list. I ended up making another Scriptable Object called MoveDatabase that held all the moves in a list that I could loop through. This requires me to drag and drop the move into the database each time i make a new move.

The other issue I have with this that I really only want the Scriptable Objects data to be a reference. So lets say I have the Move Fireball. I want a monster to have this Move but the properties of this Move may change.

Move

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "New Move", menuName = "Create Move/Add Move", order = 5)]

[System.Serializable]
public class Move : ScriptableObject
{
    public string moveName;
    public string moveDesc;
    public float movePower;
    public float moveCharges;
    public float currentMoveCharges;
}

This move has been added to a monster however “currentMoveCharges” changes on this move during gameplay. Those changes then affect the original scriptable object. I dont want this. So I tried to do something like this, and I get the warning

public void TestingMonsterMoves()
{
  
foreach (Move move in moveDatabase.Moves)
    {
            testMove = new Move(move); //Gives Warning
            testMove = move; //Overwrites orginal scriptable object
            testMove = Object.Instantiate(move); //Very slow
            testMove.movePower = 50;
    }
}

I suggest that “currentMoveCharges” would properly be considered an attribute of the monster, NOT the move itself.

If a monster has a bunch of moves and you need to maintain some monster-specific data on each one, you could consider writing a second class along the lines of

[System.Serializable]
public class MonsterMove
{
    public Move moveDefinition;   // A reference to the underlying Move
    public float currentCharges;  // How many charges THIS MONSTER currently has of this move
    public float cooldown;        // etc.
}

Marking this class as Serializable means that your Monster class can have a field like public MonsterMove myMove; and you’ll be able to edit the individual sub-variables from the inspector.

1 Like

I haven’t tried this, but it looks like ScriptableObjects have an Awake method, so you could probably write your Move class so that in Awake it automatically adds itself to some globally-visible collection somewhere. (However, note that this means the order they get added might be inconsistent, so if order matters for anything you should sort them at some point.)

I think I got this working the way I want now. This will automatically add the each individual Move to the scriptable object MoveDatabase without the need to drag and drop. From this point the order they are added wont matter because this can be easily sorted if needed.

    private void OnValidate()
    {
        //Clear the list on change
        Moves.Clear();
        //Store the global unique identifiers of type Move into a array
        string[] assetGUID = AssetDatabase.FindAssets("t:Move");
        //Loop through the GUIDs
        foreach (string asset in assetGUID)
        {
            //Get the path to the asset based on the GUID
            string movePath = AssetDatabase.GUIDToAssetPath(asset);
            //Use the asset path to get the Move
            Move moveToAdd = AssetDatabase.LoadAssetAtPath<Move>(movePath);
            //Add move to database
            Moves.Add(moveToAdd);
        }
    }

This is one way to do it, yes. In my project, I also have several sets holding a bunch of ScriptableObjects, being one themselves. However, in my case, these sets are also shared by other objects. If your only problem is to have a container containing multiple ScriptableObject, then introducing a new ScriptableObject to hold them, for this only purpose, is a little overkill, in my opinion.

Instead of drag’n’dropping them, I recommend using that little button to the right of a collection element and select it from the selection window. The usability of this solution depends on your total amount of moves, of course, and you still have to go one by one move. Nonetheless, you don’t have to introduce a new object type as a result. However, if there is more that you want to do with your moves, like assigning unique information to a bunch of them, then your solution with introducing a ScriptableObject holding multiple moves is more than viable.

Using ScriptableObject, this is not possible, unfortunately. What you would need to do is introducing a so-called wrapper who contains both the move and the properties to the move being changed at runtime through various effects. ScriptableObject are only for static data, one could say, being shared by ideally more than one object.
Think about a hero in a game. He has a maximum health and a current health value. The maximum health value, or at least its base value, never changes. The current health value changes all the time. Your maximum health value, the base value for it, would be stored in a ScriptableObject, while a MonoBehavior component on your hero object keeps track of the current health, while referencing the maximum health from the ScriptableObject. This could look like this:

    public class Stat<T> : ScriptableObject
    {
        /*...*/
    }

    public class Vital: Stat<Vital> {
        public float Maximum;
    }

    public class HeroLife : MonoBehavior
    {
        public Vital health;
        public float current;
    }

With a little effort, this can become very powerful, as you can imagine, if you introduce a general Vital type, with the component wrapping it into an additional changing current value. Energy, Stamina, Damage, Regeneration, they all work roughly the same and can be easily expanded on new stats this way, only requiring you to work on one end, the ScriptableObject because the component handles the wrapping of current values themselves.

ScriptableObject are not really something to be created at runtime. Remember my rambling about wrappers to the previous quote? You could set it up like this, similar to the solution that Antistone already posted earlier:

    public class Move : ScriptableObject
    {
        public string moveName;
        public string moveDesc;
        public float movePower;
        public float moveCharges;
    }

    [System.Serializable]
    public class ActiveMove
    {
        public Move move;
        public float currentCharges;
        public float currentCooldown;
    }

    public class Skills : MonoBehaviour
    {
        public List<ActiveMove> moves;
    }

Don’t be afraid to introduce more types and decouple your code. It is neat to have everything in one place, but it will often stop you from moving onto new things.