Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Custom Assets: how to create without triggering "level changed"?

Discussion in 'Scripting' started by talecrafter, Oct 7, 2014.

  1. talecrafter

    talecrafter

    Joined:
    Mar 26, 2013
    Posts:
    34
    Yeah, well, I thought Custom Assets should get created with the hideFlag "HideAndDontSave", with the save refering to scene hierarchy and not the project itself.

    But when I set "HideAndDonSave", there is this error message in the console:
    "o->TestHideFlag (Object::kDontSave) && (options & kAllowDontSaveObjectsToBePersistent) == 0
    UnityEditor.DockArea:OnGUI()"
    And the asset looks broken.

    When I don't set any HideFlag, the asset works fine, but the scene get's marked as "changed".

    When I use "DontSave", I get the warning/error message from above, but the asset works fine. Until I make a build and get the error: "An asset is marked with HideFlags.DontSave but is included in the build..."

    This is the utility function I use to create the asset:

    Code (CSharp):
    1. public static T CreateAsset<T>(string customName = "") where T : ScriptableObject
    2. {          
    3.     T asset = ScriptableObject.CreateInstance<T>();
    4.  
    5.     string path = AssetDatabase.GetAssetPath(Selection.activeObject);
    6.     if (path == "")
    7.     {
    8.         path = "Assets";
    9.     }
    10.     else if (Path.GetExtension(path) != "")
    11.     {
    12.         path = path.Replace(Path.GetFileName(AssetDatabase.GetAssetPath(Selection.activeObject)), "");
    13.     }
    14.  
    15.     if (customName == "")
    16.         customName = "New " + typeof(T).ToString();
    17.  
    18.     string assetPathAndName = AssetDatabase.GenerateUniqueAssetPath(path + "/" + customName + ".asset");
    19.  
    20.     ProjectWindowUtil.CreateAsset(asset, assetPathAndName);
    21.  
    22.     return asset;
    23. }
    It is called like this:
    Code (CSharp):
    1. [MenuItem("Assets/Create/Sequence")]
    2. public static void CreateSequenceAsset()
    3. {
    4.     Sequence newSequence = AssetUtilities.CreateAsset<Sequence>("Sequence");
    5. }
    I used 4.5.4 and 4.6 Beta 20.
    So, in short: How do I create a custom asset from a ScriptableObject without changing the scene? Is this buggy at the moment or am I doing something wrong?
     
  2. Melang

    Melang

    Joined:
    Mar 30, 2014
    Posts:
    166
    Also very much interested in this, did you by chance find a solution? Anyways, bump. I remember being happy just because custom assets work, but constant "Do you want to save the scene?" quickly becomes annoying...
     
  3. Melang

    Melang

    Joined:
    Mar 30, 2014
    Posts:
    166
    Ok, I've managed to solve this, for my case at least. Flags had nothing to do with it, new ScriptableObjects mark scene as dirty when they haven't been assigned to a file yet (CreateAsset()). In my case, I had an "unchanged settings" file that the user could reroll to, and a "current settings" file that is editable. The latter wasn't saved to a file and was causing the scene to become marked as dirty. I've started using a temp file for the current settings and this solved the problem.
     
  4. cmcpasserby

    cmcpasserby

    Joined:
    Jul 18, 2014
    Posts:
    315
    i just use version control in this case, and revert my scene if it gets saved
     
  5. talecrafter

    talecrafter

    Joined:
    Mar 26, 2013
    Posts:
    34
    I do also use version control, but this get's annoying.

    On further examination I saw that ProjectWindowUtil.CreateAsset is the culprit here. AssetDatabase.CreateAsset creates the asset without changing the scene, ProjectWindowUtil.CreateAsset flags the scene as changed, although both serve the same function. The difference being that ProjectWindowUtil.CreateAsset also initializes naming of the asset, which is useful for my purpose.
    You can also see that with the standard resources: Rightclick in the project window -> Create -> Material. The scene gets marked as changed.

    I reported a bug for this because I think that this is unwanted behaviour.
     
  6. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Why is the scene marked as changed is an unwanted behaviour? Just ignore it.
     
  7. talecrafter

    talecrafter

    Joined:
    Mar 26, 2013
    Posts:
    34
    I say it is unwanted behaviour because AssetDatabase.CreateAsset doesn't flag the scene as changed and it is unneeded. I would agree that it's more of a minor thing, but when often "discarding changes" in git becomes part of the daily workflow I would say it's worth fixing.
     
    ModLunar likes this.
  8. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    You are aware that ScriptableObject/Asset can be save inside a scene... So I would assume that when you first create one (CreateInstance), the instance is assigned to the world. When you save your scene, the hierarchy would be traversed, and if nothing reference the SO, it would be ignored. However, I don't see how Unity would be able to know that until it happens.

    Saving to disk is actually the second step, and it's optional. The first step already happened, and it's kinda too late.

    The real issue is that Unity doesn't have separated memory to handle active items. If an item is loaded, it's in the "internal scene".
     
  9. talecrafter

    talecrafter

    Joined:
    Mar 26, 2013
    Posts:
    34
    I am aware that ScriptableObjects can be part of a scene. That's why I pondered about the hideflags.

    But if you take this Utility Method:

    Code (CSharp):
    1. public static T CreateAsset<T>(string customName = "") where T : ScriptableObject
    2. {        
    3.     T asset = ScriptableObject.CreateInstance<T>();
    4.     string path = AssetDatabase.GetAssetPath(Selection.activeObject);
    5.     if (path == "")
    6.     {
    7.         path = "Assets";
    8.     }
    9.     else if (Path.GetExtension(path) != "")
    10.     {
    11.         path = path.Replace(Path.GetFileName(AssetDatabase.GetAssetPath(Selection.activeObject)), "");
    12.     }
    13.     if (customName == "")
    14.         customName = "New " + typeof(T).ToString();
    15.     string assetPathAndName = AssetDatabase.GenerateUniqueAssetPath(path + "/" + customName + ".asset");
    16.     ProjectWindowUtil.CreateAsset(asset, assetPathAndName);
    17.     return asset;
    18. }
    and replace

    Code (CSharp):
    1. ProjectWindowUtil.CreateAsset(asset, assetPathAndName);
    with

    Code (CSharp):
    1. AssetDatabase.CreateAsset(asset, assetPathAndName);
    it works without flagging the scene as changed. Everything else exactly the same. Therefore I assume it is possible to save Assets without changing the scene.
    But I would of course like to use ProjectWindowUtil, because that initializes the renaming of the asset.