1
\$\begingroup\$

I'm working on implementing room prefabs for my 2D procedural map generation algorithm.

Basically I made a tool which parses a scene's

  • monsters, items, etc into an ObjectContainer prefab,
  • and into a ScriptableObject containing the tilemap's TileDatas, EntryTiles, and the ObjectContainerPrefab.

It works well, but my problem occurs when I try to build the game, or just restart the editor.

The TileData array will be empty.

And thus for example the EntryTiles' TileData reference will be null.

Here 's how I use them:

[MenuItem("Custom Tools/Parse room prefab from scene")]
private static void ParseRoomPrefabFromScene()
{
    var tilemap = GameObject.Find("Tilemap").GetComponent<Tilemap>();
    RoomPrefab roomPrefab = ScriptableObject.CreateInstance<RoomPrefab>();
    ParseTilemap(roomPrefab, tilemap);
    ParseObjects(roomPrefab, tilemap);
    ParseEntryTiles(roomPrefab, tilemap);
    AssetDatabase.CreateAsset(roomPrefab, "xyz.asset");
    AssetDatabase.SaveAssets();
}

// How I setup my scriptable object, i.e. in ParseTilemap:
var tileDatas = new List<TileData>();
...
    var tileData = new TileData(coordinates, unityTile, isWall);
    tileDatas.Add(tileData);
...
roomPrefab.tileDatas = tileDatas.ToArray();

Here are the classes:

public class RoomPrefab : ScriptableObject
{
    public TileData[] tileDatas;
    public EntryTile[] entryTiles;
    public ObjectContainerPrefab ObjectContainerPrefab;
}

[Serializable]
public class TileData
{
    public TileBase UnityTile
    {
        get;
        private set;
    }

    public Vector2Int Coordinates
    {
        get;
        set;
    }

    public bool IsWall
    {
        get;
        private set;
    }

    public TileData(Vector2Int coordinates, TileBase unityTile, bool isWall)
    {
        Coordinates = coordinates;
        UnityTile = unityTile;
        IsWall = isWall;
    }
...

[Serializable]
public class EntryTile
{
    public TileData TileData
    {
        get;
        private set;
    }

    public Vector2Int LeaveDirection
    {
        get;
        private set;
    }

    public EntryTile(TileData tileData, Vector2Int leaveDirection)
    {
        TileData = tileData;
        LeaveDirection = leaveDirection;
    }
...

[Serializable]
public class ObjectContainerPrefab
{
    public Vector2Int Coordinates
    {
        get;
        private set;
    }

    public float Rotation
    {
        get;
        private set;
    }

    public GameObject Prefab
    {
        get;
        private set;
    }

    public ObjectContainerPrefab(Vector2Int coordinates, float rotation, GameObject prefab)
    {
        Coordinates = coordinates;
        Rotation = rotation;
        Prefab = prefab;
    }
}

UPDATE: here is a freshly parsed scriptable object. In the Editor everything is fine, while in its file its tileDatas array is already empty. Why?

%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: 30263215ad3a45a479bb2dd6409fb0a9, type: 3}
  m_Name: TestRoom
  m_EditorClassIdentifier: 
  tileDatas: []
  entryTiles:
  - DebugCube: {fileID: 0}
  - DebugCube: {fileID: 0}
\$\endgroup\$
6
  • \$\begingroup\$ Please post your code as text, not as images. Be sure to show us where you're calling Undo.RecordObject to ensure changes made via script are visible to Unity's serialization pass and saved when you click Save Project. \$\endgroup\$
    – DMGregory
    Commented Sep 28, 2019 at 19:43
  • \$\begingroup\$ Sorry, I thought that copying multiple classes would be a bit fuzzy, and a small image would give a clearer vision. Hmm, Undo.RecordObject? I call AssetDatabase.CreateAsset() and AssetDatabase.SaveAssets() to save the scriptable object. \$\endgroup\$
    – Tudvari
    Commented Sep 30, 2019 at 9:48
  • \$\begingroup\$ Show us that then, where it actually happens in the context of your script? All we can see here are your variables and constructors, so it's hard to get a sense of when this code gets run, how the values are populated into the various fields, and where you might need to add hints for the serializer. \$\endgroup\$
    – DMGregory
    Commented Sep 30, 2019 at 10:30
  • \$\begingroup\$ Sorry, I edited the post, hope it's clearer now. \$\endgroup\$
    – Tudvari
    Commented Sep 30, 2019 at 11:20
  • \$\begingroup\$ is this your problem? \$\endgroup\$ Commented Oct 17, 2019 at 14:44

3 Answers 3

4
\$\begingroup\$

It was due to having auto properties on serialized classes:

public TileData TileData
{
    get;
    private set;
}

Using properties with [SerializeField] private fields fixes it:

[SerializeField]
private TileData tileData;
public TileData TileData
{
    get { return tileData; }
    private set { tileData = value; }
}

Or if you are lazy, just use [field: SerializeField]:

[field: SerializeField] public TileData TileData {get; private set;}

UPDATE:

