π Inventory
What is Inventory
- Contains all items in the player's possession
- Inventory is internally an InventorySaveData item, which makes it managed by the SaveData system
Advantages of Inventory
- Uniform way of accessing and manipulating inventory items
- Build for maximum expandability
- Subscribe to changes in inventory
- Allows stackable and non-stackable items
How to create Inventory items
Managing inventory consists of 2 parts, InventoryAsset
and InventoryData
.
Creating the InventoryAsset
is required but the InventoryData
is optional.
Use the GameSuite preferences inventory panel to generate the files. Just enter the name of the item in the inventory name field.
- Inventory name: The format of inventory assets is
WeaponInventoryAsset
, so just enterWeapon
will generate the correct file name - Add inventory data: do you want to add additional specific data that should be saved for every item, continue reading to learn more on InventoryData .
InventoryAsset
This is the LibraryAsset
of the inventory item, which should contain all fixed data about this item that never changes (aside from app updates or AB testing). This data is not stored in the SaveData of the player.
This is the a simple example of a collectable card with an extra model and rarity to define in the inspector.
Inherit from InventoryAsset
and define the required specific variables required for this item:
namespace Game
{
public class CardInventoryAsset : InventoryAsset
{
[Resource]
public Guid Model;
[Rarity]
public Guid Rarity;
}
}
Lets have a look at a more complex example where the game needs to store information per item. When the Inventory system asks for the save data format it returns an implementation of InventoryData
named SwordInventoryData
.
namespace Game
{
public class SwordInventoryAsset : InventoryAsset
{
[Resource]
public Guid Model;
public int StartLevel;
public int LevelCap;
public int RuneSlotCount;
[Rarity]
public Guid Rarity;
public bool SingleHanded;
[BoneAttachment]
public Guid BoneAttachment;
public override InventoryData GetInventoryData(Type type = null)
{
SwordInventoryData sword = base.GetInventoryData(typeof(SwordInventoryData));
sword.Level = StartLevel;
// Add any specific initial SwordInventoryData manipulation here.
return sword;
}
}
}
InventoryData
This is the actual data object that gets stored in the SaveData and gets serialized to device. This data is optional.
Add any specific data that should be saved across game sessions here.
The default values in InventoryData
is a Guid
which points to the InventoryAsset
defined above and the Amount
of copies the player has in its possession.
This example is connected to the last example SwordInventoryAsset
and allows to store a level, durability and which runes have been equipped on every sword instance:
namespace Game
{
public class SwordInventoryData : InventoryData
{
public int Level;
public int Durability;
public List<Guid> Runes = new List<Guid>();
}
}
!!! tip "Optional"
InventoryData is optional, when the asset does not have any customizable properties that need to be stored except from the amount the player has, creating an empty SwordInventoryData
class is not required.
That being said, if you expect to have customizable properties in the future, it is a wise choice to create an empty SwordInventoryData
class.
How to access Inventory items
Get inventory items
Managing the inventory happens through the InventorySaveData
, get it from the SaveData by using:
SaveData.Get<InventorySaveData>();
The shortcut for the current player InventorySaveData
can be fetched with the shortcut Inventory
.
Then there are multiple ways to get access to inventory data. In the snippet below you can see which are accessible, when the Guid is named guid
it is referring to the internal InventoryData.UID value. assetGuid
is referring to the Library InventoryAsset UID, which in most cases is more useful as you want to tie the Inventory library to your SaveData.
public class InventorySaveData : ISaveData
{
public ObservableCollection<InventoryData> InventoryItems;
public int GetAmount(Guid assetGuid)
public bool TryGet(Guid guid, out InventoryData inventoryData)
public bool TryGet<T>(Guid guid, out T customInventoryData) where T : InventoryData
public bool TryGetByAsset(Guid assetGuid, out InventoryData customInventoryData)
public bool TryGetByAsset<T>(Guid assetGuid, out T customInventoryData) where T : InventoryData
public List<T> GetAll<T>(Guid assetGuid) where T : InventoryData
public List<InventoryData> GetAll(Guid assetGuid)
public List<T> GetAll<T>() where T : InventoryData
}
It is possible to just access the InventoryItems
list itself and do any checks yourself, but the methods included cover most use cases.
Lets say you want to get all WeaponInventoryData
items in the player's inventory:
List<WeaponInventoryData> weapons = SaveData.Get<InventorySaveData>().GetAll<WeaponInventoryData>();
Adding inventory data
Adding items is easy, either add any InventoryData
item yourself or let the system handle it for you. These are the build-in methods available:
public class InventorySaveData : ISaveData
{
public void SetAmount(Guid assetGuid, int amount)
public void Add(InventoryData inventoryData)
public void Add(Guid assetGuid, int amount = 1)
public bool TryAdd(Guid assetGuid, int amount = 1)
}
This example of a build-in action shows perfectly how easy it is to add items to the player's inventory from a library inventory asset.
public class ActionGetInventory : IAction
{
[Inventory]
public Guid Item;
[Tooltip("Use negative amount to subtract items")]
public int Amount = 1;
public void Execute()
{
Inventory.TryAdd(Item, Amount);
}
}
Remove inventory data
Either remove items directly from the InventoryData.UID or use the InventoryLibrary asset UID:
public class InventorySaveData : ISaveData
{
public void Remove(Guid assetGuid, int amount = 1)
public bool TryRemove(Guid assetGuid, int amount = 1)
public void RemoveAll()
}
Subscribing to changes
When needing to get updates on what gets added or removed from the inventory, the InventorySaveData
provides 2 ways of doing so:
public class InventorySaveData : ISaveData
{
public delegate void InventoryChangeDelegate(Guid assetGuid, int previousAmount, int newAmount);
public event InventoryChangeDelegate Changed;
public void SubscribeToChange(Guid assetGuid, Action<int, int> callback)
public void UnsubscribeToChange(Guid assetGuid, Action<int, int> callback)
}
Subscribe to general changes in the inventory using the Changed
delegate:
protected void Start()
{
Inventory.Get<InventorySaveData>().Changed += OnInventoryChanged;
}
protected void OnDestroy()
{
// Be sure to always unsubscribe when the script gets destroyed.
Inventory.Changed -= OnInventoryChanged;
}
private void OnInventoryChanged(Guid assetGuid, int previousAmount, int newAmount)
{
if (Libraries.Get<InventoryLibrary>().TryGet(assetGuid, InventoryAsset inventoryAsset))
{
// Implement logic here and do something with the InventoryAsset.
}
}
The other way is directly subscribing to a specific inventory asset:
[Inventory]
public Guid Item;
protected void Start()
{
Inventory.SubscribeToChange(Item, OnInventoryChanged);
}
protected void OnDestroy()
{
// Be sure to always unsubscribe when the script gets destroyed.
Inventory.UnsubscribeToChange(Item, OnInventoryChanged);
}
private void OnInventoryChanged(int previousAmount, int newAmount)
{
// Implement any required logic here.
}
InventoryAsset default values
Can Own Multiple
CanOwnMultiple
defines wether the player can have multiple items. An achievement or a player headquarters upgrade are examples of items that the player should only have 1 of.
Stackable vs non-stackable
Stackable
A stackable inventory asset are items which are all the same and should not contain individual customizable properties per item.
Stackable items would be:
- Coins
- Gems
- Potions
- Apples
- Arrows
- Cards in a card battler game
Non-stackable
Non-stackable inventory assets are the opposite, so they are customizable / upgradable per item:
- Armor pieces with each their own quality level (helmet lvl 1, helmet lvl 5 etc.)
- Sword with runes that can be equipped
- Upgradable characters
- Car with custom body paint and tuning parts assigned
!!! example "Upgradable cards" When having only a single item available in the entire game of that item, such as a card battler, where you would have one character card that can be leveled by gathering copies/xp, it is Stackable item. When the game has multiple characters of the same type that can be separately upgraded or manipulated, use the non-stackable inventory asset.
!!! abstract "Inner workings"
Internally, when using stackable, there will be 1 InventoryData element stored which has an int amount
inside. Non-stackable will have every item as a separate InventoryData item in the SaveData.
Initial Save
Set the player starting items in the InitialSaveConfig
, which is a ConfigLibraryAsset
. Remote configs and AB testing can be used to change these initial values so different player can have different balancing experiences.
Reference existing items
A character can have items equipped from the inventory itself. Which means the InitialSaveData should be able to link to another item within the InitialSaveData. This can be done by applying the [InventoryData]
attribute.
Lets say the game has characters which can be leveled up and should equip weapons and armor which can also be upgraded, then it is always the best option to link to items within the InventorySaveData
.
public class CharacterInventoryData : InventoryData
{
public int Health;
[InventoryData]
public List<Guid> EquippedItems = new List<Guid>();
}
Debug Menu
Go to Debug Menu > Cheats > Items > Inventory to give items to the player. They are categorized by asset type, such as WeaponInventoryAsset
, so if there are lots of different weapons in the game, they will be categorized under Weapon >
.