Problem with data in scriptable objects

Hey guys,

the problem goes like this: I create a scriptable object, which is meant to represent a dialogue tree. After some activities in the project e.g adding new files or going into play mode the instance of SO seems to be broken, it shows data of some dialogue node on the deeper level of a tree.

Weirdly, if I enter the game and try to use this “broken” SO it works as intended(shows correctd dialogues), I also checked a file of the instance of SO and the data is completly fine, so only the representation in the editor is broken.

It’s not possible to edit the Instance of SO with this bug, so that’s why Im posting.

Here is a code of the scriptable object(first time posting, idk how to format a codeblock so if it’s wrong tell me so I will try to edit)

Scriptable object file:

[CreateAssetMenu(fileName = "NewDialoguesForEachCharacterSO", menuName = "Scriptable Objects/Dialogues")]
public class DialogueNodeSO : ScriptableObject
{
    public List<DialogueNodeSONeededAssets.DialogueOption> options;

    public string id;

    [ContextMenu("Generate IDs")]
    private void GenerateIDsForAllOptions()
    {
        id = System.Guid.NewGuid().ToString("N");

        foreach (DialogueNodeSONeededAssets.DialogueOption option in options)
        {
            option.GenerateID();
        }
    }
}

And the file with needed assets(thought modifing the scriptable object source file was a problem so I made a separate file for the assets, it helped, but problem still randomly occures):

public class DialogueNodeSONeededAssets
{
    [System.Serializable]
    public class DialogueOption
    {
        public string main_character_text = "";//Main character text
        public AudioClip main_character_audio;//Audio of character saying given line
        public float eventual_npc_response_time_delay = 0.0f;//Time after which npc responses

        public List<NpcResponses> responses;//responses, since npc might talk in multiple lines

        public List<DialogueEvent> invoked_events_list = new();//List of invoked events

        public List<DialogueOption> options = new();//Possible next dialogue options

        public string id_of_dialogue_option_to_go_back_to;//Id of certain dialogue option to go back to after choosing this one

        public bool is_available = true;//Can this dialogue be shown

        public string id = "";//Id of a dialogue

        public DialogueOption()
        {
            GlobalEvents.OnLookingForDialogueListWithGivenID += TryReturningOptions;
        }

        private void TryReturningOptions(object sender, GlobalEvents.OnLookingForDialogueListWithGivenIDEventArgs e)
        {
            if (id == e.given_id)
            {
                GlobalEvents.CallbackForOnLookingForDialogueListWithGivenIDEventArgs args = new(options);
                GlobalEvents.FireCallbackForOnLookingForDialogueListWithGivenID(this, args);
            }
        }

        public void GenerateID()
        {
            if (id == "" || id == null)
            {
                id = System.Guid.NewGuid().ToString("N");
            }

            foreach (DialogueOption option in options)
            {
                option.GenerateID();
            }
        }
    }

    [System.Serializable]
    public class NpcResponses//response with time delay
    {
        public string response = "";
        public AudioClip response_audio_clip;
        public float eventual_response_time_delay = 0.0f;
    }

    [System.Serializable]
    public enum DialogueEvent//Invoked events, in DialogueManager you can Invoke GlobalEvents by using it
    {
        EndDialogue,//Throw it in if you want to end dialogue
        GoBackToCertainDialogueOption//Throw it in if you want to go back to certain options
    }
}

The most likely explanation is that you pass around references to the SO, list or option instances in your code and then modify them. As long as the SO asset is not marked as dirty, Unity will not persist those changes, and when a domain reload happens on entering play mode, Unity loads the original unchanged SO from disk.

You should treat asset SOs as strictly read-only in your game code. If you need to work with the data, first Instantiate a copy and use that instead.

Check your code for instances where you modify the dialog data. In the editor, you can use EditorUtility.IsPersistent to check if a SO is an asset and shouldn’t be modified or if it’s a copy. You could also manually mark your data as read-only, only remove that mark when you copy it and then check/error before any data is changed.

1 Like

