List of Different Classes That All Inherit From One Class

So, I have a list (using System.Collections.Generic) of a class ‘Item’. Four classes inherit from Item: MeleeWeapon, RangedWeapon, MagicWeapon, and Armor. I use upcasting (or downcasting, not sure which one it technically is) to get the information I need fairly regularly. I’m going to do my best to explain everything so no one will need to dive into the scripts below because they are quite lengthy. I have an ID based inventory system, and each item is ‘logged’ with the Item class, or classes that inherit from Item. All of the items are kept within a class ‘Database’. I have an custom editor script for database so I can add items a bit easier. I find myself having to keep a ‘reference’ variable of the type of inherited class (MeleeWeapon, RangedWeapon, MagicWeapon, and Armor) to later up/downcsat to the right type of class because all of the items are kept in a list of ‘Item’. This list is known as ‘visibleItems’ that I modify with the editor script. In the start method of the Database class, I transfer all of the visibleItems to a Dictionary for a better numerical order. Seems more suiting for an ID based inventory system. Is there a better way to work with this? I thought that keeping this list of ‘Item’ that consists of Item and its child classes would be the best approach, but I just don’t know anymore. And, my main issue is the fact that when I transfer everything over from visibleItems to the dictionaly of Items, everything seems to become just ‘Item’ and if it was supposed to be one of the child classes it appears to forget that data.

I currently only have two items in visibleItems, and both are ‘MeleeWeapon’, but they become Item when I start the game. They forget the variables that are declared within Melee.

Hopefully no one has to dig though this too much, but Ill list the scripts I mentioned. I want to note, though, that I will not include MagicWeapon, RangedWepaon, and Armor as they are blank. I also want to note that whenever I have switch statements that go through the different sub classes, MeleeWeapon is the only one you should worry about because I have not yet begun to implement the other weapons, so those statements are blank for the most part. You can also probably ignore most of the ditor script, but maybe note how I keep track of that reference variable I mentioned and what I do with it.

I will take any tips anyone has for me as well. The editor script isnt too unorganized, but I’m a bit more lax to organization in that compared to script that actually go in the game itself.

//Item.cs
using UnityEngine;
using System.Collections.Generic;
using Inventory;

[System.Serializable]
public class Item {

	//DataBase Preference
	public int id;
	public string name;
	public int maxStack;

	public string icon;//Path to icon

	public bool handEquippable = false;//NOT ARMOR, Weapon Only
	public bool matchHandRotation = true;//Does the object always point a certain way (according to the player look direction)
	public bool lockToHand = true;//Does the object lock itself to the players hand even if the animation attempts to take it far away
	public string model;
	public string fps_equipAnimation;//Path to animation
	public string world_equipAnimation;//Path to animation

	//Reference Variables
	public ItemType type = ItemType.Generic;

	//Constructor
	public Item( ItemType iType = ItemType.Generic ) {
		type = iType;
	}
	public Item( int iId, string iName, int iMaxStack ) {
		id = iId;
		name = iName;
		maxStack = iMaxStack;
	}
	public Item( int iId, string iName, int iMaxStack, bool iHandEquippable, bool iMatchHandRotation, bool iLockToHand, ItemType iType = ItemType.Generic ) {
		id = iId;
		name = iName;
		maxStack = iMaxStack;
		handEquippable = iHandEquippable;
		matchHandRotation = iMatchHandRotation;
		lockToHand = iLockToHand;
		type = iType;
	}
}

//MeleeWeapon.cs
using UnityEngine;
using System.Collections.Generic;
using Inventory;

public class MeleeWeapon : Item {

	//Preference Variables
	public float damage;
	public float range;

	//Constructor
	public MeleeWeapon() : base( ItemType.MeleeWeapon ) {
	}

}

//Database.cs
using UnityEngine;

using System;
using System.Collections;
using System.Collections.Generic;

using Inventory;

public class Database : MonoBehaviour {

