- Home /
Efficiently serializing large collision grid
I am currently making a Tile-Map class in Unity with a custom editor. This TileMap currently only contains a 2-D grid, with each tile being either collidable or non-collidable. At the moment, my TileMap class looks something like:
public class TileMap : MonoBehaviour {
public int cols = 100;
public int rows = 100;
public bool[] collisionGrid;
}
The collisionGrid
variable is a flattened 2D array of size cols * rows
, and represents each grid space on the 2D grid. If collisionGrid[r * cols + c] = true, then that grid space is impassable.
My TileEditor
script is a class that extends Editor. It gets the mouse position and, if the mouse is clicked, sets the corresponding tile to true.
void OnSceneGUI(){
//Get Mouse position and appropriate tile index
if(e.type == EventType.MouseDown || e.type == EventType.MouseDrag){
tMap.collisionGrid[tileIndex] = true;
}
EditorUtility.SetDirty(tMap);
}
My problem is that, while this does what it is supposed to do, the editor slows to almost a halt. Clicking and dragging to add new collidable positions is next to impossible, and even attempting to pan the camera has noticeable delay.
There are several things I have tried already to resolve this issue:
Changing the dimensions from 100 x 100 to, say, 20 x 20, then everything works smoothly. However, I specifically want to make larger tile maps, And I feel like 100 x 100 is hardly a demanding size to work with.
Removing the "EditorUtility.SetDirty(tMap)". However, If I do this, Unity will not persist my collisionGrid properties between sessions. I need it to persist.
Moving the "EditorUtility.SetDirty(tmap)" inside of the "if" condition. This resolves the problem of regular Unity functionality (like panning and zooming) being sluggish, but doesn't resolve the core problem, that editing the grid of collision values is so slow as to be unusable.
So my question is, what am I doing wrong? How can I edit a moderately sized grid in real-time and have it persist? Is it a case of calling EditorUtility.SetDirty() sometimes, but not every frame? Or something smarter than that?
Answer by esg-tim · Oct 15, 2015 at 04:41 PM
You have a few avenues to explore:
You could try targeting the
SerializedProperty
of the collision by using// when setting var tileProperty = serializedObject.FindProperty("collisionGrid").GetArrayElementAtIndex(tileIndex); tileProperty.boolValue = true; // to apply serializedObject.ApplyModifiedProperties();
I'm not sure, but this may be faster, and only copy the changed properties of the object.
You could add a cached version of the array that the editor changes when working, and then try to make it call "SetDirty" only after the user has been idle for more than 3 (or whatever) seconds.
You can ensure the cached field exists only in the editor using
#if UNITY_EDITOR
preprocessor blocks. You can make theTileMap
choose to draw from the cached field first before the actual values when in the editor.Setting a timer in the Editor could be accomplished in a few ways. A simple way would be to add a listener to
EditorApplication.update
that looks for whenEditor.timeSinceStartup - idleStartTime > 3.0d
, then copies the cache and callsEditorUtility.SetDirty(targetObject)
if it needs to. UpdateidleStartTime
toEditorApplication.timeSinceStartup
every time the mouse is down.
@esg-tim with regards to the second option, this would indeed take some of the burden off of the amount of serializations done. However, would this guarantee that the object is serialized when needed.
As an example, when unity is closed, or the user clicks "play" to run their game, any properties that have not been serialized are reset. So if my SetDirty interval was say, 5 seconds, and 2 seconds after editing, the user clicked "Play", wouldn't they lose the changes they had just made? Ideally, I think I need some way to say "If I am in a situation where I am about to throw away unsaved changes, serialize any changes".
Alternatively i could just make the interval so small as to guarantee the user cant get from the tile map editor to the play button in that amount of time, but at that point, the "SetDirty"s will be so frequent that the problem is back to square one!
You might be able to detect when to serialize on the Tile$$anonymous$$ap
by implementing the ISerializationCallbackReceiver
, which would call OnBeforeSerialize
every time the object needed to be serialized. In this method you could copy the cache into the actual object data. In fact, maybe you wouldn't need the timer at all if you went this route?
Answer by Bunny83 · Oct 15, 2015 at 03:19 AM
Well, when calling SetDirty you mark your array as "dirty" so it will be reserialized to the assetfile where its parent class is actually contained. 100 x 00 are actually 10000 bool values (or 10 kB).
Of course you should only call SetDirty when you actually changed something. So put the call inside that if statement.
Additionally you should implement some kind of enable / diable for your editor. Otherwise it will always execute the check while you have your object selected.
@Bunny83 The two things you have suggested (putting the "SetDirty" inside of the click/drag condition, and allowing the editor to be enabled disabled) solve one part of the problem: Namely that the editor would no longer stutter and slow when panning and zoo$$anonymous$$g etc.
However, it still doesn't really solve the core of the problem. If I move the SetDirty inside the conditional, then this will not prevent the editor from stuttering when I attempt to paint on various collision blocks by clicking and dragging.
I think what I really need is to know some way of having the Unity Editor re-serialize the large grid of values only when the data is required to be persisted rather than re serializing every single time a field in the grid is altered.
@Bunny83 I have now updated the question to take account of your suggestion
Your answer
Follow this Question
Related Questions
How to save sprite color change on custom inspector ? 0 Answers
"Unbroken Reference" problem when using a custom Editor to Save/Load a ScriptableObject Asset 0 Answers
How to insert an element of specific type to a list as serializedProperty? 1 Answer
Finding property with serializedObject on script with a generic 0 Answers