Thank you Adrian, I will try to change the code to work with a copy, if it does not work I will probably ask again :stuck_out_tongue:

Also maybe I will show the code just to be sure, all I do that might modify the SO is this:

private DialogueNodeSO current_dialogue_root;
private List<DialogueNodeSONeededAssets.DialogueOption> current_options_list;

current_dialogue_root = dialogue_root;
current_options_list = dialogue_root.options;

private void GoBackToCertainDialogueOptionsCallbacked(object sender, GlobalEvents.CallbackForOnLookingForDialogueListWithGivenIDEventArgs e)
    {
        current_options_list = e.list_of_options;
        dialogue_options_ui.DisplayOptions(current_options_list);
    }

so the line where I do current_options_list = e.list_of_options; so idk if that might be the problem?
Thanks again for an answer!

EDIT: Looking back at your answer, even if I do any changes in the play mode(which the only possibility is the code above) I actually don’t want them to remain after stopping the play mode so I don’t see the solution to my problem there.

Lists are references types. When you assign lists to fields or variables, e.g. the current_options_list = e.list_of_options; in your code, you’re not making a copy of the list, you only copy the reference, which still points to the same list. Unless you make an explicit copy of the list, a change anywhere in your code will change the list for all other places that hold a reference to it. If you pass lists around, where it is coming from can be easily lost and tracking where a change is coming from gets complicated to untangle.

One approach is to always first make a copy of the list and then pass around that copy. This is not ideal allocation-wise but simple and effective. Another is to pass around lists as IEnumerable<T>, which doesn’t directly allow changing it and forces the receiving code to first create a new list.

But, working with scriptable object assets, the simplest is maybe to always copy the asset with Instantiate at the very beginning when your game starts. This also copies all the lists the scriptable object contains. Then have all your code use the copy instead of the original asset, which will prevent any inadvertent modifications.

1 Like

Ok I made sure I work on a copy but problem still occures, look at the image:

I set up the dialogue node, I locked the editor(I mean I clicked the lock image on SO editor menu) on it to drag audio references easier. Then I unlocked the editor, dragged the SO reference to my NPC and the SO broke again(without even entering the playmode). As you can see after I entered the play mode it shows different options(correct ones) than in the editor window. DO you maybe have solution to this?

EDIT: Look, as I said earlier even the source file of an instance of SO is correct:

