- Home /
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[i].type ) {
case ItemType.Armor:
items.Add( i, visibleItems[i] as Armor );
break;
case ItemType.MeleeWeapon:
items.Add( i, visibleItems[i] as MeleeWeapon );
break;
case ItemType.RangedWeapon:
items.Add( i, visibleItems[i] as RangedWeapon );
break;
case ItemType.MagicWeapon:
items.Add( i, visibleItems[i] as MagicWeapon );
break;
case ItemType.Generic:
items.Add( i, visibleItems[i] );
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[i] 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[i] 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[i] 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[i] as RangedWeapon;
break;
case ItemType.MagicWeapon:
MagicWeapon magicWeapon = db.visibleItems[i] 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[i] = db.visibleItems[i];
}
}
db.visibleItems = newArray;
} else {
Item[] newArray = new Item[db.visibleItems.Length + 1];
for ( int i = 0; i < location + 1; i++ ) {
newArray[i] = db.visibleItems[i];
}
newArray[location + 1] = new Item();
for ( int i = location + 1; i < newArray.Length - 1; i++ ) {
newArray[i + 1] = db.visibleItems[i];
}
db.visibleItems = new Item[newArray.Length];
for ( int i = 0; i < newArray.Length; i++ ) {
db.visibleItems[i] = newArray[i];
}
}
}
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[i] = db.visibleItems[i];
}
for ( int i = index; i < db.visibleItems.Length - 1; i++ ) {
newArray[i] = db.visibleItems[i + 1];
}
db.visibleItems = new Item[newArray.Length];
for ( int i = 0; i < newArray.Length; i++ ) {
db.visibleItems[i] = newArray[i];
}
}
}
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. :)
Thanks -Michael
PS: Going to bed, very late. I plan on checking back here early tomorrow! ~9-10hr
I reworked Reinitialize method:
I am not sure if it will works, i wrote it in notepad.
void Reininialize( int index ) { Item old = db.visibleItems[index];
if(old == null)
{
Debug.LogError("Index: "+index+" not found.");
return;
}
Item item = null;
switch ( db.visibleItems[index].type )
{
...
case ItemType.$$anonymous$$eleeWeapon:
// add a constructor for the additional properties in the $$anonymous$$eleeWeapon class.
item = new $$anonymous$$eleeWeapon((($$anonymous$$eleeWeapon)old).damage, (($$anonymous$$eleeWeapon)old).range);
break;
...
}
if(item != null)
{
item.id = old.id;
item.name = old.name;
item.maxStack = old.maxStack;
item.icon = old.icon;
item.handEquippable = old.handEquippable;
item.matchHandRotation = old.matchHandRotation;
item.lockToHand = old.lockToHand;
item.model = old.model;
item.fps_equipAnimation = old.fps_equipAnimation;
db.visibleItems[index] = item;
}
else
Debug.LogError("Item ID: "+old.id+" error.");
}
item = new $$anonymous$$eleeWeapon( ( ($$anonymous$$eleeWeapon)old ).damage, ( ($$anonymous$$eleeWeapon)old ).range );
InvalidCastException: Cannot cast from source type to destination type. DatabaseEditor.Reininialize (Int32 index) (at Assets/Scripts/InventorySystem/Inventory/Editor/DatabaseEditor.cs:225)
I've been trying different ways to fix that but I'm just not sure how to retrieve the data correctly to use it.
Answer by Bunny83 · Jul 09, 2016 at 05:09 PM
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.
I haven't looked into scriptable objects yet... but, will I able to create different classes of a scriptable object and store them all in a list like I was trying to with my four classes that inherit from 'Item'? If I can, they are certainly worth looking into.
Yes you can. However ScriptableObjects can't be created with "new". If you create them manually you have to use ScriptableObject.CreateInstance<YourClass>()
.
Another difference is that they won't show up in the inspector like nested fields as they are actual assets. Those have to be viewed seperately in the inspector unless you write a custom inspector / property drawer for them.
I suppose I might not really need a custom inspector for it, if I create an item one time and never really return to it because I have no need for it...
I want to thank you guys ($$anonymous$$asterio, and Bunny83) for helping me. $$anonymous$$uch appreciated :)
Your answer
Follow this Question
Related Questions
A node in a childnode? 1 Answer
Inherited member variables not set to base value c# 1 Answer
Loading component by superclass. 1 Answer
How do I Sort a List of Classes by a property? 2 Answers
Custom class always returning Null 1 Answer