2
\$\begingroup\$

In an item system I'm trying to create I'm using prefabs to store an item's default values, and then grab that item from the prefab to use in runtime. It works well, but I was wondering if there was a way to dynamically change a variable with a base type to a derived type, probably with a custom editor, so that I wouldn't have to make separate classes for each derived type. This is the how I'm imagining it would be like:

public class ItemPrefab : Monobehaviour {
    public ItemTypes itemType;
    public Item item;
}

public enum ItemTypes {
    meleeWeapon,
    rangedWeapon
}

Item is an abstract base class, and MeleeWeapon and RangedWeapon are serializable derivatives. With that, I'm trying to do something to the effect of this:

switch (itemType) {
    case ItemTypes.meleeWeapon:
        item = item as MeleeWeapon;
        break;
    case ItemTypes.rangedWeapon:
        item = item as RangedWeapon;
        break;
}

I was wanting it to be the same as the below, just less scripts.

public class MeleeWeaponItemPrefab : Monobehaviour {
    public MeleeWeapon item;
}

public class RangedWeaponItemPrefab : Monobehaviour {
    public RangedWeapon item;
}

Edit: Here is my item system so far (I just included MeleeWeapon because I didn't want this to get too long).

public abstract class Item {

    [Header("Basic Info")]
    [SerializeField] private string name = "";
    [SerializeField] private Image icon = null;
    [SerializeField] private GameObject itemPrefab = null; 
    [SerializeField] private float weight = 0;
    [SerializeField] private int maxStack = 0; 
    private Guid gUID;

    public string Name => name;
    public Image Icon => icon;
    public GameObject ItemPrefab => itemPrefab;
    public float Weight => weight;
    public int MaxStack => maxStack;
    public Guid GUID { get { return gUID; } private set { gUID = Guid.NewGuid(); } }
}

public abstract class Weapon : Item {
    public abstract void Attack();
}

[System.Serializable]
public class MeleeWeapon : Weapon {

    [Header("Weapon Attributes")]
    [SerializeField] private MeleeWeaponTypes weaponType = default;
    [SerializeField] private float damage = 0;
    [SerializeField] private float chargeDamage = 0;
    [SerializeField] private float meleeSpeed = 0;
    [SerializeField] private float chargeTime = 0;
    [SerializeField] private float chargeCooldown = 0;

    public MeleeWeaponTypes WeaponType => weaponType;
    public float Damage => damage;
    public float ChargeDamage => chargeDamage;
    public float MeleeSpeed => meleeSpeed;
    public float ChargeTime => chargeTime;
    public float ChargeCooldown => chargeCooldown;

    public override void Attack() {
        // attack
    }
}

I have other stuff in Weapon and its derivatives but I removed it to keep the base idea more simple. Anyway, I then have prefabs with a MonoBehaviour that hold a derived type of Item, like I showed above, as data containers that I use to instantiate items during runtime.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Polymorphic serialization in Unity is pretty fraught. You're usually better off using Composition Over Inheritance, where your item can have a ranged component attached to it, or a melee component, or heck, maybe even both (say a magic sword that can also shoot fireballs, or a rifle with a bayonet attached), rather than being one or the other in a rigid inheritance hierarchy. If you show us how you're using your items atm, we may be able to help you adapt it for this style. \$\endgroup\$
    – DMGregory
    Jul 27, 2020 at 12:08
  • \$\begingroup\$ @DMGregory I'm using base c# classes for my items so that I can use them as-is in an inventory system without any conversion, and I'm using an attachment system similar to the component examples you were talking about. I'm not sure if I'd want to change my system again at this point, I just wanted to know if there was a way to implement polymorphic serialization for convenience purposes, but I appreciate the suggestion and I'll definitely look into it. Anyway, I attached part of my code as an example, so just let me know if you need anything else for context. \$\endgroup\$
    – Genzou
    Jul 27, 2020 at 12:52

1 Answer 1

1
\$\begingroup\$

First, your Item class should probably extend ScriptableObject so you can define your weapons in asset files.

There are two approaches I use for situations like this, both which involve generics.

Approach 1: Use a generic base class

public class WeaponItemPrefab<T> : Monobehaviour where T : Weapon {
    [SerializeField] private T item;
    public T Item => item;
} 

public class MeleeWeaponItemPrefab : WeaponItemPrefab<MeleeWeapon> { }
public class RangedWeaponItemPrefab : WeaponItemPrefab<RangedWeapon> { }

Approach 2: Create a service class for retrieving the weapon types

public class WeaponPrefabService : MonoBehaviour {
    [SerializeField] private List<Weapon> weapons; //filled in from Inspector

    public T GetWeapon<T>() where T : Weapon {
        foreach (Weapon weapon in weapons) {
            if (weapon is T) {
                return weapon as T;
            }
        }
        return null;
    }
}

Note that if you have a large list of weapons and call the GetWeapon() function frequently, it might be better for performance to sort the weapons into a Dictionary:

private Dictionary<Type, Weapon> weaponsDictionary;

public void Start() {
    weaponsDictionary = new Dictionary<Type, Weapon>();
    foreach (var weapon in weapons) {
        weaponsDictionary.Add(weapon.GetType(), weapon);
    }
}

public T GetWeapon<T>() where T : Weapon {
    T weapon = null;
    weaponsDictionary.TryGetValue(typeof(T), out weapon);
    return weapon;
}

\$\endgroup\$
1
  • \$\begingroup\$ I initially used ScriptableObject for my items but I've since added functionality for attachments which change weapon stats. This unfortunately doesn't work with SOs because it would affect every instance of that weapon. While I still have to create multiple scripts for each item type in your example, generics seems like the way to go for avoiding code repetition so I'll implement that, thanks for the suggestion. Also, I spawn items randomly from an ItemGeneration singleton class, but that seems like a good system so I'll remember it for games I make in the future. \$\endgroup\$
    – Genzou
    Jul 28, 2020 at 8:52

You must log in to answer this question.

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