%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 0}
  m_Enabled: 1
  m_EditorHideFlags: 0
  m_Script: {fileID: 11500000, guid: abfbdd4898e0b0a41bca645267cba75d, type: 3}
  m_Name: TestDialogueBlackjack
  m_EditorClassIdentifier: 
  options:
  - main_character_text: Hej, ty!
    main_character_audio: {fileID: 8300000, guid: d37d0385ff9cef14f8fb59d4fa0e177c,
      type: 3}
    eventual_npc_response_time_delay: 0
    responses:
    - response: Czego, chcesz?
      response_audio_clip: {fileID: 8300000, guid: 5e3aae376b0874546ad79b2280e261cb,
        type: 3}
      eventual_response_time_delay: 0
    invoked_events_list: 
    options:
    - main_character_text: "Przechodz\u0105c obok Twojej celi widzia\u0142em, \u017Ce
        grasz w jakie\u015B karty. O co tu chodzi?"
      main_character_audio: {fileID: 8300000, guid: aedf51f01b44d7449995931da896adbd,
        type: 3}
      eventual_npc_response_time_delay: 0
      responses:
      - response: A, tak!
        response_audio_clip: {fileID: 8300000, guid: 275187c34592baa4e8086830a514c028,
          type: 3}
        eventual_response_time_delay: 0
      - response: Od lat jestem mistrzem blackjacka!
        response_audio_clip: {fileID: 8300000, guid: 9bad3916a5132444cb62ddcd4e238193,
          type: 3}
        eventual_response_time_delay: 0
      - response: wiesz w ogole, co to zagra?
        response_audio_clip: {fileID: 8300000, guid: 732be6eec52c4e84ea8ae62ce59cb1a5,
          type: 3}
        eventual_response_time_delay: 0
      invoked_events_list: 
      options:
      - main_character_text: "Nie mam poj\u0119cia, o co w niej chodzi?"
        main_character_audio: {fileID: 8300000, guid: 46328b31ba449844eb72ae4c5b5b4630,
          type: 3}
        eventual_npc_response_time_delay: 0
        responses:
        - response: Jakas losowa...insturkcja
          response_audio_clip: {fileID: 8300000, guid: 92af2e4daa550cc4bb7a0c47c23698d2,
            type: 3}
          eventual_response_time_delay: 0
        invoked_events_list: 01000000
        options: []
        id_of_dialogue_option_to_go_back_to: 6896bc2f048b491a9c3738e4ad92e633
        is_available: 1
        id: e9f68f2ef2cb47eb8fbfaa9f02a9ef24
      - main_character_text: Pewnie!
        main_character_audio: {fileID: 8300000, guid: c916c01f720743342899d1c8b0d05141,
          type: 3}
        eventual_npc_response_time_delay: 0
        responses:
        - response: PFF!
          response_audio_clip: {fileID: 8300000, guid: 96df167d12753c14dbc53eea11e8ac25,
            type: 3}
          eventual_response_time_delay: 0
        - response: No to siadaj, zobaczymy czy jest taki mocny.
          response_audio_clip: {fileID: 8300000, guid: 575a3a8f63ed739408d2b9fc11ccb2ad,
            type: 3}
          eventual_response_time_delay: 0
        - response: Nie gram dla zabawy
          response_audio_clip: {fileID: 8300000, guid: 25b662ed24bfba445ab3a818d0f354fe,
            type: 3}
          eventual_response_time_delay: 0
        - response: O co gramy?
          response_audio_clip: {fileID: 8300000, guid: 64b3b59cc5e6a174b9c9bfb0e9202789,
            type: 3}
          eventual_response_time_delay: 0
        invoked_events_list: 
        options:
        - main_character_text: "Gramy o kas\u0119. 300 sztuk z\u0142ota dla zwyci\u0119zcy."
          main_character_audio: {fileID: 8300000, guid: 4b4d4df1a620d4342ba41621d41f539f,
            type: 3}
          eventual_npc_response_time_delay: 0
          responses:
          - response: ' Siadaj, gramy do 3.'
            response_audio_clip: {fileID: 8300000, guid: e1d19407f881f6247b01f22ac720405c,
              type: 3}
            eventual_response_time_delay: 0
          invoked_events_list: 
          options: []
          id_of_dialogue_option_to_go_back_to: 
          is_available: 1
          id: 66c6ad757c174f7f9be17b90aa1bc678
        - main_character_text: "Bardzo podoba mi si\u0119 kilof, kt\xF3ry le\u017Cy
            na Twoim biurku, mo\u017Ce zagramy o niego?"
          main_character_audio: {fileID: 8300000, guid: 1edc977c99e31274fa8a7a1746527771,
            type: 3}
          eventual_npc_response_time_delay: 0
          responses:
          - response: "Dobra, ale przegrana b\u0119dzie ci\u0119 kosztowa\u0107 500
              sztuk z\u0142ota. "
            response_audio_clip: {fileID: 8300000, guid: 25f1ff4c28391aa4db00448036863850,
              type: 3}
            eventual_response_time_delay: 0
          - response: Gramy do 3 wygranych.
            response_audio_clip: {fileID: 8300000, guid: e1d19407f881f6247b01f22ac720405c,
              type: 3}
            eventual_response_time_delay: 0
          invoked_events_list: 
          options:
          - main_character_text: Jasne dzaidku!
            main_character_audio: {fileID: 8300000, guid: 51915bdcc1ca49a46afcb6d394a3701d,
              type: 3}
            eventual_npc_response_time_delay: 0
            responses: []
            invoked_events_list: 00000000
            options: []
            id_of_dialogue_option_to_go_back_to: 
            is_available: 1
            id: 64f95a4fed3b4a2d8c413ff6746037e3
          - main_character_text: Niewazje, wroce pozniej
            main_character_audio: {fileID: 8300000, guid: 616daa3a300788549ad4a89f23ad2b34,
              type: 3}
            eventual_npc_response_time_delay: 0
            responses:
            - response: No to spadaj!
              response_audio_clip: {fileID: 8300000, guid: 09220f0040b695246bf7d6a86c7551c7,
                type: 3}
              eventual_response_time_delay: 0
            invoked_events_list: 
            options:
            - main_character_text: 
              main_character_audio: {fileID: 0}
              eventual_npc_response_time_delay: 0
              responses: []
              invoked_events_list: 
              options: []
              id_of_dialogue_option_to_go_back_to: 
              is_available: 0
              id: 5e0b97f21db64f679d1c732910f3fc3b
            id_of_dialogue_option_to_go_back_to: 
            is_available: 1
            id: 8efc46a74cc24372bc8b22133c612694
          id_of_dialogue_option_to_go_back_to: 
          is_available: 1
          id: ba6b68071e41480798144bb994a04b8b
        - main_character_text: Niewazne, wroce pozniej
          main_character_audio: {fileID: 8300000, guid: 616daa3a300788549ad4a89f23ad2b34,
            type: 3}
          eventual_npc_response_time_delay: 0
          responses:
          - response: No to spadaj!
            response_audio_clip: {fileID: 8300000, guid: 09220f0040b695246bf7d6a86c7551c7,
              type: 3}
            eventual_response_time_delay: 0
          invoked_events_list: 00000000
          options: []
          id_of_dialogue_option_to_go_back_to: 
          is_available: 1
          id: 70a4b0031bd44392ba289d796f7ef6d1
        id_of_dialogue_option_to_go_back_to: 
        is_available: 1
        id: fde07d24639946e5b77d3c9165da6f0c
      - main_character_text: Niewazne, wroce pozniej
        main_character_audio: {fileID: 8300000, guid: 616daa3a300788549ad4a89f23ad2b34,
          type: 3}
        eventual_npc_response_time_delay: 0
        responses:
        - response: No to spadaj!
          response_audio_clip: {fileID: 8300000, guid: 09220f0040b695246bf7d6a86c7551c7,
            type: 3}
          eventual_response_time_delay: 0
        invoked_events_list: 00000000
        options: []
        id_of_dialogue_option_to_go_back_to: 
        is_available: 1
        id: ac621131cbd8420d84383bb2fd9b46f3
      id_of_dialogue_option_to_go_back_to: 
      is_available: 1
      id: 6896bc2f048b491a9c3738e4ad92e633
    - main_character_text: Niewazne, wroce pozniej
      main_character_audio: {fileID: 8300000, guid: 616daa3a300788549ad4a89f23ad2b34,
        type: 3}
      eventual_npc_response_time_delay: 0
      responses:
      - response: No to spadaj!
        response_audio_clip: {fileID: 8300000, guid: 09220f0040b695246bf7d6a86c7551c7,
          type: 3}
        eventual_response_time_delay: 0
      invoked_events_list: 00000000
      options: []
      id_of_dialogue_option_to_go_back_to: 
      is_available: 1
      id: 7e4edea338bd492b85280c679ad46cb5
    id_of_dialogue_option_to_go_back_to: 
    is_available: 1
    id: 8878faa657e243a2b2944710acf7c8b3
  - main_character_text: Niewazne, wroce pozniej
    main_character_audio: {fileID: 8300000, guid: 616daa3a300788549ad4a89f23ad2b34,
      type: 3}
    eventual_npc_response_time_delay: 0
    responses:
    - response: No to spadaj!
      response_audio_clip: {fileID: 8300000, guid: 09220f0040b695246bf7d6a86c7551c7,
        type: 3}
      eventual_response_time_delay: 0
    invoked_events_list: 00000000
    options: []
    id_of_dialogue_option_to_go_back_to: 
    is_available: 1
    id: ca7f3b85c2fc4a56aed9969fd7d62e7f
  id: 88c1a1515f614f21a336c6ff2ff6bba1

But again the representation in editor is wrong…