  • It was still losing references,

  • solve it by calling EditorUtility.SetDirty() before AssetDatabase.SaveAssets()

\$\endgroup\$
2
  • 2
    \$\begingroup\$ EditorUtility.SetDirty() also was the missing piece for me. I assigned a freshly created ScriptableObject to another, already existing ScriptableObject and much trouble before finding your answer! \$\endgroup\$ Commented Apr 19, 2023 at 20:11
  • \$\begingroup\$ Once I set SetDirty it worked without any change to auto properties. Having auto properties keep the code clean, so won't change that part. Instead call the SetDirty. \$\endgroup\$
    – Ayyappa
    Commented May 20, 2023 at 5:15
0
\$\begingroup\$

As I hinted at in the comments, it looks like you're missing a call to Undo.RecordObject() just before you start populating fields like the tile map data on your ScriptableObject

This gives the editor a heads-up: "I'm about to make changes to this object, so note what it looked like beforehand"

After the current editor frame, Unity looks through all the objects that were tagged this way, and does a binary diff against their cached state from when you called RecordObject. Where the state is the same, it discards it as a no-op. Where it differs, it creates an entry in the Edit→Undo history to be able to undo the change, and marks the new, modified state of the object as in need of saving.

Then the next time you save your assets (manually or via script), these marked objects are the ones that get saved to disc.

Unity doesn't try to do a diff on every bit of data in your whole project to figure out what's changed (that would make saving way too slow in big projects), so it's up to you to tell the editor which objects you're modifying through scripts so it can add them to its shortlist for the next save.

\$\endgroup\$
3
  • \$\begingroup\$ I added " Undo.RecordObject(roomPrefab, "RoomPrefab created") " just after I created the ScriptableObject instance (so before the parsing themselves occur), but it didn't fix it. \$\endgroup\$
    – Tudvari
    Commented Sep 30, 2019 at 11:50
  • \$\begingroup\$ Did I do something wrong? Why doesn't it fix it? :\ \$\endgroup\$
    – Tudvari
    Commented Oct 7, 2019 at 18:43
  • \$\begingroup\$ Found the source of the issue, check out my answer. \$\endgroup\$
    – Tudvari
    Commented Oct 12, 2019 at 4:37
-1
\$\begingroup\$

Faced similar problem when working with ScriptableObjects when build or re-open the project. Actually this issue is not in your side. Issue belongs to here

[MenuItem("Custom Tools/Parse room prefab from scene")]

It's seems funny but whenever you worked with ScriptableObjects and create any Menu item where ScriptableObjects is there, you have to remove any space in Menu/SubMenu name. You can change the names like this

[MenuItem("CustomTools/ParseRoomPrefabFromScene")]

instead of previous one. Hit a try. I solved my problem with this silly solution.

N.B: If you try this in your existing project, You have to delete those scripts where ScriptableObjects referenced and ScriptableObjects. Then re-open your project so that temp file and solutions being reset. After that you create those scripts and make sure the Menu Item names without space. Hope your problem will be solved.

\$\endgroup\$
2
  • \$\begingroup\$ This seems a so big of bug that it should have been reported somewhere. Removing spaces didn't solve the issue, And that script deleting thing seems a bit unrealistic to me, but anyway I deleted the project files, temp files manually, and it didn't solve the case :\ \$\endgroup\$
    – Tudvari
    Commented Oct 12, 2019 at 4:01
  • \$\begingroup\$ Found the source of the issue, check out my answer. \$\endgroup\$
    – Tudvari
    Commented Oct 12, 2019 at 4:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .