I have been trying to chase down a bug, and I fear I am a bit in over my head in dealing with inheritance, an array, and storing these derived classes in a single container (of type: base class),
-------------------
The Bug
-------------------
Whenever I spawn a ContainerObject (a class derived from GameWorldObject), it calls the SetupNewObject() method so everything is fine. I check the ContainerObjectData (class derived from ObjectData)& all items I placed within the chest are correct.
Whenever I leave & return to the level, and it calls LoadObjectData, the ContainerObject & its ContainerObjectData has only a few of the items or none of them.
The strangest part to this bug is that if I put multiple items with the same name in slots next to each other, it will only load a single item.
Blueprint, Blueprint, Apple, Blueprint will result in Blueprint, Apple, None, None.
Blueprint, Blueprint, Blueprint will result in Blueprint, None, None, None.
Or sometimes it will randomly decide to only have the very first item, and none others.
Blueprint, None, None, None.
------------------------
The Cause???
------------------------
I am almost certain the reason this is happening is because I am doing something wrong with inheritance and typecasting / storing in the base class dictionary.
I don’t know how else to store every derived GameWorldObject / ObjectData in a single container, other than having a container with a base class.
Am I just doing the entire structure wrong? Is this just a matter of not handling the Item[ ] array correctly somewhere?
------------------------
The Details
------------------------
I have two major base classes, which all game entities derive from.
The MonoBehaviour Component
//Unity Engine
public class GameWorldObject : MonoBehaviour
And the Serializable Data Object
//Simulation Data
[Serializable]
public class ObjectData
{
public string myCurrentLocation = "Blank";
public myVector3 myPosition = new myVector3(); //Object's world transform.position
public string myName = "Blank"; //Name of Prefab to load
public string objectGUID; //UNIQUE ID
public ObjectData()
{
}
public ObjectData(ObjectData objectData)
{
myCurrentLocation = objectData.myCurrentLocation;
myPosition = objectData.myPosition;
myName = objectData.myName;
objectGUID = objectData.objectGUID;
}
}
I store all the simulation Data in a serializable Dictionary. The Dictionary Key is objectGUID.
public Dictionary<string, ObjectData> allMyObjectData = new Dictionary<string, ObjectData>(); //A list of all the Location's objects.
I store references to all GameObjects instantiated via their objectGUID, in another Dictionary. This container is to hold all unity gameobjects which exist.
public static Dictionary<string, GameWorldObject> allActiveGameWorldObjects = new Dictionary<string, GameWorldObject>(); //Collection of all GameWorldObjects
Now, I have many derived classes for both Data (Simulation) & GameWorldObject (Engine)
public class PickableItem : GameWorldObject
public class ContainerObject : GameWorldObject
[Serializable]
public class PickableItemData : ObjectData
[Serializable]
public class ContainerObjectData : ObjectData
{
public Item[] Storage;
public bool isPlayerEquipped = false;
public bool isOpened = false;
public bool isBundle = true;
public ContainerObjectData()
{
}
public ContainerObjectData(ObjectData objectData)
{
myCurrentLocation = objectData.myCurrentLocation;
myPosition = objectData.myPosition;
myName = objectData.myName;
objectGUID = objectData.objectGUID;
}
public ContainerObjectData(ContainerObjectData containerData)
{
//ObjectData
myCurrentLocation = containerData.myCurrentLocation;
myPosition = containerData.myPosition;
myName = containerData.myName;
objectGUID = containerData.objectGUID;
//ContainerObjectData
isPlayerEquipped = containerData.isPlayerEquipped;
isOpened = containerData.isOpened;
isBundle = containerData.isBundle;
Storage = containerData.Storage;
}
}
I haven’t ran into any problems yet for any object type, except for my ContainerObjects. Specifically, their Storage. In the above, line 4 & line 31.
** ** public Item[] Storage; Storage = containerData.Storage;** **
The Item class is also Serializable Data (Part of the Simulation, not Unity Engine).
[Serializable]
public class Item
{
//Any changes should be reflected in PickableItem.cs and the copy Constructor, and Debug_ViewableItem
public string ItemName = "Blank";
public int ItemStackMax = 0;
public int CurrentItemStack = 0;
public Item()
{
ItemName = "Blank";
ItemStackMax = 0;
CurrentItemStack = 0;
}
public Item(Item newItem)
{
ItemName = newItem.ItemName;
ItemStackMax = newItem.ItemStackMax;
CurrentItemStack = newItem.CurrentItemStack;
}
}
My Serialized, Simulation Data is entirely separate from Unity. The entire world is generated in code, and then whenever I need to Render (Instantiate) a Level, I simply call one method.
This method instantiates all ObjectData (Simulation) as GameWorldObjects (Unity GameObject).
Upon instantiation of a GameWorldObject (Unity), it calls a method either based on if it is a New version from partial data or an older version Loaded from data.
I save all data to file & store the data in a dictionary with type base classes (ObjectData / GameWorldObject) even when they’re derived.
PickableItem : GameWorldObject is stored in Dictionary<string, GameWorldObject>().
ContainerObjectData : ObjectData is stored in Dictionary<string, ObjectData>().
Since a derived class ContainerObjectData has myObjectData and myContainerObjectData, I use a getter to get the proper data. This way it always returns the class type that is actually used. (Any derived GameWorldObject (Unity) uses its own derived ObjectData (Simulation).
public class ContainerObject : GameWorldObject
{
public ContainerObjectData myContainerObjectData;
//GameWorldObject variable: public ObjectData myObjectData
public override ObjectData getObjectData()
{
return myContainerObjectData;
}
/*
public virtual ObjectData getObjectData()
{
return myObjectData;
}
*/
If I’m creating a new ContainerObject (Unity), then I have no problem. I create a new Storage (Item[ ] Array) and populate it with empty item data.
However it’s when I am Loading Data that I have the bug.
public override void LoadObjectData(ObjectData newObjectData)
{
Debug.Log("Container: LoadObjectData(ObjectData " + newObjectData.myName + ")");
//Load ObjectData
myContainerObjectData = (ContainerObjectData)newObjectData;
SetAcceptItemDictionary();
/*
private void SetAcceptItemDictionary()
{
if (AcceptedItemsDictionary.Count == 0)
{
foreach (SpecialContainerInfo scInfo in AcceptedItems)
{
AcceptedItemsDictionary.Add(scInfo.acceptedItem, scInfo.newItemMaximums);
}
}
}
*/
Debug.Log("LoadObjectData: Container Storage = " + myContainerObjectData.Storage.Length);
//Load HarvestableItemData
if (myContainerObjectData.isOpened)
{
myAnimator.Play("Opened");
}
Vector3 myPosition = new Vector3(myContainerObjectData.myPosition.x, myContainerObjectData.myPosition.y, myContainerObjectData.myPosition.z);
transform.position = myPosition;
name = myContainerObjectData.myName;
RegisterGameWorldObject();
/*
public void RegisterGameWorldObject()
{
//Register with Location
ServerWorld.allLocations[getObjectData().myCurrentLocation].myGameWorldObjectKeys.Add(getObjectData().objectGUID);
//Register with MasterWorld
MasterWorld.allActiveGameWorldObjects.Add(getObjectData().objectGUID, this);
}
*/
}