- Home /
Texture2D in ScriptableObject’s Property drawer experiences serious lag
I have created a simple test scriptable object (edit: tests show same behavior with custom serializable objects, that are NOT ScriptableObject derived):
public class TestScriptableObj : ScriptableObject {
public string title;
public Texture2D insignia;
}
And used it in a simple MonoBehavior:
public class Monotest : MonoBehaviour {
public TestScriptableObj tester;
void Reset () {
tester = TestScriptableObj.CreateInstance<TestScriptableObj>();
tester.title = "testTitle2";
}
}
I also created a property drawer for it. In order to make the insignia show the texture, like on a material inspector, I was unable to use EditorGUI.PropertyField, and needed to use EditorGUI.ObjectField, and specify the type as Texture2D.
[CustomPropertyDrawer(typeof(TestScriptableObj))]
public class TestScriptableObjectPropertyDrawer :PropertyDrawer{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (property.objectReferenceValue == null)
{
EditorGUI.HelpBox(position, "Null object reference ", MessageType.Error);
return;
}
EditorGUI.BeginProperty(position, label, property);
//since TestScriptableObj is a ScriptableObject derivation (a Unity class)- we need to do this backasswards stuff, and get the object from the property: FindPropertyRelative will return null
SerializedObject propObj = new SerializedObject(property.objectReferenceValue);
SerializedProperty title = propObj.FindProperty("title");
SerializedProperty insignia = propObj.FindProperty("insignia");
position.height = EditorGUIUtility.singleLineHeight; position.width -= 80;
EditorGUI.PropertyField(position, title, label);
position.x = position.xMax; position.width = 80; position.height = 80;
insignia.objectReferenceValue = EditorGUI.ObjectField(position, GUIContent.none, insignia.objectReferenceValue, typeof(Texture2D), true);
propObj.ApplyModifiedProperties();
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return 80f;
}
}
This works: kind-of. The displayed texture consistently fails to update properly. When it does eventual update, the time it takes to do so, is inconsistent, and seems to depend on what I do with the mouse. Before it updates, there is NO texture shown- just the field background.
What is causing the delayed update? Or, does nobody else experience these delays?
Edit/upadte:
NO!! Not this solution again!! New main question is: What the heck? Surely users of my ScriptableObject asset are not required to specify a do-nothing-custom-editor for their classes that use it, do they? Adding the following do-nothing script resolves the issue:
using UnityEditor;
[CustomEditor(typeof(Monotest))]
public class MonoTestEditor : Editor {}
@Bunny83 you helped me understand why the very same solution presented above (do-nothing-custom-editor, update at bottom of Q) fixed a completely different issue: http://answers.unity3d.com/questions/1094473/strange-resolution-to-editor-error-explanation-req.html This issue doesn't not appear to be a Layout system type issue, as I'm not using any such elements. Perhaps you have some advice?
Submitted as a bug: https://fogbugz.unity3d.com/default.asp?947428_63j0ogjjrdnlsjm1
Answer by Adam-Mechtley · Sep 01, 2017 at 05:52 PM
This is a bug as a consequence of an implementation detail in Unity.
What is happening here is that Unity internally has a concept of what are called optimized GUI blocks in the Inspector. The idea is that the Editor tries to avoid redrawing stuff unnecessarily unless the user actually changes/interacts with something. This optimization helps tremendously with things like big arrays.
Whenever users define their own custom Editors, this code path is effectively ignored, because we don't want to make assumptions about what might need to be updated in the user's Editor.
Right now, textures drawn with GUI.DrawTexture (which is what this thumbnail uses internally) do not properly register themselves with the optimized GUI block. (The same goes for some things like Handles.) I'm looking into it, but it's unfortunately not a trivial fix. On the plus side, 2017.3 adds a method to PropertyDrawer, CanCacheInspectorGUI(), which you can override to disable the optimized path for individual property drawers. I confirmed that it fixes the problem in this case.
Answer by HenryStrattonFW · Feb 11, 2017 at 07:51 PM
This is likely due to the fact that the GUI does not repaint every "frame" like you would in a game. The editor GUI only updates and repaints as it needs to in response to various events. So what I think is happening here is that you're data is updating immediately, then when you move your mouse over the inspector, or some other event occurs that causes the GUI to update, you then see the inspector draw the new data, with the perceived delay having occurred.
However, you can get around this. I've not personally needed this yet for any of my drawers so I cant give exact code solution, however there is a post that has a number of suggested examples that appear to have success for various people on the post. So I'd suggest giving one of those a try.
http://answers.unity3d.com/questions/505697/how-to-repaint-from-a-property-drawer.html
Thanks for the input Henry. I tried a bunch of the suggestion on that link, but none helped, so I tried a sanity check to confirm if it was in-fact a repainting issue. I added the following line after the ObjectField
EditorGUI.DrawTextureTransparent(position, (Texture)insignia.objectReferenceValue, Scale$$anonymous$$ode.StretchToFill);
This call draws the texture every-time, without delay or issues, a soon as the inspector is shown. Which implies the PropertyDrawer does have the correct texture every-time it is drawn. Why does the ObjectField behave otherwise?
hmm interesting, I'm going to fire up unity and paste in your scripts, see if I can work out a way to get it to work.
Awsome! FYI: also added position.y+=80;
before DrawTexture line, and changed GetPropertyHeight to return 160f;
Answer by Bunny83 · Feb 17, 2017 at 01:30 PM
I'm not sure what might cause your updating problem, but it might still be related to the missing layout event. Maybe somethings else. Usually Unity will automatically repaint the inspector when an inspected object has changed. Though the object you are changing is not really "inspected". You can try to manually repaint the inspector when the value has changed. Unfortunately only custom Inspectors can request a redraw.
The Repaint method of the Editor class just calls the static InspectorWindow.RepaintAllInspectors();
method. Unfortunately this class and method are internal. So there are two ways to invoke it manually:
using reflection
using a temp "Editor" instance
This might work:
if(propObj.ApplyModifiedProperties())
{
var tmp = ScriptableObject.Createinstance<Editor>();
tmp.Repaint();
DestroyImmediate(tmp);
}
How exactly do you assign your texture? Do you use the object selector or do you use drag&drop? Is there a difference between the two ways?
You also might want to add a propObj.Update();
after you create the serialized object, just in case.
Hi Bunny, away for a week, but will test your repaint suggestion upon return. Texture is assigned either with drag and drop or by hitting the select button, or hard coded. Same issue no matter the assignment method. Oh, also tried the update as suggetzed, no help.
Alas, calling the repaint method as you suggested, didn't help. I also tried other repaint methods like: EditorUtility.SetDirty(property.objectReferenceValue);
and EditorApplication.update.Invoke();
I should have added it to the initial Q, but the first comment on Henry's Answer had some interesting results, I suspect this clue is critical. Simply drawing the texture with EditorGUI.DrawTextureTransparent(position, (Texture)insignia.objectReferenceValue, Scale$$anonymous$$ode.StretchToFill);
ALWAYS works/updates properly.
I will attach a zipped version of my current test project to the initial Q, in a moment. edit: oops max attachements on Q... here it is: link text