Search Unity

I need help saving a reference to a ScriptableObject asset

Discussion in 'Scripting' started by DDaddySupreme, Aug 10, 2019.

  1. DDaddySupreme

    DDaddySupreme

    Joined:
    Jun 3, 2017
    Posts:
    10
    I've been searching for hours now and haven't found anyone who seems to be giving the answers I need, so I figured I would ask directly (Sorry if this is in the wrong place, it's been a long and frustrating night). I'm creating a visual novel with a friend, and I'm storing the dialogue data as scriptable objects, which was working great until I started trying to implement save/load functions.

    I'm trying to save the game using a binary formatter to shove a serialized class containing important information into a file I can load later. To progress from one dialogue to the next, I've been using a system where each dialogue links to another dialogue via a variable that I drag and drop my ScriptableObject asset into.

    Here's the problem: in order to load properly, I need to save a reference to the dialogue ScriptableObject asset. I've tried saving the assets name but Resources.Load doesn't seem to load anything. It's been a little while since I've used this program so I don't know if I'm just using the wrong function, or if it's impossible and I just need to switch from ScriptableObjects to JSON files.

    I had a similar problem in a previous project where ScriptableObject assets represented spells. Back then I believe I made a dictionary or list with every spell and saved only their name in the binary file, but that came with its own headaches and isn't a viable solution for this project since I don't know how big the writer is going to want to make it, or how well he can be trusted to update a dictionary whenever he creates a new file.

    TLDR: I need to save a reference to a ScriptableObject asset as something serializable in runtime. Probably in the form of a string, but if there's an AssetReference class or something I don't know about that would work just as well
     
  2. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    Dragging and dropping dialogue references to chain them together is fine- it's what's referred to as a "linked list", in which each entry contains information to find the next entry. However, that structure by itself is insufficient to achieve the functionality you want and jump right into the middle of a dialogue at any point. You should view the linked list instead as a shortcut system on top of another lookup method that's more accessible.

    Give each dialogue entry its own unique identifier IMO (I'm fond of Guids and reverse-domain style string lookups myself, depending on the necessary organization level), and then have all references loaded right off the bat so that you can store them in a lookup of some sort, like a Dictionary of id keys to DialogueEntry values. You can break this down into smaller chunks and avoiding loading objects unnecessarily by using ConversationIDs on top of it. When you go to store the current position in your VN, simply serialize the Guid or integer or string or whatever you use for the Conversation and Entry identifiers, then use the lookup on load in order to track your way back to that exact location in the dialogue.

    There are several ways to accomplish what you want without changing much- you can use Addressables and give each ScriptableObject dialogue entry its own unique address there like "DialogueEntry/1256", but this could get tedious. You could also give the class a new recursive function called "RegisterEntry" (for instance), in which each one calls the same function on the next entries. An example would be:
    Code (csharp):
    1. public void RegisterEntry()
    2. {
    3.     bool notAlreadyRegistered = ConversationService.Register(id, this); // 'this' is the reference to the ScriptableObject instance
    4.     if(notAlreadyRegistered)
    5.     {
    6.         for(var i = 0; i < nextNodes.Length; i++)
    7.             nextNodes[i].RegisterEntry();
    8.     }
    9. }
    The ConversationService would return whether the entry had already been registered, avoiding infinite loops and branching paths each multiplying the amount of time all of the registrations take. Once you're done, you can then use the ConversationService.Lookup(entryID) in order to find the reference to the exact entry you want, without affecting any of your other functionality.

    An alternative would be to iterate through every single instance of the DialogueEntry ScriptableObject class using Resources.LoadAll, or through a master list you create of all entries in the project you want to load. For instance, ScriptableObjects for conversations, which list every entry within that conversation in one collection. In that case, you can iterate over all of the entries externally, registering their ids to the lookup and then using that lookup later to find where the exact entries you want are.

    If you can't trust the author to maintain the master list, then don't, but you should still use a lookup, just automate the process of registering all of the entries. But yeah, just some possibilities, hopefully it gives you some ideas.
     
    Last edited: Aug 10, 2019
    Omti1990 and DDaddySupreme like this.