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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question Creating a scriptable object from JSON text file in custom editor wizard

Discussion in 'Scripting' started by AStickInTheMud, Sep 10, 2023.

  1. AStickInTheMud

    AStickInTheMud

    Joined:
    Feb 15, 2019
    Posts:
    4
    I am trying to create a scripatble object file using a custom scriptable wizard inside the unity editor. I seem to be having trouble with properly populating the values specified within the input file. Whenever I try to do so, the script runs and generates the scriptable object file, but the values are all default null or 0. Is there something I am missing when using EditorJsonUtility.FromJsonOverwrite() ?

    The wizard in question:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEditor;
    5. using System.IO;
    6. using System;
    7.  
    8. public class AbilityRecipeCreator : ScriptableWizard
    9. {
    10.     public TextAsset abilityFile;
    11.  
    12.     string JSONContents;
    13.  
    14.     [MenuItem("Custom Asset Creation/Ability Recipe Wizard")]
    15.     static void CreateWizard()
    16.     {
    17.         ScriptableWizard.DisplayWizard<AbilityRecipeCreator>("Ability Recipe Creator", "Create Recipe", "Clear Fields");
    18.     }
    19.  
    20.     private void OnWizardCreate()
    21.     {
    22.         AbilityRecipe recipe = ScriptableObject.CreateInstance<AbilityRecipe>();
    23.         JSONContents = abilityFile.ToString();
    24.  
    25.         EditorJsonUtility.FromJsonOverwrite(JSONContents, recipe);
    26.  
    27.         string filePath = AssetDatabase.GetAssetPath(abilityFile);
    28.         string fileName = abilityFile.name;
    29.         filePath = Path.GetDirectoryName(filePath);
    30.  
    31.         ScriptableObjectUtility.CreateAsset<AbilityRecipe>(recipe, filePath, fileName);
    32.     }
    33.  
    34.     private void OnWizardOtherButton()
    35.     {
    36.         abilityModelPrefab = null;
    37.         abilityFile = null;
    38.     }
    39. }
    40.  
    The Scriptable Object:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. [System.Serializable]
    6. public class AbilityRecipe : ScriptableObject
    7. {
    8.     public string model;
    9.  
    10.     public string abilityName;
    11.     public string rangeType;
    12.     public string areaType;
    13.     public int horRange;
    14.     public int vertRange;
    15.     public string effectScript;
    16.  
    17. }
    18.  
    The JSON file:
    Code (CSharp):
    1. {
    2.     "abilityName":"Fight",
    3.     "rangeType":"ConstantValueRange",
    4.     "areaType":"SingleTargetAOE",
    5.     "horRange":1,
    6.     "vertRange":1,
    7.     "effectScript":"DefaultDamageEffect"
    8. }
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    37,150
    I think you might just be missing marking it dirty, perhaps with EditorUtility.SetDirty()

    If that doesn't cut it, try reorganizing:

    - make the SO
    - write the asset to disk
    - json-overwrite your stuff
    - dirty it

    The simple act of writing it to disk does a lot, changing the nature of the reference in very subtle ways related to the native code side of the Unity Engine.
     
    Last edited: Sep 10, 2023
  3. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    4,310
    Personally I prefer to keep the data in a struct rather than fields. This adds a lot of flexibility as you can more easily (de)serialize a struct and you can pass it around and use it without actually modifying the SO data. And you can swap the data in and out without having to create new instances of the SO.
    Code (CSharp):
    1. [System.Serializable]
    2. public struct MyData
    3. {
    4.     public string model;
    5.     public string abilityName;
    6.     public string rangeType;
    7.     public string areaType;
    8.     public int horRange;
    9.     public int vertRange;
    10.     public string effectScript;
    11. }
    12.  
    13. public class MyDataSO : ScriptableObject
    14. {
    15.     [SerializeField] MyData data;
    16. }
    17.  
     
  4. AStickInTheMud

    AStickInTheMud

    Joined:
    Feb 15, 2019
    Posts:
    4
    Simply setting the recipe as dirty (after line 22) did not work. The scriptable object file still wrote to disk as default values.

    The end result I'm trying to achieve here is to have the ability to create simple JSON files for all the abilities I want to load into the game as scriptable objects so I can instantiate gameobject components corresponding to the field strings and attach them to gameobjects at runtime. This way, it's quicker for me to create variations of abilities, then I can iterate through and create all the scriptable objects needed for runtime.

    I have a factory class that takes in the scriptable object to do all the runtime component attachment, but I'd need to generate the scriptable object first. I COULD theoretically directly use the JSON files at runtime instead but I'd prefer not to have to read those in every time I load up the game and instead use native Unity scriptable objects.

    Maybe I'm overthinking this?
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    37,150
    Then reorder it. It really will work, assuming the populate actually populates and you haven't made JSON mistakes.

    Verify your JSON by making a non-SO structure, populating it, printing the values.

    When you know that works, return to the reordering suggestion I said above.

    Here's the JSON blurb:

    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:

    [/icode] "com.unity.nuget.newtonsoft-json": "3.0.2",[/icode]

    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
     
  6. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,220
    I think you need to
    AssetDatabase.SaveAssets()
    and
    AssetDatabase.Refresh()
    too.
     
    Kurt-Dekker likes this.
  7. AStickInTheMud

    AStickInTheMud

    Joined:
    Feb 15, 2019
    Posts:
    4
    Well I've got some insight. It seems that the scriptable object instance never gets its data populated correctly. Below is some test code I wrote to see if that was indeed the case:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEditor;
    5.  
    6. public class JSONOverwtiteTester : ScriptableWizard
    7. {
    8.     public TextAsset testFile;
    9.  
    10.     string JSONContents;
    11.  
    12.     [MenuItem("Test/JSON Overwrite Tester")]
    13.  
    14.     static void CreateWizard()
    15.     {
    16.         ScriptableWizard.DisplayWizard<JSONOverwtiteTester>("JSON Overwrite Tester", "Print object", "Clear Fields");
    17.     }
    18.  
    19.     private void OnWizardCreate()
    20.     {
    21.         //make class object
    22.         //TestClass input = new TestClass(); //this works fine
    23.         //TestSO input = new TestSO(); //fails to overwrite values
    24.         TestSO input = ScriptableObject.CreateInstance<TestSO>(); //also fails to overwrite content
    25.         //EditorUtility.SetDirty(input);
    26.  
    27.         //overwrite content
    28.         JSONContents = testFile.ToString();
    29.         EditorJsonUtility.FromJsonOverwrite(JSONContents, input);
    30.  
    31.         //output to debug log
    32.         Debug.Log("Values:\r\n" + "TestInt: " + input.testInt + "\r\nTestFloat: " + input.testFloat + "\r\nTestString: " + input.testString);
    33.  
    34.         //write to disk
    35.     }
    36. }
    Code (CSharp):
    1. {
    2.     "testInt":1,
    3.     "testFloat":1.1,
    4.     "testString":"string"
    5. }
    I tested using the same input test file for both a regular C# class and a scriptable object. While the regular class seemed to populate correctly, the scriptable object does not. This I confirmed to be the same original issue I had using code breakpoints: The EditorJsonUtility.FromJsonOverwrite() method does not seem to populate my scriptable object instances.

    This leads me to think it's something to due with how the scriptable object is passed to this method that somehow differs from a normal class object?