Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Writing json files to user's system during play

Discussion in 'Scripting' started by DTECTOR, Sep 3, 2023.

  1. DTECTOR

    DTECTOR

    Joined:
    Aug 23, 2020
    Posts:
    128
    I have been doing a lot with accessing and storing information via json to various files but I know that in build none of this will probably work. What exactly do I need to do to both create new folders/files and read and write from them during gameplay?

    And then also, if I have a json already in the assets folder and there is a script that reads and writes from the known path, does that get maintained in some way after compilation or is that going to break?

    I am looking around at addressables, resources.load, streamingassetsfolder and all that but I don't feel like I know what to do in this regard after reading those. Especially if I want to use Newtonsoft.json, how does that work with those?
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    Runtime writing should only be under
    Application.persistentDataPath


    If you have something in Assets, you need to read it out and write it to the above location at runtime.

    You should avoid making a pre-made save file, unless it's appropriate to your game in the sense of "here's my game, and here's a pre-made save game to get you started." If you do that, put the pre-made stuff in StreamingAssets and Unity will not touch it at all and you can read it in the build. See docs for how.

    Well that's just JSON... and here's my blurb on that:

    Problems with Unity "tiny lite" built-in JSON:

    In general I highly suggest staying away from Unity's JSON "tiny lite" package. It's really not very capable at all and will silently fail on very common data structures, such as bare arrays, tuples, Dictionaries and Hashes and ALL properties.

    Instead grab Newtonsoft JSON .NET from the Unity Package Manager (Window -> Package Manager).

    If you want to avoid the Package Mangler's UI, just add this to your
    Packages/manifest.json
    file:

        "com.unity.nuget.newtonsoft-json": "3.0.2",


    https://assetstore.unity.com/packages/tools/input-management/json-net-for-unity-11347

    Also, always be sure to leverage sites like:

    https://jsonlint.com
    https://json2csharp.com
    https://csharp2json.io

    ------------------------

    Finally I leave you with another carpet bomb of useful links regarding loading / saving, as it is both an extremely well-traveled area of game development, as well as one that has a lot of technical, spatial and temporal traps you can fall into when implementing it.

    Load/Save steps:

    https://forum.unity.com/threads/save-system-questions.930366/#post-6087384

    An excellent discussion of loading/saving in Unity3D by Xarbrough:

    https://forum.unity.com/threads/save-system.1232301/#post-7872586

    Loading/Saving ScriptableObjects by a proxy identifier such as name:

    https://forum.unity.com/threads/use...lds-in-editor-and-build.1327059/#post-8394573

    When loading, you can never re-create a MonoBehaviour or ScriptableObject instance directly from JSON. The reason is they are hybrid C# and native engine objects, and when the JSON package calls
    new
    to make one, it cannot make the native engine portion of the object.

    Instead you must first create the MonoBehaviour using AddComponent<T>() on a GameObject instance, or use ScriptableObject.CreateInstance<T>() to make your SO, then use the appropriate JSON "populate object" call to fill in its public fields.

    If you want to use PlayerPrefs to save your game, it's always better to use a JSON-based wrapper such as this one I forked from a fellow named Brett M Johnson on github:

    https://gist.github.com/kurtdekker/7db0500da01c3eb2a7ac8040198ce7f6

    Do not use the binary formatter/serializer: it is insecure, it cannot be made secure, and it makes debugging very difficult, plus it actually will NOT prevent people from modifying your save data on their computers.

    https://docs.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide
     
    Last edited: Sep 3, 2023
    Sluggy and DTECTOR like this.
  3. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    3,899
    Also worth mentioning that for WebGL you'd have to treat file I/O differently since there's no file system access.
     
    DTECTOR, angrypenguin and Kurt-Dekker like this.
  4. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,495
    Though Unity (or emscripten to be more precise) emulates a filesystem through the indexedDB in the browser. The persistent data path points to
    "/idbfs/<md5 hash of data path>"
    . AFAIK you can actually create files and folders with that path and the files end up in the browsers indexedDB.

    Though the indexedDB has a size limit of about 200MB. So be careful what you actually store there. Most modern browser's developer tools allow you to inspect the indexedDB
     
    Kurt-Dekker and DTECTOR like this.
  5. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Even easier, just encapsulate your save data with a scriptable object class. Thus you can just author the data in the inspector, and reference it where needed.
     
    DTECTOR likes this.
  6. DTECTOR

    DTECTOR

    Joined:
    Aug 23, 2020
    Posts:
    128
    Wow, you answered every possible concern and offered more than I could have hoped for, thank you.
     
    Kurt-Dekker likes this.
  7. DTECTOR

    DTECTOR

    Joined:
    Aug 23, 2020
    Posts:
    128
    Actually I have one last question, regarding addressables and resources. I am using newtonsoft and merely writing to some random place for testing and to gradually build out the required features and saved configs, I am curious, can you load a json file from resources folder and then deserialize it? If so I imagine that might be a simple way to ship the game with preserialized json files, if one so desired. I see in one of the links a discussion about loading a scriptable object with save with resources.load but what about a straight up .json file? Where you save the json file directly to resources and resources.load the json file? Is that possible? I guess it doesn't have a type which is why you need the scriptableobject?
     
    Last edited: Sep 5, 2023
  8. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    You can just use a text asset: https://docs.unity3d.com/ScriptReference/TextAsset.html
     
    DTECTOR likes this.
  9. DTECTOR

    DTECTOR

    Joined:
    Aug 23, 2020
    Posts:
    128
    So I am testing the writing to the Application.persistentDataPath and the issue I get is the path has mixed seperators, partically in unix format and partially in windows format. The persistentDataPath is unix and the part I add via path.combine is windows, and I am on windows. Now that I am doing this I remember that this was happening so I stopped trying to use this path for testing because it was doing this.
     
  10. DTECTOR

    DTECTOR

    Joined:
    Aug 23, 2020
    Posts:
    128
    Okay well I just resorted to reformatting the path manually. Idk why it returns a unix path on windows. I know in the assetdatabase it always maintains unix paths but I would imagine it would not do that during game play and not using assetdatabase class. If you guys have any better ideas that would be less error prone I would appreciate it.
     
  11. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Show your code.

    In Windows builds/editor it should be giving backslashes. I've never had an issue using it before.
     
    DTECTOR likes this.
  12. DTECTOR

    DTECTOR

    Joined:
    Aug 23, 2020
    Posts:
    128
    You are correct, I was misattributing the issue. In debug.log it prints a mixed path to console, but it seems to be working correctly as I can find the files and folders written in the file explorer. I merely had a character in the path that was incorrect between reading and writing to file that was overlooking. I works perfectly now. Thanks for your help.
     
    Kurt-Dekker likes this.
  13. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    280
    I added a PathUtility to my code base because I ran into an issue with using the output of Application.persistentDataPath, combining it with desired relative paths, and using those paths with some .NET methods. I wrote some helper methods to can combine a base path with a relative file path, and clean paths so that they only contain valid characters and single directory separator characters defaulted for the current platform. So, you're not hallucinating having issues with making sure your paths are valid. I can't remember the exact issue off the top of my head, but I definitely hit a point where I felt it necessary to just validate all paths according to the rules of the current platform and what the relevant .NET file IO methods need to not throw exceptions. It may have been something as simple as the directory separator characters I used in specifying the relative file paths not matching the ones returned by Application.persistentDataPath. So, when combined there were mismatched separator chars in one path. But, like I said I can't remember exactly I just know I also added path validation to all of my file IO code to fix up any bad paths if possible before running headlong into an avoidable exception.