- Home /
Inventory with Upgradable Items and Varying Stats
Hello,
I created an inventory system following this tutorial series: https://www.youtube.com/watch?v=bTPEMt1RG3s&list=PLm7W8dbdfloj4CWX8RS5_cnDWVn1Q6u9Q∈dex=1
The problem with this tutorial, as well as all the other inventory tutorials I've been able to find, use scriptable objects as the items. This is fine for items that have stats that don't change, but for my game, the equipable items will be generated with a range of random stats (similar to Diablo) and players will also be able to upgrade these items multiple times to increase its stats. So using scriptable objects for this won't really work since the stats change.
I tried creating a GameObject and having a MonoBehaviour on that object for storing the current stats, etc., but I can't store that into the inventory because it wants an Item class. It also doesn't seem like a good idea to store GameObjects because then I would need to use GetComponent() on each GameObject every time I want to retrieve the item's stats. There has to be a better way to do this, but I'm having trouble finding what that is. The creator of that tutorial series hasn't been answering people's questions, so I'm not sure how he would handle this issue.
The design is really nice in that I can easily create multiple inventories/bank and different item types, but what I really need is something that can also store items with dynamic stats. Anyone know a good way to convert this inventory system to be able to do that? Thanks in advance.
Answer by Razputin · Jun 27, 2019 at 06:21 AM
Can't you just make a new scriptable object for the randomly generated items using the normal scriptable object as the base?
Spawn new scriptable object
Set its stats equal to the base stats of the randomized object
Add randomness to the new scriptable object
I don't remember how to make a new scriptable object off the top of my head but something like
new ScriptableObject RNGHammer;
RNGHammer = NormalHammer;
RNGHammer.attack += Random.Range(0, 10);
I actually didn't know you can create scriptable objects at runtime. It turns out the tutorial is already instantiating copies of scriptable objects before adding them to the inventory. I just need to modify the stats when it's instantiated and save that data for loading later.
I'm having some issues accessing the variables of the current class though. Here's a small sample of how it's currently set up:
Item Scriptable Object (this is the base Item class that can be used as a template for different item types)
public class Item : ScriptableObject
{
public string id;
public new string name;
public int maximumStacks = 1;
public Sprite icon;
public virtual Item GetCopy()
{
return this;
}
public virtual void GenerateStats()
{
}
}
EquipmentItem class (this is an equippable item with a range of possible starting values)
public class EquipmentItem : Item
{
public enum EquipmentType { Barrel, Breech, Head, $$anonymous$$ount, Swivel, Base, $$anonymous$$od1, $$anonymous$$od2 }
public EquipmentType equipmentType;
public int starting$$anonymous$$inFirepower;
public int starting$$anonymous$$axFirepower;
public int starting$$anonymous$$inFireRate;
public int starting$$anonymous$$axFireRate;
public int starting$$anonymous$$inTrackingSpeed;
public int starting$$anonymous$$axTrackingSpeed;
public int starting$$anonymous$$inAccuracy;
public int starting$$anonymous$$axAccuracy;
public override Item GetCopy()
{
return Instantiate(this);
}
public override void GenerateStats()
{
// calculate stats
}
}
For my loot drop script, it's something like this:
public Item item;
private void DropLoot()
{
Item itemCopy = item.GetCopy();
itemCopy.GenerateStats(); // this works fine
itemCopy.name = "New Name"; // this also works fine
int temp = itemCopy.starting$$anonymous$$inFirepower; // doesn't work
}
Now if I try to access the name of the item, doing "itemCopy.name" works since it's in the Item class, but I can't access any of the variables in the EquipmentItem class like that even though I was able to put an EquipmentItem in the "item" slot of my loot drop script.
I could change it so that the loot drop script takes an EquipmentItem ins$$anonymous$$d of Item, but then I wouldn't be able to put in non equipment items, like consumables, currency, etc.
I suggest you to add func Init() in Item class and override this func inside GetCopy class. and then you can calc your attributes there.
Or calc atributes in GetCopy - instantiate scriptable object, fill attributes and return instance.
you also may need attribute in Item which will describe that type of item is that - weapon, Equipment and so on. Then you can do something like that:
private void DropLoot()
{
Item itemCopy = item.GetCopy();
if(itemCopy.itemType == ItemType.Equipment)
{
EquipmentItem equipItem ?= itemCopy;
//do what you need with equipItem
}
}
but if you want to calc some attributes of an Item - I suggest you to add func in Item and override it in EquipmentItem.
if your "item" do damage - you can add function in Item - GetDamage which will return 0 for class Item. and then override this function in EquipmentItem and return some value.
such stuff will help you to work with items and easely expand it.
if(item.itemType == ItemType.Weapon)
{
float dmg = item.GetDamage();
}
I added some more info to my script samples above to make things more clear. Calculating the attributes isn't a problem with my GenerateStats() function, but reading a variable from a child class is. But it just occurred to me that I probably shouldn't be reading the variables directly anyway and that it would be better to create another function inside the child class for that.
Answer by Kennai · Jun 27, 2019 at 07:25 AM
Hello, @Arithan!
Everything depends on how you make scriptable object for items!
If you make it as item object - then yes, its not fit you. But you can make it as 'template" for item and then use it in separate class.
I mean, in scriptable object you can set min and max damage, sprite, item color, or a set of colors, which will depends on item "level". If item is level 0, then it has min damage, if item is level 1, it has min + 5 damage and so on.
After you will make your "template" scriptable object, you can create a new pure class (without mono behaviour and etc)
and use that new class instead of scriptable object! You also can access Scriptable object from Item class, if needed (to show image or something else)
I made small example, where TemplateItem is a scriptable object.
example of Item class, I made two constructors, to show how it can be used:
public class Item
{
public float damage;
public float cost;
public float mass;
public TemplateItem item { get; private set; }
public Item(TemplateItem tempItem)
{ //tempItem is your scriptable object template
this.item = tempItem;
this.damage = tempItem.minDamage + Random.value *
(tempItem.maxDamage - tempItem.minDamage);
this.cost = tempItem.baseCost + Random.value * tempItem.randCost;
this.mass = tempItem.mass;
}
public Item(TemplateItem tempItem, int level)
{ //tempItem is your scriptable object template
this.damage = tempItem.minDamage + level * tempItem.lvlDamage;
this.cost = tempItem.baseCost + level * tempItem.lvlCost;
this.mass = tempItem.mass + level * tempItem.lvlMass;
}
}
I think this is similar to what's already happening. I have an Item class (parent class) that is a scriptable object, and various other child classes for each type of item, like EquipmentItem for equippable items. The EquipmentItem class has a random range of starting stats for the item. I also had a class called Equipment that stores the current stats after they have been generated, but this class is a $$anonymous$$onoBehaviour, which causes the problem of not being able to add it to an inventory since the Inventory class only wants objects with the Item class.
After reading your answer and the answer from @Razputin I think the best thing to do is to create new instances of the scriptable object at runtime when the loot is generated and then save that data into json or something so it can be loaded later. But I currently have the issue of not being able to access certain variables and methods of the child classes. $$anonymous$$ore in depth details about this are in the comments sections of @Razputin answer.
Look at this pls, is it suit you?
public class Item : ScriptableObject
{
public string id;
public new string name;
public int maximumStacks = 1;
public Sprite icon;
public bool isDroppable = true;
public bool isTradable = true;
public bool isEquipment = true;
public bool isStackable = true;
public virtual Item GetCopy()
{
return this;
}
//this func is used to equip your hero
public virtual void Equip(Hero hero)
{
//do nothing in Item
}
public virtual void Unequip(Hero hero)
{
//do nothing in Item
}
}
public class EquipmentItem : Item
{
private bool installed = false;
public float Armor { get; private set; }
public float Damage { get; private set; }
public float $$anonymous$$Damage;
public float maxDamage;
public float $$anonymous$$Armor;
public float maxArmor;
public override Item GetCopy()
{
EquipmentItem equipItem = Instantiate(this);
equipItem.Armor = $$anonymous$$Armor + Random.value * (maxArmor - $$anonymous$$Armor);
equipItem.Damage = $$anonymous$$Damage + Random.value * (maxDamage - $$anonymous$$Damage);
return equipItem;
}
public override void Equip(Hero hero)
{
if (installed)
return;
hero.Add(this);
hero.damage = Damage;
hero.armor += Armor;
installed = true;
}
public override void Unequip(Hero hero)
{
if (!installed)
return;
hero.Remove(this);
hero.damage = 0f;
hero.armor -= Armor;
installed = false;
}
}
public class SomeClass
{
public Item item;//or a list of items - what you want
public Hero hero;
public void InstallButton()
{
if (item.isEquipment)
item.Equip(hero);
}
}
Thanks for this. It helped me understand more about how inheritance works. I like how you do the calculations in the GetCopy() function before returning the item rather than how I was trying to do it after.
I'll have to try to put what I learned from these answers into my scripts tomorrow and see if I can get it to work how I want.
Your answer
Follow this Question
Related Questions
Inventory armor wielding proplem,How to convert from derived to base 1 Answer
Equipting a Item/Weapon 1 Answer
How to Instantiate objects that are Scriptable Objects 0 Answers
How i can make a script-made button interactable? 0 Answers
Scriptableobject List and Instantiating objects from it 3 Answers