I’m working on inventory system where players can pickup/drop objects on the ground/in containers.
When a player picks up an item, the item no longer needs to be visible, but its data needs to be preserved.
Here are some possibilities I have considered:
When an item is picked up, copy the data into an object, and destroy the GameObject. When an item is dropped, copy the object’s values to the script component in a newly instantiated GameObject.
Same as above, but move around the entire component instead of copying its values. (not sure if this is even possible)
When an item is picked up, move it out of the playing area and/or disable its renderer, and then when it is dropped again move it to the drop location and/or enable its renderer.
Are any of these good ways to handle this, or is there a better way?
One way to create an inventory system is to have an item database. Despite the fancy term, it is merely a list of GameObject or Components of all the items in your game. There probably will be multiple databases/ lists of items depending on how complex your game is.
public class ItemDatabase {
//list to contain all items in the game.
public static List<GameObject>() = new List<GameObject>();
}
So, what does a database do. Nothing. It simply holds data. What it allows you to do however, is to simply use a reference ID in the list to grab an object. so lets say i have the item “Gloves” in the item database at index 3. What i will do when accessing its data when i have it equiped would be something like this.
//list of items equipped, notice it is merely a list of ints representing item IDs.
List<int> equipped = new List<int>();
//to simulate an equipped item
equipped.Add(3);
//to simulate a value representing defence, as an example of a stat modified by items
float totalDef = 0;
//looping through all equipped items
for(int i = 0; i < equipped.Count; i++){
//adding defence from items to total defence
totalDef += ItemDatabase.itemList[equipped[i]].GetComponent<Stats>().def;
}
And when dropping an item, again the item ID is referenced and the item is created. The item ID from the list of equipped items on the player is simply removed, and the corresponding item in the database is instantiated as a new item.
That wouldn’t really matter. The only thing that matters is the static list which doesn’t require the class to extend from anything. In fact it would be beneficial IMO that the class does not extend from anything so that there is no extra baggage from the inherited script, in this case ScriptableObject.
ScriptableObjects allow the data to exist in the project as a “thing”. Without inheriting from any class how would you persist the data between “plays”?
Oh that can come later. Piling too many things up to worry about all at one time will simply confuse the person asking the question IMO. Although I would expect static stuff to remain between “plays” as you call it.
Thanks for reply Laperen- that doesn’t exactly answer what I was asking, but it is helpful. I was asking how to handle item visibility when picking up/dropping items, not how to store their existence and ownership. I suppose it boils down to whether its better to:
Have persistent GameObjects and hide/move them when they’re picked up (since GameObjects must exist in the scene)
or
Keep C# objects that contain the item data, and delete the GameObjects when they’re not needed.
I am thinking that both are viable, its just that 1 would be simpler and 2 would be more efficient and elegant. I was also wondering if there were any alternatives I wasn’t considering. I think 1 will work good enough for me though.
There are advantages and disadvantages to both methods. If you only have a few unique inventory items then you can go with the first. If you have hundreds of generic items run with the second.
The data base idea is sound. For robustness I would suggest accessing elements via a string or unique ID rather then an array position. Don’t want all of your potions to be replaced by poisons if you add an item to the middle of the array.
Right, one thing that has not been asked is although rather important to know, what kind of game is this going to be? I was envisioning a RPG inventory, but evidently that is not the case.
The best way that I have found to do this - in very broad terms, is to create:
Item Spawners - that are responsible for spawning items and remembering whether or not their items have been taken.
Items - that describe the items themselves, represent them through mesh/renderer, and do not save any properties (i.e. they only exist during runtime).
Item Descriptors - that the player “carries around” (i.e. simple class that describes the properties of an item).
World/Scene Inventories - collections of Item Descriptors that essentially describe items that have been dropped (i.e. they don’t belong to an Item Spawner or the player).
When I finish “The Deep Paths” and have some time, I’m going to do a series of YouTube videos on some of the methods that I use to do these common RPG type things. I think I’ve got some reasonably good stuff happening after much trial and error over the course of several games, and a great deal of blood, sweat, and tears.
Its a top down tile based simulation. There are many different characters that will each have their own inventory. For each character, the inventory system shouldn’t be too far off from an RPG.
Thanks. I think that helped me cook up something decent… all this is in the character controller code. Nothing is tested but I think I’ve got the right idea.
// pick up item from ground
public void pickUp(ItemDescriptor itemDesc)
{
inventory.Add(itemDesc);
GlobVars.items[tileCoords.y, tileCoords.x] = null; // remove item at current position from world inventory
Destroy(itemDesc.gameObject); // remove item from scene
}
// drop item from inventory onto ground
public void drop(ItemDescriptor itemDesc)
{
inventory.Remove(itemDesc);
GameObject obj = Instantiate(item, transform.position.x, transform.position.y) as GameObject; // create item prefab at character location
obj.GetComponent <ItemDescriptor>() = itemDesc; // this obviously won't work -- I'll need to copy the values from itemDesc in obj's ItemDescriptor here
GlobVars.items[tileCoords.y, tileCoords.x] = itemDesc;
}
// consume item from inventory
public void consume(ItemDescriptor itemDesc)
{
// do whatever the itemDesc specifies -- case switch statement?
Destroy(gameObject);
}
// take item from container
public void take(ItemDescriptor itemDesc, ref Container cont)
{
cont.items.Remove(itemDesc);
inventory.Add(itemDesc);
}
// put item in container
public void put(ItemDescriptor itemDesc, ref Container cont)
{
cont.items.Add(itemDesc);
inventory.Remove(itemDesc);
}
Yeah - that looks like a start. In my implementation, part of the ItemDescriptor is the Prefab name of the Item representation, so when it’s dropped, I instantiate the physical version of it using that information (by loading a Resource). The ItemSpawner does the same thing - it’s told either a specific item type that it spawns (e.g. Sword1) or a List of items that it can possibly spawn (e.g. Sword1, Dagger2, Staff1), to give the game a little variety on replays. The first time the ItemSpawner “decides” which item it’s going to spawn, it remembers that (when saving) and it will then always spawn that item from then on (unless it’s marked itself as taken). This way when the player enters a level where they previously didn’t bother to take the sword that was on the ground, they still see a sword on the ground instead of a dagger (which would be weird).
The player inventory is pretty much what you’re doing - a List of ItemDescriptors. This way you can show the information about the item in the inventory screen etc. as well as use its stats in combat and other calculations.
The world inventory (or location inventory - depending on how your game is split up) is even simpler - it’s just an ItemDescriptor-like class that simply stores the item’s Prefab name and it’s x, y, z coordinates. On world or location launch, a routine just iterates through that list and instantiates all of those items back into the world where the player left them.
I’ve been working on this a bit more over the past week and I’ve run into a bit of a problem…
Basically my design is that I have ItemBehaviour and ItemData classes, and I have items that inherit from them. The ItemBehaviours are components of the item prefabs. They have an ItemData property, so that I can take an ItemData out of a character’s inventory and attach it to the ItemBehaviour when dropping an item. So for every item, there is a data and a behaviour object. For example, a fruit has a FruitData and a FruitBehaviour object. These inherit from FoodData and FruitBehaviour, respectively. In turn, these inherit from ItemData and ItemBehaviour.
Now here’s my problem. For each ItemBehaviour there is this “data” property – it has the same name (“data”) in every class. How can I handle it so that I can access it generically?
Consider this function from my character controller class:
// drop item from inventory onto ground
public void drop(ItemData item)
{
string prefabPath = "prefabs/" + item.prefabName; // get prefab name
GameObject newItem = Instantiate(Resources.Load(prefabPath), new Vector3(transform.position.x, transform.position.y, ItemData.depth), Quaternion.identity) as GameObject; // create item prefab at character location
newItem.GetComponent<ItemBehaviour>().data = item; // attach ItemData from inventory to dropped ItemBehaviour
inventory.Remove(item); // remove ItemData from inventory
GameBoard.items[tileCoords.y, tileCoords.x] = newItem.GetComponent<ItemBehaviour>(); // put ItemBehaviour in the world inventory
}
On line 7, it is setting the ItemData in ItemBehaviour equal to “item”. Instead, I need it to set the “data” property of the specific type of item equal to “item”. For example, if I passed a FruitData to this function, it would create a FruitBehaviour. But then I need it to set it its FruitData equal to “item”, NOT set its ItemData equal to “item” (which I think is what its doing).
You shouldn’t mix item handling stuff with item type stuff. For example, should “Fruit” know about your item handling gears? If not, it should not override from it, but rather be an independent component attached to it. Also, consider switching you data/behaviour class pair to interfaces.
I’m not sure what you mean by “item handling gears”. Do mean drop(), pickup(), etc.? If so, those are in the character controller script currently. (I will move them to a separate Inventory class later, but I’m just trying to get everything working right now).
interface IItem
{
ItemData data { get; }
}
public abstract class ItemBehaviour<DataType> : MonoBehaviour, IItem
where DataType : ItemData, new()
{
ItemData IItem.data { get { return (ItemData)this.data; } }
public DataType data
{
get { return data; }
set { data = value; }
}
// other stuff
}
public abstract class FoodBehaviour<FoodType> : ItemBehaviour<FoodData>
where FoodType : FoodData, new()
{
// stuff
}
public class FruitBehaviour : FoodBehaviour<FruitData>
{
// stuff
}
public class PoopBehaviour : ItemBehaviour<PoopData>
{
// stuff
}
Is this a good solution? I’m encountering issues – for example, I can’t add a FruitBehaviour to a list of type ItemBehaviour. I’m sure that’s solvable, but I want to be sure that I’m on the right track before I start going down rabbit holes specific to this implementation.