Can the GetFromID method be used to save other scriptable objects?

I am wondering if I could apply the code used to save inventory items to other scriptable objects?

        public static InventoryItem GetFromID(string itemID)
        {
            if (itemLookupCache == null)
            {
                itemLookupCache = new Dictionary<string, InventoryItem>();
                var itemList = Resources.LoadAll<InventoryItem>("");
                foreach (var item in itemList)
                {
                    if (itemLookupCache.ContainsKey(item.itemID))
                    {
                        Debug.LogError(string.Format("Looks like there's a duplicate GameDevTV.UI.InventorySystem ID for objects: {0} and {1}", itemLookupCache[item.itemID], item));
                        continue;
                    }

                    itemLookupCache[item.itemID] = item;
                }
            }

            if (itemID == null || !itemLookupCache.ContainsKey(itemID)) return null;
            return itemLookupCache[itemID];
        }

I am trying to apply the same ID to my own scriptable object, but GetFromCropID keeps returning null.

[CreateAssetMenu(fileName = "Growables", menuName = "Plant Seeds/Make New Seed", order = 0)]
    public class PlantConfig : ScriptableObject, ISerializationCallbackReceiver
    {

        [SerializeField] string cropID = null;
        [SerializeField] int daysUntilSeedling = 1;
        [SerializeField] int daysUntilSprout = 2;
        [SerializeField] int daysUntilBudding = 3;
        [SerializeField] int daysUntilHarvest = 4;

        [SerializeField] GameObject seededPrefab = null;
        [SerializeField] GameObject seedlingPrefab = null;
        [SerializeField] GameObject sproutPrefab = null;
        [SerializeField] GameObject buddingPrefab = null;
        [SerializeField] GameObject harvestPrefab = null;
        [SerializeField] InventoryItem harvestableItem = null;


        // for saving
        static Dictionary<string, PlantConfig> cropLookupCache;

        public int DaysUntilSeedling()
        {
            return daysUntilSeedling;
        }
        public int DaysUntilSprout()
        {
            return daysUntilSprout;
        }
        public int DaysUntilBudding()
        {
            return daysUntilBudding;
        }
        public int DaysUntilHarvest()
        {
            return daysUntilHarvest;
        }

        public GameObject SpawnSeeds()
        {
            return seededPrefab;
        }

        public GameObject SpawnSeedling()
        {
            return seedlingPrefab;
        }

        public GameObject SpawnSprout()
        {
            return sproutPrefab;
        }

        public GameObject SpawnBudding()
        {
            return buddingPrefab;
        }

        public GameObject SpawnHearvestable()
        {
            return harvestPrefab;
        }

        public InventoryItem SpawnHarvestedPickup()
        {
            return harvestableItem;
        }

        public string GetCropID()
        {
            return cropID;
        }

        public static PlantConfig GetFromCropID(string cropID)
        {
            if (cropLookupCache == null)
            {
                cropLookupCache = new Dictionary<string, PlantConfig>();
                var cropList = Resources.LoadAll<PlantConfig>("");
                foreach (var crop in cropList)
                {
                    if (cropLookupCache.ContainsKey(crop.cropID))
                    {
                        Debug.LogError(string.Format("Looks like there's a duplicate PlantConfig ID for objects: {0} and {1}", cropLookupCache[crop.cropID], crop));
                        continue;
                    }

                    cropLookupCache[crop.cropID] = crop;
                }
            }

            if (cropID == null || !cropLookupCache.ContainsKey(cropID)) return null;
            return cropLookupCache[cropID];
        }

        void ISerializationCallbackReceiver.OnBeforeSerialize()
        {
            if (string.IsNullOrWhiteSpace(cropID))

            {
                cropID = System.Guid.NewGuid().ToString();
            }
        }

        void ISerializationCallbackReceiver.OnAfterDeserialize()
        {
            // Require by the ISerializationCallbackReceiver but we don't need
            // to do anything with it.
        }
    }

Here is the class that should save/load the crop ID:

[System.Serializable]
    public class FarmPlot : MonoBehaviour, ISaveable
    {
        public bool isTilled = false;
        public bool isPlanted = false;
        public bool isWatered = false;
        [SerializeField] Material untilledSoilTexture = null;
        [SerializeField] Material tilledSoilTexture = null;
        [SerializeField] Material wateredSoilTexture = null;
        GameObject player = null;
        GameObject plantPrefab = null;
        PlantConfig plant;
        public bool isHarvestable = false;
        public FarmPlotRecord farmPlotRecord;
        public string cropID;

        int daysSincePlanted = 0;

        private void Awake()
        {
            player = GameObject.FindGameObjectWithTag("Player");
        }

        private void UpdateFarmPlot()
        {
            if (isTilled)
            {
                gameObject.GetComponent<MeshRenderer>().material = tilledSoilTexture;
            }
            if (isTilled && isWatered)
            {
                gameObject.GetComponent<MeshRenderer>().material = wateredSoilTexture;
            }
            if (isPlanted)
            {
                //if (GetComponentInChildren<CropGrower>() == null)
                //{
                //    isPlanted = false;
                var newPlantPrefab = Instantiate(plant.SpawnSeeds(), transform.position, Quaternion.identity);
                newPlantPrefab.transform.parent = gameObject.transform;
            }
        }

        void Update()
        {
            if (Input.GetMouseButtonDown(0))
            {
                HandleRaycast();
            }
        }

        public void HandleRaycast()
        {
            RaycastHit hit;
            if (Physics.Raycast(GetMouseRay(), out hit))
            {
                var farmTool = player.GetComponent<Equipment>().GetItemInSlot(EquipLocation.Weapon) as WeaponConfig;
                //print(farmTool);
                if (farmTool == null) return;
                if (hit.collider.gameObject.tag == "Farm")
                {
                    FarmPlot soil = hit.collider.gameObject.GetComponent<FarmPlot>();
                    TillFarmPlot(soil, farmTool);
                    WaterFarmPlot(soil, farmTool);
                    PlantinFarmPlot(soil, farmTool);
                }
            }
        }

        public void TillFarmPlot(FarmPlot soil, WeaponConfig farmTool)
        {
            if (soil.isTilled) return;
            if (!soil.isTilled && farmTool.GetTool() == "Hoe")
            {
                soil.isTilled = true;
                soil.GetComponent<MeshRenderer>().material = tilledSoilTexture;
            }
        }

        public void WaterFarmPlot(FarmPlot soil, WeaponConfig farmTool)
        {
            if (soil.isWatered || !soil.isTilled) return;
            if (!soil.isWatered && soil.isTilled && farmTool.GetTool() == "Waterer")
            {
                soil.isWatered = true;
                soil.GetComponent<MeshRenderer>().material = wateredSoilTexture;
                print("Watering this farm plot: " + soil.gameObject.name);
            }
        }

        public void PlantinFarmPlot(FarmPlot soil, WeaponConfig farmTool)
        {
            if (soil.isPlanted || !soil.isTilled) return;
            if (soil.isTilled && farmTool.GetTool() == "Seeds")
            {
                soil.isPlanted = true;
                Vector3 soilLocation = soil.transform.position;
                plant = farmTool.GetSeeds();
                print(plant);
                plantPrefab = plant.SpawnSeeds();
                cropID = plant.GetCropID();
                var newPlantPrefab = Instantiate(plantPrefab, soilLocation, Quaternion.identity);
                newPlantPrefab.transform.parent = soil.gameObject.transform;
            }
        }

        private static Ray GetMouseRay()
        {
            return Camera.main.ScreenPointToRay(Input.mousePosition);
        }

        public void WateringReset()
        {
            if (!isWatered) return;
            if (isWatered)
            {
                isWatered = false;
                GetComponent<MeshRenderer>().material = tilledSoilTexture;
            }
        }

        public void GrowthStatus(FarmPlot soil)
        {
            if (soil.isWatered && soil.isPlanted)
            {
                CropGrower cropGrower = GetComponentInChildren<CropGrower>();
                cropGrower.GrowthStatus(soil);
            }
        }

        public void ResetCropStage()
        {
            isPlanted = false;
        }

        [System.Serializable]
        public struct FarmPlotRecord
        {
            public bool waterRecord;
            public bool tillRecord;
            public bool plantRecord;
            public int daysSincePlantedRecord;
            public bool harvestableRecord;
            public string cropID;

        }

        public object CaptureState()
        {
            farmPlotRecord.waterRecord = isWatered;
            farmPlotRecord.tillRecord = isTilled;
            farmPlotRecord.plantRecord = isPlanted;

            if (isPlanted)
            {
                farmPlotRecord.daysSincePlantedRecord = daysSincePlanted;
                farmPlotRecord.harvestableRecord = isHarvestable;
                farmPlotRecord.cropID = cropID;
            }
            Debug.Log("testing farm save: " + gameObject.name + " " + farmPlotRecord.tillRecord + " " + farmPlotRecord.waterRecord + " " + farmPlotRecord.plantRecord);
            return farmPlotRecord;
        }

        public void RestoreState(object state)
        {
            var farmPlotStatus = (FarmPlotRecord)state;

            isWatered = farmPlotStatus.waterRecord;
            isTilled = farmPlotStatus.tillRecord;
            isPlanted = farmPlotStatus.plantRecord;
            if (isPlanted)
            {
                plant = PlantConfig.GetFromCropID(farmPlotStatus.cropID);
                Debug.Log(plant);
            }
            UpdateFarmPlot();

            Debug.Log("testing farm load: " + gameObject.name + " " + farmPlotStatus.tillRecord + " " + farmPlotStatus.waterRecord + " " + farmPlotStatus.plantRecord);
            
        }
    }

Per the Debug Log, it looks like GetFromCropID is not able to get the crop id from the object.
image

I added a few more debug logs to find where exactly it all goes wrong. It looks like it has no problem getting an ID when calling PlantInFarmPlot():

