0
\$\begingroup\$

I'm used to application development with frameworks like Qt, and I'm really fond of using MVC-like patterns. Now I'm diving into game dev and I have a bunch of data that I want to show the user and I'm wondering the Unity way to do that. For example, say I want a flyout menu that appears on hover of an ability that looks like this badly drawn paint image below:

enter image description here

Say I have 60+ abilities on paper that all have the following data:

  • ability art icon
  • name
  • description
  • attributes such as cooldown amount, type, modifiers, etc

I want to make it such that I can feed some system this data and it just displays the correct thing at the right time. This way when I add new abilities the system already just knows how to handle it.

My question:

What is the Unity way to:

  1. Store this data?
  2. Use this data to drive a UI element?

I've looked at scriptable objects, they seem like they will play a role here but I'm still unclear as to how that gets mapped to UI elements. I'm also guessing that scriptable objects are preferred over reading in a JSON file or data on a database? Maybe someone can confirm that. I suppose I'm asking for best practice here.

\$\endgroup\$
1
\$\begingroup\$

I generally prefer ScriptableObjects for storing data that:

  1. You want to be editable with a GUI in the Unity Editor (as they can be edited in the Inspector much like the components on a GameObject)
  2. Is determined at design time and will not change at runtime
  3. You want to be able to assign to fields in the Unity Inspector

Here's the typical strategy I'll use for this:

//Data model
public class Ability : ScriptableObject {
    [SerializeField] private string displayName;
    [SerializeField] private string description;
    //etc

    public string DisplayName => displayName;
    public string Description => description;
    //etc
}

//Component for UI view
public class AbilityPresenter : MonoBehaviour {
    [SerializeField] private Text nameText;
    [SerializeField] private Text descriptionText;
    //etc

    private Ability model;

    public Ability Model {
        get => model;
        set {
            model = value;
            Refresh();
        }
    }

    private void Refresh() {
        if (model != null) {
            nameText.text = model.DisplayName;
            descriptionText.text = model.Description;
            //etc
        }
    }
}

This is probably most analogous to the Model-View-Presenter pattern (MVP), where the AbilityPresenter component would be the presenter, and a corresponding UI prefab would be the view.

If you'd also like to display your data in list form and are looking for a way to save some time, I sell a package on the Unity Asset Store that uses the above strategy for displaying sets of data in a list. In this package, you define a data model (which can be any struct or class) and view component very similar to the above, and a corresponding UI prefab. Then you can simply pass sets of data into the list controller and it will automatically instantiate view instances from the prefab and feed them the data models

EDIT: If you need to display a combination of static and dynamic data, one way is to wrap the static data in a container:

public interface IWeapon {
    string DisplayName { get; }
    float Damage { get; }
}

public class Weapon : ScriptableObject, IWeapon {
    [SerializeField] private string displayName;
    [SerializeField] private float damage = 10;
    //etc

    public string DisplayName => displayName;
    public float Damage => damage;
    //etc
}

public class DamageScaledWeapon : IWeapon {
    private Weapon weapon;
    private float damageMultiplier = 1;

    public string DisplayName => weapon.DisplayName;
    public float Damage => weapon.Damage * damageMultiplier;
    public float DamageMultiplier { get => damageMultiplier; set => damageMultiplier = value; }
    //etc

    public DamageScaledWeapon(Weapon weapon) {
        this.weapon = weapon;
    }
}

//Can display a Weapon or a DamageScaledWeapon 
public class WeaponPresenter : MonoBehaviour {
    [SerializeField] private Text nameText;
    [SerializeField] private Text damageText;
    //etc    

    private IWeapon model;

    public IWeapon Model {
        get => model;
        set {
            model = value;
            Refresh();
        }
    }

    private void Refresh() {
        if (model != null) {
            nameText.text = model.DisplayName;
            damageText.text = model.Damage.ToString("N1");
            //etc
        }
    }
}
\$\endgroup\$
4
  • \$\begingroup\$ Interesting! This makes a lot of sense. I am buying most of my art from the asset store but since I'm a software engineer my goal is to get my hands dirty in the code, so for stuff like this I feel its valuable for me to figure out. Your solution looks great though! How would you go about storing damage values? Say your description has something like "Does x damage for 2 turns". Would you store that in a JSON file separate from the data in the scriptable object? Or keep it all in one place? \$\endgroup\$ Aug 19 '20 at 5:07
  • \$\begingroup\$ I suppose I could rephrase the question as, this solution is great for static data. But what about data that could be modified? For example damage might be reduced so it does 2 rather than 4. are scriptable objects a bad candidate for data like this? \$\endgroup\$ Aug 19 '20 at 5:33
  • \$\begingroup\$ Unless your dynamic data is being sent out over a network, there's no point in converting it to JSON. As far as how to display it, you can always wrap the static data in a container that tracks modifier values. I'll update my answer. \$\endgroup\$
    – Kevin
    Aug 19 '20 at 7:11
  • \$\begingroup\$ @JoshSanders Updated with example \$\endgroup\$
    – Kevin
    Aug 19 '20 at 7:23

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .