So I’ll try to be as short as possible, if I fail to describe anything please let me know and I’ll edit this right away. I have a project and these are the requirements:
-
I need to have items that can be organized in the inventory and placed into the current game scene.
-
Some items attributes can be customized by the player.
-
When the player saves the game, every item must be saved.
-
When loading a game everything should be the same, so items that have been customized must save their attributes, items that are placed in the scene must save their transform data.
A good use example would be: Player gets a sword, a rock and a potion, then throws the rock on the ground, enchants the sword, then saves. When loading, the potion and sword (with the enchantment) must be on the inventory and the rock must be on the same place on the ground.
I tried to think on a good solution to this, and this is my current solution (which I’m not satisfied with):
-
Items are scriptable objects (SOs), I have a base Item class inheriting from SO and every other item inheriting from it, like Consumable or Weapon, each adding it’s own specific attributes.
-
Every Item asset has a prefab attribute (which is the Item representation in the scene), the prefab has an ItemController, which is responsible for handling the behaviour of the specific Item on the scene and has a reference to the Item it controls.
-
Every unmodified Item is a reference to it’s respective SO asset in the folders.
-
When modifying an Item, I create a new instance of the Item with Object.Instantiate if necessary (to make it unique), then modify it’s attributes.
-
Item class has a Save function that will serialize the item name and, if the item has been modified, will serialize every other attribute.
-
ItemController has a Save function that will serialize the GO transform values, then calls Save function on the Item it references.
-
When saving, PersistenceManager calls Save function on every Item on inventory and every ItemController on scene.
-
When loading, PersistenceManager spawns item prefabs based on the previously serialized Item name and GO transform; recreates references to the respective SO asset for every unmodified Item based on it’s name; then also creates new instances of the Item and initialize them with it’s modified values for every modified Item.
I’m thinking my current approach could cause some unwanted behaviour due to instantiating SOs and not saving them as assets, can anyone confirm?
Maybe a better approach would be: Continue to have SOs for immutable Item data, and have regular classes (POCOs) to handle the modifiable data for each item, but then I’ll be writing more classes for many items and also some functionalities, like simply displaying all data for the item, may become not so trivial. Any thoughts on that?
How would you approach this situation?
Bump, maybe something is wrong with my question? If so, please point out and I’ll correct it ASAP.
I think games like Minecraft use two separate classes for each item: an item (in inventory) class and a pickup (in the world) class. Each of these is derived from a base class.
Personally I would do something like this (this code is probably wrong and might not work, especially the bits where I cast the classes, as well as the abstract MonoBehaviour, but you get the idea):
public class InventoryItem
{
public string ClassName;
public string ItemName;
}
public static class Inventory
{
public static List<InventoryItem> OurInventory = new List<InventoryItem>();
public static void SpawnItem(int index)
{
// load asset using OurInventory[index].ItemName or ClassName as handle
// instantiate it, assign it to g
GameObject g = null; // pretend this is our new gameobject
WorldItem w = g.GetComponent(OurInventory[index].ClassName) as WorldItem;
w.Initialise(OurInventory[index]);
}
public static void Save()
{
// save OurInventory
}
}
public class Potion_Data : InventoryItem
{
public Potion_Data()
{
ClassName = "Potion_Item";
ItemName = "Potion";
}
public int HealingPower;
}
abstract public class WorldItem : MonoBehaviour
{
abstract public void Initialise(InventoryItem data);
}
public class Potion_Item : WorldItem
{
Potion_Data OurData;
public void Save()
{
// save
}
public override void Initialise(InventoryItem data)
{
OurData = data as Potion_Data;
}
void PutInInventory()
{
Inventory.OurInventory.Add(OurData as InventoryItem);
Destroy(gameObject);
}
public void OnTouch()
{
PutInInventory();
}
}
Haven’t tested it, but that’s how I would try and design it. Not sure if the InventoryItems would retain their polymorphism upon being serialized, though.
Thanks for the reply Anthony. It surely has some resemblance to how I think a solution without SOs would work.
But a few very important things should be fixed on it:
- InventoryItem should have a reference to a prefab, not a string ClassName
- WorldItem should be an interface
- Potion_Data should not have a parameterless constructor. You want to have a class definition for all potions and be able to create a new potion types from a database class, in the case of your code, like:
new Potion_Data("Small Poison Vial", smallPoisonVialPrefab, -10)
. That avoids having to create a class for every single item in the game
The question that remains is:
To design a solution for customizable items, which approach is the best:
- A pure scriptable object (SO) based solution, like the one I described in my original post
- A pure regular class based solution, like the one you suggested
- A mixed SO and regular class solution, with SOs for immutable data, and regular classes to handle the modifiable data for each item
Regarding code structure I guess both pure solutions are at the same level. The mixed solution should lose on that matter, for it requires writing two classes for some items and also writing methods that deal with data from both of those classes.
I really like the idea of having every item organized as a SO and not having to manually type paths for prefabs and images, but if could cause some unwanted behaviour due to instantiating new SOs and not saving them as assets, I would surely prefer another solution. I really hope someone could confirm this.
I think the best way to approach things like these is simply TIASWH (try it and see what happens)
Personally I think it seems like a good idea to use SOs for the immutable data. That means that the classes are easily editable (without having to recompile) and it means that you get all the flexibility of using classes (if there even is any greater flexibility). I don’t have much experience with SOs so I’m not sure if a pure SO solution would suit your needs, but like I said, you can certainly try.