Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Item creation

Discussion in 'Scripting' started by Collin_Patrick, Sep 24, 2016.

  1. Collin_Patrick

    Collin_Patrick

    Joined:
    Sep 24, 2016
    Posts:
    83
    I want to create an inventory system that auto sorts items into different tabs depending on the item type (weapons, potions, etc.). But before I can sort the items I must first create them. I have seen several ways of doing this but they all seem like a headache.

    Is there a simple way to create items and add different stat effects to them(I will sort out how to apply the effects myself)? If so an example or sample code would be helpful.

    This is what I have so far,,,
    Code (CSharp):
    1. public class Item
    2. {
    3.     public enum ItemType
    4.     {
    5.         Sword,
    6.         Bow,
    7.         Staff
    8.     }
    9.     public ItemType itemType;
    10.  
    11.     public enum Rarity
    12.     {
    13.         Common,
    14.         Uncommon,
    15.         Rare,
    16.         Epic,
    17.         Legendary
    18.     }
    19.     public Rarity rarity;
    20.  
    21.     public Item(string ID, ItemType itemType, Rarity rarity, int healthMod, int magicMod, int strengthMod, int dexterityMod, int luckMod)
    22.     {
    23.  
    24.     }
    25. }
     
  2. Collin_Patrick

    Collin_Patrick

    Joined:
    Sep 24, 2016
    Posts:
    83
  3. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    This is a pretty big subject and can be broken down in to 3 parts I think

    1. Creating the data structures of an item
    2. How to create actual data for specific items and read them at run time
    3. How to instantiate and keep track of items during the game
    I'll go over 1 in this post:
    1. I would look into implementing a component based system like you were doing for the classes. You have a base Item class that every item in your game inherits from (swords, potions, scrolls , shields). This class contains the basic data that every item should have. You then implement Interfaces with any functionality an item should have. Create derived classes from Item that implement the appropriate Interface like this:
    Code (CSharp):
    1.  
    2. public interface IMeleeWeapon
    3. {
    4.     float AutoAttack(GameObject enemy); // Some Auto Attack function
    5. }
    6.  
    Avoid putting fields in the Interface unless they specifically are needed to be exposed to other classes. Only thing that goes into an interface is something another class needs to see.

    Your items could look like this:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public enum ItemType
    6. {
    7.     sword,
    8.     bow,
    9.     potion,
    10. }
    11.  
    12. public enum ItemRarity
    13. {
    14.     common,
    15.     uncommon,
    16.     rare,
    17.     epic,
    18.     legendary
    19. }
    20.  
    21. // this is just a struct to help Initialize a new Item
    22. public struct ItemInit
    23. {
    24.     public string name;
    25.     public ItemType type;
    26.     public ItemRarity rarity;
    27.     public int value;
    28. }
    29.  
    30. // this is just a struct to help iniitalize a new melee weapon
    31. public struct MeleeInit
    32. {
    33.     public bool twohanded;
    34.     public int numberDice;
    35.     public int typeDice;
    36.     public int flatDamage;
    37. }
    38. // things every Item in your game needs
    39. public class Item
    40. {
    41.     string name;
    42.     ItemType type;
    43.     ItemRarity rarity;
    44.     int value; // money for selling this thing
    45.  
    46.     public Item(ItemInit data)
    47.     {
    48.         this.name = data.name;
    49.         this.type = data.type;
    50.         this.rarity = data.rarity;
    51.         this.value = data.value;
    52.     }
    53. }
    54.  
    55. public class MeleeWeapon : Item, IMeleeWeapon
    56. {
    57.     bool twoHanded;
    58.  
    59.     // if your damage was 3d7+5
    60.     int numberDice;  // 3
    61.     int typeDice;    // 7
    62.     int flatDamage;  // 5
    63.  
    64.     public MeleeWeapon(MeleeInit meleeData, ItemInit itemData)
    65.         :base (itemData)
    66.     {
    67.         this.twoHanded = meleeData.twohanded;
    68.         this.numberDice = meleeData.numberDice;
    69.         this.typeDice = meleeData.typeDice;
    70.         this.flatDamage = meleeData.flatDamage;
    71.  
    72.     }
    73.     public float AutoAttack(GameObject enemy)
    74.     {
    75.         float damage = 0;
    76.         for (int i=0;i<numberDice;i++)
    77.             damage += Random.Range(1, (typeDice + 1));
    78.         damage += flatDamage;
    79.         return damage;
    80.     }
    81. }
    Then later on say you had code in your player to auto attack it might look like this:
    Code (CSharp):
    1. // Somewhere in the Update function
    2. if (timer > autoAttackDelay)
    3. {
    4.         Item item = GetMyWeapon();
    5.         AutoAttack(Item);
    6. }
    7.  
    8. // then the AutoAttack method
    9. void AutoAttack(Item item)
    10.     {
    11.         IMeleeWeapon  weapon = item as IMeleeWeapon;
    12.         GameObject target = GetMyTarget();
    13.         Debug.Log(weapon.AutoAttack(target));
    14.     }
    Notice the code just passes in a generic Item. This is because the item could be ANY class that derived from Item, as long as that class implements the IMeleeWeapon. This way your AutoAttack code doesn't care or need to know specifically what kind of weapon its getting.


    Now lets say you decided to add a new type of weapon that was a mace weapon. This weapon is just like a normal melee weapon but it has a chance to stun the enemy:
    Code (CSharp):
    1. // exactly like any normal melee weapon
    2. // but all maces can stun opponents
    3. public class MaceWeapon : MeleeWeapon, IMeleeWeapon
    4. {
    5.     public MaceWeapon(MeleeInit meleeData, ItemInit itemData)
    6.         : base(meleeData, itemData)
    7.     {
    8.     }
    9.     public new float AutoAttack(GameObject enemy)
    10.     {
    11.         // Check if we stun them
    12.         if (Random.Range(0, 100) < 5)
    13.             enemy.SetStatus(StatusEnum.Stun);
    14.         float damage = 0;
    15.         for (int i = 0; i < numberDice; i++)
    16.             damage += Random.Range(1, (typeDice + 1));
    17.         damage += flatDamage;
    18.         return damage;
    19.     }
    20. }
    Your existing code doesn't change at all! The current code to implement an autoattack just cares about IMeleeWeapon Interface. It doesn't care how a particular item implements it as long as its there.
     
    Last edited: Sep 25, 2016
  4. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Now lets talk about 3 Instantiating new items. (i'll get to 2 )
    This can get a little tricky, but if you notice I added a public struct that had all the fields of a particular class in it. This will make creating items easier. To create an item of a specific class.. You just have to create an Initialize the struct of its class, and the struct of each parent up the chain. If you look at how I set up the public constructors, they take these structs and call their parents constructors with them as well.

    So for example this code could create a new MeleeWeapon:
    Code (CSharp):
    1. void Awake()
    2.     {
    3.         // normally you would get all this data from a file
    4.         MeleeInit meleeData = new MeleeInit();
    5.         meleeData.twohanded = false;
    6.         meleeData.numberDice = 3;
    7.         meleeData.typeDice = 7;
    8.         meleeData.flatDamage = 5;
    9.  
    10.         ItemInit data = new ItemInit();
    11.         data.name = "sword";
    12.         data.type = ItemType.sword;
    13.         data.rarity = ItemRarity.common;
    14.         data.value = 10;
    15.  
    16.         weapon = new MeleeWeapon(meleeData, data);
    A quick note about interfaces I should have mentioned in the last post:
    Anytime you can check the validity of an item with the is keyword:
    Code (CSharp):
    1. void SomeFunction(Item item)
    2. {
    3.        if (!(item is IMeleeWeapon))
    4.             return; // maybe throw exception
    5.  
    6.         // you can also check this way
    7.         IMeleeWeapon = item as IMeleeWeapon;
    8.         if (IMeleeWeapon == null)
    9.            // throw a fit
    10.  
     
    Last edited: Sep 25, 2016
  5. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    So the hardest part might be creating and storing all these items. However, the BinaryFormatter gives you an easy way to serialize Data of derived classes. You can create a method to write out all your items as Item, and read them back in. However, if an Item is actually a MaceWeapon, it will store all that data. When you read it back in, it will read it back in as a MaceWeapon even if you've cast it as Item.

    Here is some Write and Read methods. Probably best to make these static in some script somewhere:
    Code (CSharp):
    1.     private void WriteItemToFile<T>(string path, T item)
    2.     {
    3.         // create a new formatter instance
    4.         System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter =
    5.             new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
    6.  
    7.         // open a filestream
    8.         using (FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write))
    9.         {
    10.             formatter.Serialize(stream, item);
    11.             stream.Close();
    12.         }
    13.  
    14.     }
    15.  
    16.     private T ReadItemFromFile<T>(string path, ref long position)
    17.     {
    18.         // create a new formatter instance
    19.         System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter =
    20.             new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
    21.  
    22.         // read the item as position back
    23.         T item = default(T);
    24.         using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read))
    25.         {
    26.             if (position < stream.Length)
    27.             {
    28.                 stream.Seek(position, SeekOrigin.Begin);
    29.                 item = (T)formatter.Deserialize(stream);
    30.                 position = stream.Position;
    31.             }
    32.         }
    33.         return item;
    34.     }
    Now to actually use these here is a sample code, but not necessarily how you'd want to do this:
    Code (CSharp):
    1.     MeleeWeapon weapon;
    2.  
    3.     void Awake()
    4.     {
    5.         MeleeInit meleeData = new MeleeInit();
    6.         meleeData.twohanded = false;
    7.         meleeData.numberDice = 3;
    8.         meleeData.typeDice = 7;
    9.         meleeData.flatDamage = 5;
    10.  
    11.         ItemInit data = new ItemInit();
    12.         data.name = "sword";
    13.         data.type = ItemType.sword;
    14.         data.rarity = ItemRarity.common;
    15.         data.value = 10;
    16.  
    17.         Type type = Type.GetType("MeleeWeapon");
    18.  
    19.         weapon = new MeleeWeapon(meleeData, data);
    20.         WriteItemToFile<Item>(Path.Combine(Application.dataPath, "ItemList"),weapon);
    21.  
    22.         long position = 0;
    23.         Item testItem = ReadItemFromFile<Item>(Path.Combine(Application.dataPath, "ItemList"),ref position);
    24.  
    25.         IMeleeWeapon testWeapon = testItem as IMeleeWeapon;
    26.         if (testItem is MeleeWeapon)
    27.             Debug.Log("its a melee weapon");
    28.         if (testItem is MaceWeapon)
    29.             Debug.Log("its a mace weapon");
    30.  
    31.         Debug.Log(testWeapon.AutoAttack(gameObject));
    32.     }
    Ideally what you would do. Is once you've written up all your Interfaces, and made all your classes, for example you might have something like this:
    Code (CSharp):
    1. public class Potion: Item, IDrinkable
    2. {
    3. }
    You will need to create a separate Item creation program. This is probably easiest done in .net on Visual Studio.. but you could use Unity or whatever you like. This program will have all your Interface and Item Class definitions. Then you can create a series of buttons/dropdowns whatever you like to create a new item. And of course InputFields to input the correct data. You'll have to play around a bit with a few items, to get the hang of how to read in data, and store it, without overwriting data you've already done. Once you've got that done, you Item File can just be read by your main Game program with the ReadFunction. Then you just read in all the items your game will have. You can sort them into Lists based on class type if you'd like (using if (item is ClassType). Or whatever structure you need to handle all the potential items in the game.

    Note: In the Writer Method I listed, it has FileMode.Create. This means it will create a brand new file everytime its called. You'll probably want to use this sometimes if your just going to dump the full data into the file and want to override changes you've made live into the file. But if your just adding new data, you can change it to FileMode.Append. Maybe add a bool flag to the function to switch between types.

    In general when working with serialized data, its fairly difficult to seek to a specific item and load just it. (thought its possible). Unless your working with Tens of thousands of items, you item creation program would work easiest just loading every Item you've created. Then its easy to create UI elements to search through existing items as well as create new ones. When your done just save the entire thing back out into the file with Create mode

    Last note: as you may have started to realize, a good inventory system for an RPG game can be a lot of work if you want to do it right. It will be a major chunk of the time you spend on the game. Once you get the actual items sorted out and how your game will handle them, the original question (sorting them into tabs) will be cinch.
     
    Last edited: Sep 25, 2016
  6. Collin_Patrick

    Collin_Patrick

    Joined:
    Sep 24, 2016
    Posts:
    83
    The information you have given me is in no doubt very helpful, but I feel like this is a bit more complex than it has to be for my game specifically. I am creating a turned based game like fire emblem. Stuff like auto attack are not really necessary and I am simply adding any stat buffs from the weapon equipped to the main stat pool (maybe extra effects like stun later on).
    EX:
    Code (CSharp):
    1.  public int health { get { return currentClass.health + baseHealth + equippedWeapon.healthMod; } }
    I would them use the main health (or attack, magic, etc) stat in my calculations.
    What I really need is an instance of an item with all the information I need to add stat mods and evaluate weapon types for advantages/disadvantages. What you have given me would work better for faster paced games.

    Also do not worry about telling me how to save things. I already have a firm grasp on that topic.
     
  7. Collin_Patrick

    Collin_Patrick

    Joined:
    Sep 24, 2016
    Posts:
    83
    I found the contents of this video to be close to what I want:
     
  8. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    I think the Event Component design is hard to wrap your head around at first, but I actually find it makes programming easier not more complex. So I would still definitely use it if I were writing Fire Emblem. That being said, with the classical approach your using, it seems have an array that holds the primary stats of an item, or player the simplest way to go. Then you can expose public properties like you posted above to let other objects have access to the secondary stats. Was there a specific issue you were running into trying to develop this?
     
  9. Collin_Patrick

    Collin_Patrick

    Joined:
    Sep 24, 2016
    Posts:
    83
    I have the scriptable objects working but what would be the best way to go about saving and loading them? This is a similar system to how I am saving and loading all of my characters.
    this is what I have so far:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.IO;
    5. using System.Runtime.Serialization.Formatters.Binary;
    6.  
    7. public class ItemStorage : MonoBehaviour {
    8.  
    9.     public static List<BaseItem> allItems = new List<BaseItem>();
    10.     public List<string> allItemID = new List<string>();
    11.  
    12.     public static ItemStorage itemStorage;
    13.  
    14.     void Awake()
    15.     {
    16.         if (itemStorage == null)
    17.         {
    18.             itemStorage = this;
    19.             DontDestroyOnLoad(gameObject);
    20.         }
    21.         else if (itemStorage != this)
    22.         {
    23.             Destroy(gameObject);
    24.         }
    25.  
    26.         LoadItems();
    27.     }
    28.  
    29.     public void GetAllItems()
    30.     {
    31.         allItemID.Clear();
    32.         foreach(BaseItem item in allItems)
    33.         {
    34.             allItemID.Add(item.ItemID);
    35.         }
    36.     }
    37.     public void SaveItems()
    38.     {
    39.         BinaryFormatter bf = new BinaryFormatter();
    40.         FileStream file = File.Create(Application.persistentDataPath + "/ItemData.dat");
    41.  
    42.         GetAllItems();
    43.         ItemData data = new ItemData();
    44.  
    45.         data.items = allItemID;
    46.         bf.Serialize(file, data);
    47.         file.Close();
    48.         Debug.Log("Save Compleated");
    49.         print(Application.persistentDataPath);
    50.  
    51.     }
    52.     public static void LoadItems()
    53.     {
    54.         if (File.Exists(Application.persistentDataPath + "/ItemData.dat"))
    55.         {
    56.             BinaryFormatter bf = new BinaryFormatter();
    57.             FileStream file = File.Open(Application.persistentDataPath + "/ItemData.dat", FileMode.Open);
    58.             ItemData data = (ItemData)bf.Deserialize(file);
    59.             file.Close();
    60.  
    61.             foreach(string itemID in data.items)
    62.             {
    63.                 BaseItem item = ItemDatabase.GetItem(itemID);
    64.                 if (item != null)
    65.                 {
    66.                     Debug.Log(string.Format("Item ID: {0}, Item Name: {1}, Item Description: {2}", item.ItemID, item.ItemName, item.ItemDescription));
    67.                     allItems.Add(item);
    68.                 }
    69.  
    70.             }
    71.             Debug.Log("Item Load Successful");
    72.         }
    73.     }
    74. }
    75. [System.Serializable]
    76. public class ItemData
    77. {
    78.     public List<string> items;
    79.  
    80. }
     
  10. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Well if all your items inherit from a base Item class. You can use the BinaryFormatter to both load and save them regardless of their actual class by saving them all cast as BaseItem. IT will still save all the relevant data, as well as load it. You would just need to store some variable in the base item class that tells you what class it really is.. then you can recast it to that.
     
  11. jimroberts

    jimroberts

    Joined:
    Sep 4, 2014
    Posts:
    560
    What do you mean by saving and loading ScriptableObjects? They are already serialized assets...
     
  12. Collin_Patrick

    Collin_Patrick

    Joined:
    Sep 24, 2016
    Posts:
    83
    Sorry that did not come across correctly. What I meant was the instances of the scriptable object when I spawn a new one. What I am currently doing is saving a list of all of the item id's in the inventory and just spawning new items every time I load them. I can see how this would create a lot of problems when I want to load in an equipped item. The equipped item would not retain information as to who it is equipped to because it is a new instance of the item.

    I am not sure if there would be an easy fix to this because I plan to create an inventory of about 7 slots(equipped Items) for each of my characters and a main inventory list(all items/item storage) that has all obtained items regardless as to if they are equipped or not.
     
    Last edited: Sep 27, 2016
  13. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    I assume your items of the same type are not all identical? Obviously a long sword item would be different than a mace item. But is every long sword the same.. or do long swords have a range of stats they might instantiate with. Some are +1 str, but others might be +2 or even +3 str?

    If so, you would need to give every instatiated item a unique ID (different than an ID saying its a long sword). This would be a global static counter that started at 0, and went to infinity never resetting. This should be part of the base item class. So when you save all the objects that are currently instantiated in the world, they will also be saved with their unique personal ID. Then you would save your characters, you would save an array of these unique IDs.

    So when you load the game back up. Your character loads and sees that equipment[0] = some int ID. (equipment[0] could always be the helmet). You just write a function that parses your list of loaded objects and returns the one whose ID = unique ID you give it. Then requip that item to the characters helmet on game load
     
  14. absolute_disgrace

    absolute_disgrace

    Joined:
    Aug 28, 2016
    Posts:
    253
    An alternate method could be to have all of your items predefined as prefab then simply have seralisable data classes that save the name of the prefab and any additional transforming data relating to those objects. This data could be its location in the world, its health, level, bonuses, whatever. This is quite space efficient as you are only storing the "Differences" compared to what an object starts in. Not suitable in every case but i'm personally use it for a project where the world and the items are all generated at run-time by random values (so not exactly procedural as i'm not using a seed). This allows me to store and re-load all of the items.
     
  15. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    @absolute_disgrace Thats a great idea. I'd still combine it with mine as well. The unique ID would be part of the differences from the Prefab. Just because i suspect its easier to have the character have a list of objects that its equipping, than to have every object have a spot that says "Nope i'm not equipped" or "I'm equipped HERE".

    They would be useful for storing items in stashes banks, etc. Since the bank could have a list of unique IDs its should have, and them just grab them from the global list of items that exist in the world on gameLoad
     
  16. absolute_disgrace

    absolute_disgrace

    Joined:
    Aug 28, 2016
    Posts:
    253
    Yeah this is a problem i'm yet to solve too, simply because i'm not there in the project yet. I like your idea too.
     
  17. Collin_Patrick

    Collin_Patrick

    Joined:
    Sep 24, 2016
    Posts:
    83
    Before we can go any further, I still need to finalize how things save. I would first like to note that items will spawn in from the folder holding all of my scriptable objects, so there will not be any stat generation because they are already predefined.

    As I said before, currently I am saving all of the inventory items by only saving the unique Item id and spawning them back into the inventory on load via the ID. From what I see, I have 2 options, I could either assign the ID of the character the item is equipped to the item and save the Character ID along with the item ID(probably with the dictionary feature). Or I could find a way to save and load the the same item(similar to how I had to get around saving my characters without including the monobehavior.) instance each time to a blank scriptable object.

    I am just spitting ideas though. I am self taught, so I could be sounding like an idiot right now.
     
  18. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Since each item you save already has an ID its saved with, I still think its easier to have the characters have an array of IDs for their equipment. Then you GameLoad logic is like this:
    • Load all the objects in the world
    • Load the characters after.
    • As the characters are loaded they use their equipment array to look up in to the objects list what object htey neeed to equip and your code equips it on them.
    Easier to have one list of say 10 IDs attached to the characters, then have 1 ID pointing to a character (or null) on hundreds(thousands?) of items.
     
  19. Collin_Patrick

    Collin_Patrick

    Joined:
    Sep 24, 2016
    Posts:
    83
    Ok, what I am thinking is to have 2 arrays attached to my characters regarding their equipped items. the first would be the array with all of the items, this would be empty every time the character is loaded. The second will be an array consisting of the equipped item ID and will be saved with the rest of the character data. First I will let all of the items load in, and the characters second. During character load, I will have it go through each ID in the array and assign the items again.
     
  20. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Yes, that is exactly what I was trying to get across.
     
  21. Collin_Patrick

    Collin_Patrick

    Joined:
    Sep 24, 2016
    Posts:
    83
    I have set up my items to load but I have come across the problem of the items not loading fast enough. How can I set up my character load to wait till after the items have loaded in.
     
  22. absolute_disgrace

    absolute_disgrace

    Joined:
    Aug 28, 2016
    Posts:
    253
    I like to use Boolean flags along the lines of 'bool LevelLoading' which is set to true when i'm starting my load up, then you set it to false once everything is done. You could then use an start a coroutine of an IEnumerator function to wait until loading is complete.
     
  23. oblivion-modder

    oblivion-modder

    Joined:
    Mar 18, 2015
    Posts:
    3
    Attach something like this to the player. You may have to delete a little as it was an old script which had a little more to it.

    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine.UI;

    public class inventory : MonoBehaviour {



    public List<GameObject> weaponList = new List<GameObject>();
    public List<GameObject> armourList = new List<GameObject>();
    public List<GameObject> ingredientList = new List<GameObject>();
    public List<GameObject> miscList = new List<GameObject>();

    private RaycastHit oldHit;
    private RaycastHit hit;
    public Ray ray;

    public Text objectName;

    void Update()
    {
    ray = Camera.main.ScreenPointToRay(Input.mousePosition);

    if (Physics.Raycast(ray, out hit, 3))
    {
    if (hit.transform.gameObject.GetComponent("inventoryItem") == null)
    {
    objectName.text = ("");
    }
    if (hit.transform.gameObject.GetComponent<inventoryItem>().playable == true)
    {
    pickUp();
    displayInfoOnScreen();
    }


    }

    }



    void pickUp()
    {
    if (Input.GetKeyDown("e"))
    {
    if (hit.transform.gameObject.GetComponent<inventoryItem>().type == ("weapon"))
    {
    weaponList.Add(hit.transform.gameObject);
    }
    else if (hit.transform.gameObject.GetComponent<inventoryItem>().type == ("armour"))
    {
    armourList.Add(hit.transform.gameObject);
    }
    else if (hit.transform.gameObject.GetComponent<inventoryItem>().type == ("ingredient"))
    {
    armourList.Add(hit.transform.gameObject);
    }
    else if (hit.transform.gameObject.GetComponent<inventoryItem>().type == ("misc"))
    {
    miscList.Add(hit.transform.gameObject);
    }


    hit.transform.position = new Vector3(0, -17, 0);
    }
    }


    then add a script called inventory Item to each item and fill out the vars.


    public bool playable = true;
    public string type;
     
  24. Collin_Patrick

    Collin_Patrick

    Joined:
    Sep 24, 2016
    Posts:
    83
    I am trying to set something similar up but I keep getting the "Argument out of range" error when I try to debug all the item names in the inventory.

    Code (CSharp):
    1.  public void Awake()
    2.     {
    3.         equippedItems = new List<BaseItem>(7);
    4.         equippedItemsID = new List<string>(equippedItems.Count);
    5.         StartCoroutine("Load");
    6.     }
    7.     IEnumerator Load()
    8.     {
    9.         while (ItemStorage.itemsLoaded == false)
    10.         {
    11.             yield return null;
    12.         }
    13.         if (loadCharacterData == true)
    14.         {
    15.             CharacterManager.Load(this.gameObject);
    16.             CurrentCharacterClass parsedClassEnum = (CurrentCharacterClass)System.Enum.Parse(typeof(CurrentCharacterClass), currentClass.GetType().Name);
    17.             currentCharacterClass = parsedClassEnum;
    18.         }
    19.         else
    20.         {
    21.             currentClass = CreateInstance<IBaseCharacterClass>(currentCharacterClass.ToString());
    22.             currentSubClass = CreateInstance<IBaseCharacterSubClass>(currentCharacterSubClass.ToString());
    23.             boon = CreateInstance<IBoon>(currentBoon.ToString());
    24.             bane = CreateInstance<IBane>(currentBane.ToString());
    25.             AddExp(5000);
    26.         }
    27.  
    28.  
    29.         for (int i = 0; i > equippedItemsID.Count; i++)
    30.         {
    31.             foreach (BaseItem item in ItemStorage.allItems)
    32.             {
    33.                 if (item.ItemID == equippedItemsID[i] && item.isEquipped == false)
    34.                 {
    35.                     equippedItems.Add(item);
    36.                     item.isEquipped = true;
    37.                     item.characterEquippedTo = entityID;
    38.                 }
    39.             }
    40.         }
    41.         // foreach (BaseItem item in ItemStorage.allItems) ItemStorage.EquipItem(this, item);
    42.         foreach (BaseItem item in equippedItems)
    43.         {
    44.             Debug.Log(characterName + " inventory: " + item.ItemName);
    45.         }
    46.  
    47.     }
    Code (CSharp):
    1. public static void LoadItems()
    2.     {
    3.         if (File.Exists(Application.persistentDataPath + "/ItemData.dat"))
    4.         {
    5.             BinaryFormatter bf = new BinaryFormatter();
    6.             FileStream file = File.Open(Application.persistentDataPath + "/ItemData.dat", FileMode.Open);
    7.             ItemData data = (ItemData)bf.Deserialize(file);
    8.             file.Close();
    9.  
    10.             foreach(string itemID in data.items)
    11.             {
    12.                 BaseItem item = ItemDatabase.GetItem(itemID);
    13.                 if (item != null)
    14.                 {
    15.                     Debug.Log(string.Format("Item ID: {0}, Item Name: {1}, Item Description: {2}", item.ItemID, item.ItemName, item.ItemDescription));
    16.                     allItems.Add(item);
    17.                     //itemStorage.SaveItems();
    18.                 }
    19.  
    20.             }
    21.             Debug.Log("Item Load Sucessful");
    22.  
    23.         }
    24.     }
    *UPDATE*
    I have fixed the code to actually add the item id's to the list when they are equipped and vice versa. I also noticed the mistake in saving and moved the line that saved the item id's before it ends. Still getting the same error though.
     
    Last edited: Sep 29, 2016
  25. absolute_disgrace

    absolute_disgrace

    Joined:
    Aug 28, 2016
    Posts:
    253
    Trying putting the debug statement near you .Add(item) line. Lets check that the item has all the required info back there.
     
  26. Collin_Patrick

    Collin_Patrick

    Joined:
    Sep 24, 2016
    Posts:
    83
    both the list with the characters equipped items and the item id's have all the correct information when I manually equip the items. I narrowed down the problem to when I save and load the list of id's.

    Save:
    Code (CSharp):
    1.   characterSheetContainer.equippedItems = CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().equippedItemsID;
    2.             //Debug.Log(CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().equippedItemsID[0]);
    Load:
    Code (CSharp):
    1. //foreach (string ID in data.Sheets[i].equippedItems) { objectCharacterSheet.equippedItemsID.Add(ID); }
    2.                     objectCharacterSheet.equippedItemsID = data.Sheets[i].equippedItems;
    3.  
    This is the entire script:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. //using System.Xml.Serialization;
    5. using System.IO;
    6. //using System.Linq;
    7. using System.Runtime.Serialization.Formatters.Binary;
    8.  
    9.  
    10. public class CharacterManager : MonoBehaviour
    11. {
    12.     public static CharacterManager characterManager;
    13.  
    14.     void Awake()
    15.     {
    16.         if (characterManager == null)
    17.         {
    18.             characterManager = this;
    19.             DontDestroyOnLoad(gameObject);
    20.         }
    21.         else if (characterManager != this)
    22.         {
    23.             Destroy(gameObject);
    24.         }
    25.     }
    26.  
    27.     public List<GameObject> CharactersSaveAndLoad = new List<GameObject>(2);
    28.     public List<CharacterSheetContainer> CharacterSheetsSaveAndLoad = new List<CharacterSheetContainer>(2);
    29.  
    30.     public void GetSheets()
    31.     {
    32.         CharacterSheetsSaveAndLoad.Clear();
    33.         for (int i = 0; i < CharactersSaveAndLoad.Count; i++)
    34.         {
    35.             CharacterSheetContainer characterSheetContainer = new CharacterSheetContainer();
    36.             characterSheetContainer.entityID = CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().entityID;
    37.             characterSheetContainer.characterName = CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().characterName;
    38.             characterSheetContainer.currentClass = CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().currentClass;
    39.             characterSheetContainer.currentSubClass = CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().currentSubClass;
    40.             characterSheetContainer.boon = CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().boon;
    41.             characterSheetContainer.boon = CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().boon;
    42.             characterSheetContainer.level = CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().level;
    43.             characterSheetContainer.currentExperience = CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().currentExperience;
    44.             characterSheetContainer.neededExperience = CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().neededExperience;
    45.             characterSheetContainer.baseVigor = CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().baseVigor;
    46.             characterSheetContainer.baseMagic = CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().baseMagic;
    47.             characterSheetContainer.baseDexterity = CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().baseDexterity;
    48.            // characterSheetContainer.maxSp = CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().maxSp;
    49.             characterSheetContainer.baseSP = CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().baseSP;
    50.             //characterSheetContainer.baseDefense = CharactersSaveAndLoad[i].gameObject.GetComponent<CharacterSheet>().baseDefense;
    51.             characterSheetContainer.baseLuck = CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().baseLuck;
    52.             characterSheetContainer.baseHealth = CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().baseHealth;
    53.             //foreach (string ID in CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().equippedItemsID) { characterSheetContainer.equippedItems.Add(ID); }
    54.             characterSheetContainer.equippedItems = CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().equippedItemsID;
    55.             //Debug.Log(CharactersSaveAndLoad[i].gameObject.GetComponent<PlayerCharacterSheet>().equippedItemsID[0]);
    56.             CharacterSheetsSaveAndLoad.Add(characterSheetContainer);
    57.             //CharacterSheetsSaveAndLoad.Add (CharactersSaveAndLoad[i].GetComponent<CharacterSheet>());
    58.          
    59.            
    60.         }
    61.     }
    62.     public void Save()
    63.     {
    64.         BinaryFormatter bf = new BinaryFormatter();
    65.         FileStream file = File.Create(Application.persistentDataPath + "/characterInfo.dat");
    66.  
    67.         GetSheets();
    68.         CharacterData data = new CharacterData();
    69.  
    70.         data.Sheets = CharacterSheetsSaveAndLoad;
    71.         bf.Serialize(file, data);
    72.         file.Close();
    73.         Debug.Log("Save Compleated");
    74.         print(Application.persistentDataPath);
    75.  
    76.     }
    77.     public static void Load(GameObject CharacterObject)
    78.     {
    79.         PlayerCharacterSheet objectCharacterSheet = CharacterObject.GetComponent<PlayerCharacterSheet>();
    80.         //CharacterSheet findCharacterSheet = CharacterObject.GetComponent<CharacterSheet>();
    81.  
    82.         if (File.Exists(Application.persistentDataPath + "/characterInfo.dat"))
    83.         {
    84.             BinaryFormatter bf = new BinaryFormatter();
    85.             FileStream file = File.Open(Application.persistentDataPath + "/characterInfo.dat", FileMode.Open);
    86.             CharacterData data = (CharacterData)bf.Deserialize(file);
    87.             file.Close();
    88.  
    89.             for (int i = 0; i < data.Sheets.Count; i++)
    90.             {
    91.                 if (data.Sheets[i].entityID == objectCharacterSheet.entityID)
    92.                 {
    93.                     //findCharacterSheet = data.Sheets[i];
    94.                     objectCharacterSheet.characterName = data.Sheets[i].characterName;
    95.                     objectCharacterSheet.currentClass = data.Sheets[i].currentClass;
    96.                     objectCharacterSheet.currentSubClass = data.Sheets[i].currentSubClass;
    97.                     objectCharacterSheet.bane = data.Sheets[i].bane;
    98.                     objectCharacterSheet.boon = data.Sheets[i].boon;
    99.                     objectCharacterSheet.level = data.Sheets[i].level;
    100.                     objectCharacterSheet.currentExperience = data.Sheets[i].currentExperience;
    101.                     objectCharacterSheet.neededExperience = data.Sheets[i].neededExperience;
    102.                     objectCharacterSheet.baseSP = data.Sheets[i].baseSP;
    103.                    // objectCharacterSheet.maxSp = data.Sheets[i].maxSp;
    104.                     objectCharacterSheet.baseVigor = data.Sheets[i].baseVigor;
    105.                     objectCharacterSheet.baseMagic = data.Sheets[i].baseMagic;
    106.                     objectCharacterSheet.baseDexterity = data.Sheets[i].baseDexterity;
    107.                     //objectCharacterSheet.baseDefense = data.Sheets[i].baseDefense;
    108.                     objectCharacterSheet.baseLuck = data.Sheets[i].baseLuck;
    109.                     objectCharacterSheet.baseHealth = data.Sheets[i].baseHealth;
    110.                     //foreach (string ID in data.Sheets[i].equippedItems) { objectCharacterSheet.equippedItemsID.Add(ID); }
    111.                     objectCharacterSheet.equippedItemsID = data.Sheets[i].equippedItems;
    112.  
    113.                 }
    114.             }
    115.  
    116.         }
    117.     }
    118. }
    119. [System.Serializable]
    120. public class CharacterSheetContainer
    121. {
    122.     public IBaseCharacterClass currentClass;// = new FighterClass();
    123.     public IBaseCharacterSubClass currentSubClass;// = new AdventurerSubClass();
    124.     public IBane bane;// = new SlowBane();
    125.     public IBoon boon;// = new RobustBoon();
    126.  
    127.     public string characterName = "Empty";
    128.  
    129.     public int level = 1;
    130.     public int currentExperience = 0;
    131.     public int neededExperience = 100;
    132.  
    133.     public string entityID = "0";
    134.  
    135.    // public int maxSp = 1;
    136.     public int baseSP = 1;
    137.  
    138.     public int baseHealth = 1;
    139.     public int baseVigor = 1;
    140.     public int baseMagic = 1;
    141.     public int baseDexterity = 1;
    142.     public int baseLuck = 1;
    143.  
    144.     public List<string> equippedItems;
    145. }
    146. [System.Serializable]
    147. public class CharacterData
    148. {
    149.     public List<CharacterSheetContainer> Sheets;
    150.  
    151. }
     
  27. absolute_disgrace

    absolute_disgrace

    Joined:
    Aug 28, 2016
    Posts:
    253
    Overall the code looks fine. We should add some debug statements to each part of the chain to see where the information is going missing. Suspect #1 is in the 'Load' method. Inside your loop you have "data.Sheets.entityID== objectCharacterSheet.entityID". We should check that this condition is true at least once. Its possible, for whatever reason, this is never being satisfied and then you are never loading in any items.

    I'd also suggest using some debug statements just prior to saving to make sure the object data hasn't disappeared.
     
  28. Collin_Patrick

    Collin_Patrick

    Joined:
    Sep 24, 2016
    Posts:
    83
    As you said, the code is fine. I decided to delete the save files on my computer and it worked perfectly.
     
  29. absolute_disgrace

    absolute_disgrace

    Joined:
    Aug 28, 2016
    Posts:
    253
    That's a relief! Glad you got it working!