	//Static Variables
	public static Database database;
	public static Dictionary<int, Item> items = new Dictionary<int, Item>();

	//Preference Variables
	public Item[] visibleItems;

	void Start() {
		if ( database == null ) {
			database = this;
			SetupDatabase();
		} else {
			Destroy( this );
		}
	}

	private void SetupDatabase() {
		for ( int i = 0; i < visibleItems.Length; i++ ) {
			switch ( visibleItems*.type ) {*
  •   			case ItemType.Armor:*
    

_ items.Add( i, visibleItems as Armor );_
* break;*
* case ItemType.MeleeWeapon:*
_ items.Add( i, visibleItems as MeleeWeapon );
* break;
case ItemType.RangedWeapon:
items.Add( i, visibleItems as RangedWeapon );
break;
case ItemType.MagicWeapon:
items.Add( i, visibleItems as MagicWeapon );
break;
case ItemType.Generic:
items.Add( i, visibleItems );
break;
}
}
}
public static int FindItemIdByName( string name ) {
foreach ( KeyValuePair<int, Item> i in items ) {
if ( i.Value.name == name ) {
return i.Key;
}
}
return -1;
}
}
namespace Inventory {
public enum ItemType {
Generic,//Item base-class (non-eqippable)
Armor,
MeleeWeapon,
RangedWeapon,
MagicWeapon*

* }
public enum DualHandType {
SingleHandEquip,
BothHands,
LeftHandOnly,
RightHandOnly*

* }
}
//DatabaseEditor.cs*

