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\$
  • \$\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 Sep 28 '19 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 Sep 30 '19 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 Sep 30 '19 at 10:30
  • \$\begingroup\$ Sorry, I edited the post, hope it's clearer now. \$\endgroup\$ – Tudvari Sep 30 '19 at 11:20
  • \$\begingroup\$ is this your problem? \$\endgroup\$ – Seyed Morteza Kamali Oct 17 '19 at 14:44
2
\$\begingroup\$

It was due to having auto properties on serialized classes:

public TileData TileData
{
    get;
    private set;
}

Using properties with [SerializableField] 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 public members:

public TileData TileData;

(But it's against some design principles, so obviously try not to use them.)

\$\endgroup\$
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\$
  • \$\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 Sep 30 '19 at 11:50
  • \$\begingroup\$ Did I do something wrong? Why doesn't it fix it? :\ \$\endgroup\$ – Tudvari Oct 7 '19 at 18:43
  • \$\begingroup\$ Found the source of the issue, check out my answer. \$\endgroup\$ – Tudvari Oct 12 '19 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\$
  • \$\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 Oct 12 '19 at 4:01
  • \$\begingroup\$ Found the source of the issue, check out my answer. \$\endgroup\$ – Tudvari Oct 12 '19 at 4:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.