Prefab array keeps clearing field

I am using a CSV to import data and I'm tracking it in this array.
(I understand Scriptable Objects is a thing, CSV is still the best path for this project.)

However for each item in the CSV I am also manually adding a gameobject.

When I start the game, the gameobject clears itself from the array.

My assumption was I could just plug each one in and it would be fine, but I think I'm just in over my head here.

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

public class CSVReaderRanged : MonoBehaviour
{

    public TextAsset textAssetData;

    [System.Serializable]
    public class CSVRangedData
    {
        public GameObject pickupObject;
        public int elementNumber;
        public string rangedDataPrefsID;
        public string weaponName;
        public string chestType;
        public int xpRequired;
        public int magicCost;
        public int itemCost;
        public int firePointSize;
        public float timeBetweenShots;
        public float speed;
        public int baseDamage;
        public float spread;
        public float knockbackForce;

    }

    [System.Serializable]
    public class CSVRangedDataList
    {
        public CSVRangedData[] rangedData;
    }

    public CSVRangedDataList rangedDataList;



    // Start is called before the first frame update
    void Awake()
    {
        ReadCSV();
    }

    public void ReadCSV()
    {
        string[] data = textAssetData.text.Split(new string[] { ",", "\n" }, StringSplitOptions.None);
        int tableSize = data.Length / 13 - 1;

        rangedDataList.rangedData = new CSVRangedData[tableSize];

        for (int i = 0; i < tableSize; i++)

        {
            rangedDataList.rangedData[i] = new CSVRangedData();

            rangedDataList.rangedData[i].elementNumber = int.Parse(data[13 * (i + 1)]);
            rangedDataList.rangedData[i].rangedDataPrefsID = data[13 * (i + 1) + 1];
            rangedDataList.rangedData[i].weaponName = data[13 * (i + 1) + 2];//<== fixed here
            rangedDataList.rangedData[i].chestType = data[13 * (i + 1) + 3];//<== fixed here
            rangedDataList.rangedData[i].xpRequired = int.Parse(data[13 * (i + 1) + 4]);//<== fixed here
            rangedDataList.rangedData[i].magicCost = int.Parse(data[13 * (i + 1) + 5]);//<== fixed here
            rangedDataList.rangedData[i].itemCost = int.Parse(data[13 * (i + 1) + 6]);//<== fixed here
            rangedDataList.rangedData[i].firePointSize = int.Parse(data[13 * (i + 1) + 7]);//<== fixed here
            rangedDataList.rangedData[i].timeBetweenShots = float.Parse(data[13 * (i + 1) + 8]);//<== fixed here
            rangedDataList.rangedData[i].speed = float.Parse(data[13 * (i + 1) + 9]);//<== fixed here
            rangedDataList.rangedData[i].baseDamage = int.Parse(data[13 * (i + 1) + 10]);//<== fixed here
            rangedDataList.rangedData[i].spread = float.Parse(data[13 * (i + 1) + 11]);//<== fixed here
            rangedDataList.rangedData[i].knockbackForce = float.Parse(data[13 * (i + 1) + 12]);//<== fixed here

            //rangedDataList.rangedData[i].weaponObject = CSVReader.instance.availableRanged.;

            //CSVReader.instance.availableRanged.Add(weaponClone);



        }
    }

}

Any thoughts on this?


When you start the game, Awake is called and your data is being regenerated - so anything it had in Edit Mode would be gone. If you only wanted your ReadCSV function to be called in Awake during Edit Mode, you might say:
if (!Application.isPlaying) ReadCSV();

Also, as you're not using [ExecuteInEditMode] or [ExecuteAlways] I assume you've called ReadCSV from somewhere else in Edit Mode (i.e. someReferenceToThis.ReadCSV() ), in which case called it from Awake is pointless (and causing your issue) so you could just remove that. Also, maybe no point mentioning it ..your function isn't assigning a GameObject at all (it has that commented out - so I'm just assuming that was to keep what you're posting shorter, and you actually do have a reference assigned by your full version of the code).

Wow excellent point. Think my brain was just on autopilot and I couldn't figure it out.

Any ideas on a good workaround for this? I could maybe have a separate list and compare corresponding data within each gameobject, like the weaponName or something. Maybe that's not the most efficient way.

What do you think?


Well, I'm not sure of your workflow here - but every time your game starts (unless you call ReadCSV again during Play Mode.. which you are by having that in Awake) it would have the same values it had in Edit Mode. When you play the game, any changes to those values on instances in the scene will be lost/reset when the game ends. So if you have it populated already in Edit Mode, there's no reason to call ReadCSV again.. just remove your call to ReadCSV in Awake? Otherwise it's more complicated, so need to know more about your workflow - where/when are you populating your weaponObject reference from? An editor script? Manually in edit mode? Where is that weapon coming from (is it instantiated, or somewhere in the scene etc.). Also typically if you want to check if data is same [originally anyway] as other data, you have an "id" field (typically int) that's unique for it, also typically auto-incremented like a database row, so that's what you would compare instead of the weaponName string.

Also maybe helpful, as a general tip, you can create an object all together in 1 call like this,

    for (int i = 0; i < tableSize; i++)
    {
      int index = (i + 1) * 13;
      rangedDataList.rangedData[i] = new CSVRangedData()
      {
        elementNumber = int.Parse(data[index]),
        rangedDataPrefsID = data[index + 1],
        weaponName = data[index + 2],
        chestType = data[index + 3],
        xpRequired = int.Parse(data[index + 4]),
        magicCost = int.Parse(data[index + 5]),
        itemCost = int.Parse(data[index + 6]),
        firePointSize = int.Parse(data[index + 7]),
        timeBetweenShots = float.Parse(data[index + 8]),
        speed = float.Parse(data[index + 9]),
        baseDamage = int.Parse(data[index + 10]),
        spread = float.Parse(data[index + 11]),
        knockbackForce = float.Parse(data[index + 12])
      };
    }

Edit: I'm assuming you're saying "i + 1" to skip the first row in the CSV because it's column titles.

Great summary of the code, had no idea you could do that.

I'll try and answer your questions here:

  • The CSV data is loading in a separate script (I put it below) / so maybe it is as simple as removing the Awake call, I'll have to try that
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


[ExecuteInEditMode]
public class CSVLoadOnEditorStart : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        GetComponent<CSVReaderRanged>().ReadCSV();
        GetComponent<CSVReaderMelee>().ReadCSV();
        print("Weapon data loaded in editor boiii");
    }
}
  • I was hoping to populate weaponObject manually but literally just putting each weapon into its associated index #. It's not instantiated or in the scene, I'm talking literally just putting it in there. I took a lot out of the whole script, but this is the essential piece of it:
for (int i = 0; i < CSVReaderRanged.rangedDataList.rangedData.Length; i++)
{
            string playerPrefsID = CSVReader.instance.CSVReaderRanged.rangedDataList.rangedData[i].rangedDataPrefsID;

            if (PlayerPrefs.GetInt(playerPrefsID) == 1)
            {                    unlockedPickupsRangedStandard.Add(CSVReader.instance.CSVReaderRanged.rangedDataList.rangedData[i].pickupObject);
}
}

I know I'm using the cardinal sin of player prefs but it works for my project in particular.

Okay - so you've already populated the data in edit mode, just remove the call to ReadCSV in your Awake and you should be good to go. If not let me know.. but one other thing. Since you're calling ReadCSV in edit mode from a MonoBehaviour (vs. custom editor or inspector), you might want to add SetDirty after calling ReadCSV. Otherwise you might not get a prompt to save when closing the scene or could find the data you've populated doesn't get saved.

#if UNITY_EDITOR
CSVReaderRanged ranged = GetComponent<CSVReaderRanged>();
ranged.ReadCSV();
EditorUtility.SetDirty(ranged);
CSVReaderMelee melee = GetComponent<CSVReaderMelee>();
melee.ReadCSV();
EditorUtility.SetDirty(melee);
#endif

The reason I wrap that in #if UNITY_EDITOR is because EditorUtility doesn't exist in builds, so the project would throw exceptions when you compile if I didn't. However, because you only want this script to exist in the editor anyway, you might wrap the entire class in #if UNITY_EDITOR instead (i.e. put that on the first line of the file, and #endif on the last). That way none of it will be included in your build.

If you were using a custom editor or inspector to do this (say, from a button "Read CSV Now"), then instead of using SetDirty after it like that, the proper way would be to call Undo.RecordObject(yourObject, "some description of what you're doing to it"); just before calling .ReadCSV. That way you could also press CTRL-Z/Undo and it would go back to how it was before you hit the button :)