* using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using Inventory;
[CustomEditor( typeof( Database) )]
public class DatabaseEditor : Editor {
Database db;
void OnEnable() {
db = (Database)target;
for ( int i = 0; i < db.visibleItems.Length; i++ ) {
Reininialize( i );
}
}
public override void OnInspectorGUI() {
int toRemove = -1;
for ( int i = 0; i < db.visibleItems.Length; i++ ) {
GUILayout.BeginVertical( “box” );
GUILayout.BeginVertical( “box” );
//Start:Intro*

Item item = db.visibleItems as Item;
* GUILayout.BeginHorizontal();
GUILayout.Label( "ID: " + ( i <= 9 ? “0” : “” ) + ( i <= 99 ? “0” : “” ) + i );
GUILayout.FlexibleSpace();
if ( GUILayout.Button( “Up”, GUILayout.Width( 50 ) ) )
MoveUp( i );
if ( GUILayout.Button( “Down”, GUILayout.Width( 50 ) ) )
MoveDown( i );
if ( GUILayout.Button( “Remove”, GUILayout.Width( 80 ) ) )
toRemove = i;
if ( GUILayout.Button( “Add At”, GUILayout.Width( 60 ) ) )
AddItem( i );
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label( “Name:” );
item.name = EditorGUILayout.TextField( item.name );
GUILayout.FlexibleSpace();
ItemType was = item.type;
item.type = (ItemType)EditorGUILayout.EnumPopup( “as”, item.type );
if ( was != item.type ) {
Reininialize( i );
}
GUILayout.EndHorizontal();
//End*

* GUILayout.EndVertical();
GUILayout.BeginVertical( “box” );
//Start:Generic*

* if ( item.type == ItemType.Generic ) {
GUILayout.BeginHorizontal();
GUILayout.Label( “Max stack:” );
GUILayout.FlexibleSpace();
item.maxStack = EditorGUILayout.IntField( item.maxStack );
GUILayout.EndHorizontal();
} else {
item.maxStack = 1;
}
GUILayout.BeginHorizontal();
GUILayout.Label( “Icon path:” );
GUILayout.FlexibleSpace();
item.icon = EditorGUILayout.TextField( item.icon );
GUILayout.EndHorizontal();
if ( item.type == ItemType.Generic ) {
GUILayout.BeginHorizontal();
GUILayout.Label( “Hand equippable:” );
GUILayout.FlexibleSpace();
item.handEquippable = EditorGUILayout.Toggle( item.handEquippable );
GUILayout.EndHorizontal();
} else {
item.handEquippable = true;
}
if ( item.handEquippable ) {
//item.dualHandType = (DualHandType)EditorGUILayout.EnumPopup( “Dual Hand Type:”, item.dualHandType );
GUILayout.BeginHorizontal();
GUILayout.Label( “Match hand rotation:” );
GUILayout.FlexibleSpace();
item.matchHandRotation = EditorGUILayout.Toggle( item.matchHandRotation );
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label( “Lock to hand:” );
GUILayout.FlexibleSpace();
item.lockToHand = EditorGUILayout.Toggle( item.lockToHand );
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label( “Model path:” );
GUILayout.FlexibleSpace();
item.model = EditorGUILayout.TextField( item.model );
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label( “Equip anim. path (FPS):” );
GUILayout.FlexibleSpace();_

item.fps_equipAnimation = EditorGUILayout.TextField( item.fps_equipAnimation );
_ GUILayout.EndHorizontal();
/

* GUILayout.BeginVertical( “box” );
GUILayout.Label( “FPS Animations” );_

for ( int j = 0; j < item.fps_animations.Count; j++ ) {
_ GUILayout.BeginHorizontal();
GUILayout.Label( “Anim. path (FPS):” );
GUILayout.FlexibleSpace();_

item.fps_animations[j] = EditorGUILayout.TextField( item.fps_animations[j] );
_ if ( GUILayout.Button( “X”, GUILayout.Width( 35 ) ) )_
item.fps_animations.RemoveAt( j );
_ GUILayout.EndHorizontal();
}
if ( GUILayout.Button( “Add FPS Animation” ) )_

item.fps_animations.Add( “null” );
_ GUILayout.EndVertical();
/
}

* //End*
* GUILayout.EndVertical();
if ( item.type != ItemType.Generic ) {
GUILayout.BeginVertical( “box” );
//Start:Specific*

* switch ( item.type ) {
case ItemType.Armor:
Armor armor = db.visibleItems as Armor;
GUILayout.BeginHorizontal();
GUILayout.Label( “Protection:” );
GUILayout.FlexibleSpace();
armor.protection = EditorGUILayout.IntField( armor.protection );
GUILayout.EndHorizontal();
break;
case ItemType.MeleeWeapon:
MeleeWeapon meleeWeapon = db.visibleItems as MeleeWeapon;
GUILayout.BeginHorizontal();
GUILayout.Label( “Damage:” );
GUILayout.FlexibleSpace();
meleeWeapon.damage = EditorGUILayout.FloatField( meleeWeapon.damage );
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label( “Range:” );
GUILayout.FlexibleSpace();
meleeWeapon.range = EditorGUILayout.FloatField( meleeWeapon.range );
GUILayout.EndHorizontal();
break;
case ItemType.RangedWeapon:
RangedWeapon rangedWeapon = db.visibleItems as RangedWeapon;
break;
case ItemType.MagicWeapon:
MagicWeapon magicWeapon = db.visibleItems as MagicWeapon;
break;
}
//
GUILayout.EndVertical();
}
GUILayout.EndVertical();
}
GUILayout.Space( 20 );
if ( GUILayout.Button( “Add Item” ) )
AddItem( -1 );
if ( toRemove != -1 )
Remove( toRemove );
//base.OnInspectorGUI();
}
void AddItem( int location ) {
if ( location == -1 ) {
Item newArray;
if ( db.visibleItems == null ) {
newArray = new Item[1];
} else {
newArray = new Item[db.visibleItems.Length + 1];
for ( int i = 0; i < db.visibleItems.Length; i++ ) {_

newArray _= db.visibleItems;
}
}
db.visibleItems = newArray;
} else {
Item newArray = new Item[db.visibleItems.Length + 1];
for ( int i = 0; i < location + 1; i++ ) {_

newArray _= db.visibleItems;
}
newArray[location + 1] = new Item();
for ( int i = location + 1; i < newArray.Length - 1; i++ ) {
newArray[i + 1] = db.visibleItems;
}
db.visibleItems = new Item[newArray.Length];
for ( int i = 0; i < newArray.Length; i++ ) {_

db.visibleItems _= newArray;
}
}
}
void MoveUp( int index ) {
if ( index == 0 )
return;
Item temp = db.visibleItems[index - 1];
db.visibleItems[index - 1] = db.visibleItems[index];
db.visibleItems[index] = temp;
}
void MoveDown( int index ) {
if ( index == db.visibleItems.Length - 1 )
return;
Item temp = db.visibleItems[index + 1];
db.visibleItems[index + 1] = db.visibleItems[index];
db.visibleItems[index] = temp;
}
void Reininialize( int index ) {
Item old = db.visibleItems[index];
switch ( db.visibleItems[index].type ) {
case ItemType.Armor:
db.visibleItems[index] = new Armor();
break;
case ItemType.MeleeWeapon:
db.visibleItems[index] = new MeleeWeapon();
break;
case ItemType.RangedWeapon:
db.visibleItems[index] = new RangedWeapon();
break;
case ItemType.MagicWeapon:
db.visibleItems[index] = new MagicWeapon();
break;
case ItemType.Generic:
db.visibleItems[index] = new Item();
break;
}
db.visibleItems[index].id = old.id;
db.visibleItems[index].name = old.name;
db.visibleItems[index].maxStack = old.maxStack;
db.visibleItems[index].icon = old.icon;
db.visibleItems[index].handEquippable = old.handEquippable;
db.visibleItems[index].matchHandRotation = old.matchHandRotation;
db.visibleItems[index].lockToHand = old.lockToHand;
db.visibleItems[index].model = old.model;_

db.visibleItems[index].fps_equipAnimation = old.fps_equipAnimation;
_ }
void Remove( int index ) {
Item newArray = new Item[db.visibleItems.Length - 1];
for ( int i = 0; i < index; i++ ) {_

newArray _= db.visibleItems;
}
for ( int i = index; i < db.visibleItems.Length - 1; i++ ) {
newArray = db.visibleItems[i + 1];
}
db.visibleItems = new Item[newArray.Length];
for ( int i = 0; i < newArray.Length; i++ ) {_

db.visibleItems _= newArray;
}
}
}
I couldnt get it to format correctly in seperate areas, so its all together with like 10 spaces between each. It consists of Item.cs, MeleeWeapon.cs, Database.cs and DatabaseEditor.cs. The namespace ‘Inventory’ is part of the database script, located at the bottom of it. FYI.
I also want to thank anyone who looks into this. I know it a lot of code to go through. I appreciate it a whole lot, as this is the foundation of a game I am working on and I care about it very dearly. :slight_smile:
Thanks
-Michael*

PS: Going to bed, very late. I plan on checking back here early tomorrow! ~9-10hr_

Well your approach has a big problem: Unity’s seriaization system does not support inheritance for custom classes. Unity serializes custom classes based on the variable/field type. So since the type of your visibleItems array is “Item”, all instances will be of type “Item” once they got serialized / deserialized.

You would need to either use ScriptableObjects or MonoBehaviours which do support inheritance. However they are serialized as standalone assets. So if you want them to persist in your project you should save each item as asset into your project.

Custom classes are not real objects from the serialization system’s point of view. The fields of that custom class are simply “added” to the object which is serialized as asset. So a MonoBehaviour in the scene is a seperate serialized object. Custom (serializable) classes fields in that monobehaviour are serialized like fields of the MonoBehaviour. There is no type information saved. Unity recreates those custom classes based on the variable type. So all your custom class instances will be “Items” after deserialization.

If someone searching for solution check SerializeReference attribute.