I can’t paste any code here atm as I’m on my work PC, but here’s a rundown of my setup and problem.
Item Hierarchy
Item → Food
Item → Weapon
Item has name and weight.
food has uses and recover.
weapon has ammo
too keep things simple. They all inherit from Item.
The Inventory
The Inventory is a List and can have items added from any subtype of Item. GetType will properly return “Food” for food items and “Item” for base items.
The Problem…
Currently the items are simply added through a GUI textbox. What I need to do is RayCast to an object and then determine what “type” of item hit.collider is hitting. How do I translate from the gameObject into my inventory without simply storing a massive script of every possible variable for every Item and subtype. To give an example of what I DON’T want…
A Food object shouldn’t store an ammo value.
A Weapon shouldn’t have recover attached to it.
And a base Item should only have name and weight.
Somehow, when this object is hit through the raycast, it needs to add it correctly to the inventory List. I’m not exactly sure how to pull this off.
Have many pick up scripts and inherit them directly from MonoBehaviour. Then each item can have it’s own value set. Have separate data classes/structs that are stored in players inventory list and now that can have fancy inheritance hierarchy. You’ll end up with some duplicate code, but that shouldn’t be that bad.
What I’m considering is creating many prefab game objects that have attribute scrips attached to them such as the tree suggests.
For each individual item (bottom of the tree ie “Can of Beans”) can I attach a script to that item with a function that constructs an object of the appropriate type on pickup.
Then it’s only a matter of List.Add(object) and using GetType to reconstruct the items.
This is what I meant. Attribute component hierarchy and separate Item hierarchy for storing. IMHO it’s not good idea to try to use MonoBehaviours anywhere where you might want custom serialization in the future, like inventory items.
public class ItemAttributes : MonoBehaviour {
public string name;
public float weight;
}
public class CanOfBeansAttributes : ItemAttributes {
public float addHp;
}
public class SomeFancySwordAttributes : ItemAttributes {
public float attack;
public float speed;
}
public class CanBePickedUp : MonoBehaviour {
public enum ItemType {
CanOfBeans, SomeFancySword
}
public ItemType itemType;
public Item GetInventoryItem()
{
var attrs = GetComponent<ItemAttributes>();
Item item;
switch(comp.itemType) {
case ItemType.CanOfBeans:
item = new CanOfBeans();
item.addHp = (attrs as CanOfBeansAttributes).addHp;
break;
// ...
}
item.name = attrs.name;
item.weight = attrs.weight;
}
}
public class Item {
public string name;
public float weight;
}
public class CanOfBeans : Item {
public float addHp;
}
public class SomeFancySword : Item {
public float attack;
public float speed;
}
void DoRaycasts()
{
RaycastHit hit;
if(Physics.Raycast(ray, out hit, pickupItemLayerMask))
{
myInventory.Add( hit.transform.GetComponent<CanBePickedUp>().GetInventoryItem() );
}
}
Awesome…so does each of these classes need to be in their own unique script? If I put all of the ItemAttributes classes into one script, only variables from class ItemAttributes shows up in the inspector. CanOfBeansAttributes do not show up.
I’m a bit rusty on my coding so have some patience
ItemAttributes, CanOfBeansAttributes, SomeFancySwordAttributes and CanBePickedUp must be in separate files named after class name because they inherit from MonoBehaviour.
Item, CanOfBeans and SomeFancySword can be anywhere. Maybe a nice place to declare them is as nested classes inside ItemAttributes, CanOfBeansAttributes and SomeFancySwordAttributes.
Raycast code would be on a behaviour attached to player somewhere.
And for an example to be complete, an item laying on ground would be a prefab instance with two components: CanOfBeansAttributes and trigger Collider.
I simplified it and used Item and Food to define Items and Food. It seems there’s no need for “ItemAttributes” separately. So right now I have these 3 simple files. Commented out the part that I am having trouble with. My ultimate goal is to be able to have a very large number of items and add/remove items with the least amount of work possible.
//Item.js
#pragma strict
public class Item extends MonoBehaviour{
var itemName : String;
}
//Food.js
#pragma strict
public class Food extends Item {
var recoverHunger: int;
}
//Attaching Item to a gameobject results in attributes name and weight showing up in the inspector.
//Attaching Food to a gameobject results in attributes name, weight, and recoverHunger showing up in the inspector.
//ItemManager.js
#pragma strict
import System.Collections.Generic;
var invList : List.<Item> = new List.<Item>();
function Start () {
}
function Update () {
if (Input.GetKeyDown ("f")) {
var ray : Ray = Camera.main.ScreenPointToRay (Input.mousePosition);
var hit : RaycastHit;
if (Physics.Raycast(ray, hit)) {
if (hit.collider.tag == "Item"){
/*Need to distinguish here between Food and Item in order to push the proper type into my List.
I cannot use "GetComponent("script_name") because Food and Item has different scripts attached to them.
Is there any way to do this without a massive number of boolean checks
If I can get it to work with these two types then it will work for anything.
-(once the item heirarchy is filled completely there will be a ton of items)*/
}
}
}
}
That’s why I had separate set of data classes for putting to inventory.
To solve this, I bought in ItemType enum for fastest check possible.
But actually I think you can also just get the GetComponent(“Item”) and then check if that is really Food component or something else. GetComponent returns subclass instance when you query for parent type.
Here’s what I ended up with. Seems to be working outside of one little nuance.
#pragma strict
import System.Collections.Generic;
var invList : List.<Item> = new List.<Item>();
function Start () {
}
function Update () {
if (Input.GetKeyDown ("f")) {
var ray : Ray = Camera.main.ScreenPointToRay (Input.mousePosition);
var hit : RaycastHit;
if (Physics.Raycast(ray, hit)) {
if (hit.collider.tag == "Item"){
if(hit.collider.name == "Food"){
var newFood : Food = new Food();
newFood.itemName = hit.collider.gameObject.GetComponent(FoodAtt).itemName;
newFood.recoverHunger = hit.collider.GetComponent(FoodAtt).recoverHunger;
invList.Add(newFood);
}
if(hit.collider.name == "Item"){
var newJunk : Item = new Item();
newJunk.itemName = hit.collider.gameObject.GetComponent(ItemAtt).itemName;
invList.Add(newJunk);
}
}
}
}
}
For imagination’s sake… “Food” or “Item” could just as easily be “Handgun” or “Can of Beans” or “Bandage” allowing me to fairly easily add new content to my item tree.
I cannot access values directly from invList. For example…if I added “Food” to the List i cannot access it by doing invList_.recoverHunger. I have to assign invList to a Food var first like…_ var testFood : Food = new Food(); and then… testFood.recoverHunger will work flawlessly Not a huge issue but it would be nicer if I didn’t have to do this. I will probably do an ennum myself but this was just to get the system running.