Edit: You would have to add using UnityEditor; at the top of your class to do that though, or it won't know what "EditorUtility" even is - so if you're not wrapping the entire class with #if UNITY_EDITOR as noted above, then you would want to wrap that specific line with it.

Went ahead and added SetDirty, we'll see if that causes any issues.

In the meantime, removing the awake call.

It's the darndest thing. For a moment I thought it might be an issue where the frefab in question was actually a child of another prefab (so not a frefab in itself), so I pulled it out and made a new prefab. Still no luck updating - as soon as I jump back into scene mode (out of prefab mode) the gameobject disappears.

Here's a little video showing what I'm talking about:

https://imgur.com/a/r1EX5Wt


It could be that your ReadCSV is being called by your CSVLoadOnEditorStart script when you jump out of the prefab back to the scene, and that repopulates it from scratch, overwriting your changes. You could confirm this by adding Debug.Log("ReadCSV was called on " + name, this); into your ReadCSV function. Then watch the Console, and if it shows up, you can click on the line in Console and it will highlight the object that it was called in. If you see that, you know that's why it's being cleared.

If that's what it is, a quick fix would be to add a line to the top of your ReadCSV function saying like if (rangedDataList.rangedData != null && rangedDataList.rangedData.Length != 0) return;. That would prevent it from being populated if it already has data. Right now, you're resetting it every time in your CSVLoadOnEditorStart.

Edit: Instead of all this, you could just add a button in a custom inspector like "Update From CSV" and you click it when you want to overwrite your data. If you want an example let me know and I'll show you how to do that.

What is the idea of loading the data using a CSV? Just a one time thing, to have all data available without having to type it all out? Or do you want to use the CSV as a table that you will make changes to, and every time you start the game, it uses the new values?

Ended up being exactly the problem. However, it doesn't solve the issue at hand.

Also, I did end up using a YT tutorial and made a custom button! Working like a charm.

That said, the field public GameObject pickupObject; is still clearing every time I load the CSV.

I think the issue here is how I somehow have set things up from the get go. Because the gameobject itself is not in the CSV, it must be clearing the field each time it loads.

A workaround would be just having a separate list with each weapon, but then it's a bit redundant. Would love to keep it all in the same list. Any ideas here?

It's a CSV that I update regularly and use in multiple scenes in different ways. I also expect it to grow substantially. This is sort of the foundational spreadsheet here, and once I'm able to get past this one issue I will be able to add much more data to it.


What you could do if this is only being done in the editor is assign your prefab right along with the other stuff in the ReadCSV function, so like,

        spread = float.Parse(data[index + 11]),
        knockbackForce = float.Parse(data[index + 12]),
        pickupObject = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Prefabs/Weapons/" + data[index + 2] + ".prefab")

..the data[index + 2] is your weaponName, so in the above example you would just make sure your prefab is named the same thing as weaponName value and you consistently have your weapon prefabs in Assets/Prefabs/Weapons folder. Note that AssetDatabase is part of UnityEditor namespace so the stuff mentioned previously about EditorUtility.SetDirty, applies - you need to wrap it with #if UNITY_EDITOR, but ideally, refactor things to have your ReadCSV function within your ExecuteInEditMode script though, which will be a start to keeping editor-only vs. gameplay scripts separate.

1 Like


I was going to say, in that case the problem you need to solve is not that of not regenerating that array, but find a way to set the prefab by script. Adam beat me to it.

2 Likes

Yes this dude is being so attentive and helpful. Thanks anyway!

2 Likes

Wanted to report back as I have SOLVED THIS.

It's more clunky than I'd like, but it's clean and clear in the inspector, and most importantly it's working.

In the CSVReader script, I have a separate list that stores the weapon pickup objects in the same order as the CSV. Then I just refer to that. It's a prefab, very easy to organize, and again... it WORKS.

Just want to say thank you so much @adamgolden you really made this possible, and I couldn't have done it without your support. Thank you thank you thank you!!!

1 Like

Welp, I'm back again. Seems to just keep clearing. I'm giving up this path and starting over. CSV seems to be not friendly with Unity and I can't imagine why data would clear itself after saving a prefab. Does it every. single. time.

So frustrated.

We've mentioned it before, but you should really only use CSV files/data to generate a bunch of scriptable objects based on their data. Then you can just comfortably use said scriptable objects via the inspector in Unity.

Scriptable Objects are the 100% dependable method of referencing data via the inspector across multiple places. You're doing yourself a disservice by not using them.

1 Like