public void PlantinFarmPlot(FarmPlot soil, WeaponConfig farmTool)
        {
            if (soil.isPlanted || !soil.isTilled) return;
            if (soil.isTilled && farmTool.GetTool() == "Seeds")
            {
                soil.isPlanted = true;
                Vector3 soilLocation = soil.transform.position;
                plant = farmTool.GetSeeds();
                plantPrefab = plant.SpawnSeeds();
                cropID = plant.GetCropID();

                Debug.Log("planted: " + plant + " " + cropID);
                var newPlantPrefab = Instantiate(plantPrefab, soilLocation, Quaternion.identity);
                newPlantPrefab.transform.parent = soil.gameObject.transform;
            }

but the values don’t seem to make their way into CaptureState():
image

public object CaptureState()
        {
            farmPlotRecord.waterRecord = isWatered;
            farmPlotRecord.tillRecord = isTilled;
            farmPlotRecord.plantRecord = isPlanted;

            if (isPlanted)
            {
                farmPlotRecord.daysSincePlantedRecord = daysSincePlanted;
                farmPlotRecord.harvestableRecord = isHarvestable;
                farmPlotRecord.cropID = gameObject.GetComponent<FarmPlot>().cropID;
                Debug.Log("saved: " + farmPlotRecord.cropID + "cropID: " + cropID);
            }
            Debug.Log("testing farm save: " + gameObject.name + " " + farmPlotRecord.tillRecord + " " + farmPlotRecord.waterRecord + " " + farmPlotRecord.plantRecord);
            return farmPlotRecord;
        }

If you’re looking to get the SO by the cropID, it’s probably better to store the plant.GetCropID() instead.

Do you mean to update Capture state to farmPlotRecord.cropID = plant.GetCropID();? I just tried that, but I am still getting the null reference error.

It looks like the string isnt getting stored in the Farm Plot Record structure, and neither is the plant record. Its not saving because of the null reference, I’m pretty sure.
image

I made a small update in the capture statement to use if (farmPlotRecord.plantRecord) instead of if(isPlanted):

public object CaptureState()
        {
            farmPlotRecord.waterRecord = isWatered;
            farmPlotRecord.tillRecord = isTilled;
            farmPlotRecord.plantRecord = isPlanted;

            if (farmPlotRecord.plantRecord)
            {
                farmPlotRecord.daysSincePlantedRecord = daysSincePlanted;
                farmPlotRecord.harvestableRecord = isHarvestable;
                farmPlotRecord.cropID = plant.GetCropID();
                Debug.Log("saved: " + farmPlotRecord.cropID + "cropID: " + cropID);
            }
            Debug.Log("testing farm save: " + gameObject.name + " " + farmPlotRecord.tillRecord + " " + farmPlotRecord.waterRecord + " " + farmPlotRecord.plantRecord);
            return farmPlotRecord;
        }

now the plantRecord gets stored, but it still can’t save the string ID.
image

With so many of these variables public, it’s hard to determine if there isn’t a serialization issue going on… Make sure that in all the instances of Farm Plot, that Is Tilled, Is Planted, and IsWatered are false when the game is not running.

You also might consider making plant a public field for now (again, leave it blank, let PlantinFarmPlot fill it in).

Ultimately, once your script is properly debugged, you’ll want to turn MOST of those public fields (isTilled, isPlanted, isWater, player, plantprefab, isHarvestable, FarmPlotRecord, and cropID) to private fields. A sizeable percentage of bugs out there are caused by the variables being public and their being changed in the inspector by accident.

Now that I made the PlantConfig public (and cleaned up the other variables), I can see that plant never seems to be assigned:
image
Which seems strange because the debug log prints the ID when PlantInFarmPlot is called:


It’s getting cleared by something, but I’m not sure by what. This would affect cropID too, since it needs to read the instantiated plant in order to get its ID.

And that’s the mystery… the debug shows plant as assigned, but plant is then cleared, but I can’t find anything in your code that sets plant to null…

I’d like to take a closer look, if possible. Zip up your project folder, remove the Library folder from the zip, and upload the zip file to https://gdev.tv/projectupload/ I’ll take a look and see if I can figure out why plant isn’t holding on to it’s value.

1 Like

I just got it to work! I made the following change to capturestate:

 public object CaptureState()
        {
            farmPlotRecord.waterRecord = isWatered;
            farmPlotRecord.tillRecord = isTilled;
            farmPlotRecord.plantRecord = isPlanted;

            if (farmPlotRecord.plantRecord)
            {
                farmPlotRecord.daysSincePlantedRecord = daysSincePlanted;
                farmPlotRecord.harvestableRecord = isHarvestable;
                farmPlotRecord.cropID = GetComponentInChildren<CropGrower>().GetCropType().GetCropID();
                Debug.Log("saved: " + farmPlotRecord.cropID + "cropID: " + cropID);
            }
            Debug.Log("testing farm save: " + gameObject.name + " " + farmPlotRecord.tillRecord + " " + farmPlotRecord.waterRecord + " " + farmPlotRecord.plantRecord);
            return farmPlotRecord;
        }

instead of trying to save the plant config’s value, I instead went directly into the child object’s crop grower component, and called the plant config through there. The result seems to work, although I have yet to test it thoroughly.

I really appreciate the offer to take a closer look, I think there’s a good chance I’ll have more issues with this topic soon, but for now it is working.

1 Like

Privacy & Terms