Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

[SOLVED] list of classes with struct - changing one struct changes them all

Discussion in 'Scripting' started by TehGM, Sep 16, 2016.

  1. TehGM

    TehGM

    Joined:
    Nov 15, 2013
    Posts:
    89
    Hi, I basically have a struct:
    Code (csharp):
    1. [Serializable]
    2. public struct Price : IEquatable<Price>, IComparable<Price>
    3. {
    4.     [SerializeField]
    5.     private double _value;
    6.     /*  . . . */
    7. }
    The struct has a property drawer for it:
    Code (csharp):
    1. [CustomPropertyDrawer(typeof(Price))]
    2. public class PricePropertyDrawer : PropertyDrawer
    3. {
    4.     SerializedProperty value;
    5.  
    6.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    7.     {
    8.         if (value == null)
    9.         {
    10.             property.Next(true);
    11.             value = property.Copy();
    12.         }
    13.  
    14.         // now we can actually assign the value
    15.         value.doubleValue = EditorGUI.DoubleField(position, label, value.doubleValue);
    16.     }
    17. }
    This works fine when used in ScriptableObject, Monobehaviour etc directly.

    However, I have non-unity class using it as a member:
    Code (csharp):
    1. [Serializable]
    2. public class ShoppingOrder
    3. {
    4.     [SerializeField] private ProductTemplate _product;
    5.     [SerializeField] private uint _quantity;
    6.     [SerializeField] private Price _maxPrice;
    7.     /* . . . */
    8. }
    And a monobehaviour that has list of ShoppingOrders:
    Code (csharp):
    1. public class Robot : MonoBehaviour
    2. {
    3.     public Price Money;
    4.     public List<ShoppingOrder> Orders;
    5.     /* . . . */
    6. }
    And this is where the problems start. Unity Inspector shows the list. However, when I want to modify MaxPrice of one order, all of them get changed.
     
    Last edited: Sep 16, 2016
  2. MSplitz-PsychoK

    MSplitz-PsychoK

    Joined:
    May 16, 2015
    Posts:
    1,278
    I think it's a Unity Editor bug... you aren't normally allowed to edit private data in the editor unless it's serialized. Even if you could enter unique values in those two fields, the values would be lost upon running the game.

    I recommend making "_maxPrice" a [SerializedField] and that might fix the problem.
     
  3. TehGM

    TehGM

    Joined:
    Nov 15, 2013
    Posts:
    89
    Yes, _maxPrice already has [SerializeField] attribute. I've edited the first post a bit to make it more visible - I'm guessing Tooltip attribute made it less visible.
     
  4. MSplitz-PsychoK

    MSplitz-PsychoK

    Joined:
    May 16, 2015
    Posts:
    1,278
    I'm digging through the API docs, and it seems custom property drawers need a "EditorGUI.BeginProperty()" and "EditorGUI.BeginProperty()".

    Quote: "Most EditorGUI and EditorGUILayout GUI controls already have overloads that work with SerializedProperty. However, for GUI controls that don't handle SerializedProperty you can wrap them inside BeginProperty and EndProperty"

    https://docs.unity3d.com/ScriptReference/EditorGUI.BeginProperty.html
    https://docs.unity3d.com/ScriptReference/EditorGUI.EndProperty.html
     
  5. TehGM

    TehGM

    Joined:
    Nov 15, 2013
    Posts:
    89
    Thank you for your reply - I wrapped the code in those 2 methods, and removed and readded the component to the game object - just to be sure.
    However, the issue still persists.
     
  6. TehGM

    TehGM

    Joined:
    Nov 15, 2013
    Posts:
    89
    Update: the issue is definetely with Property Drawer - when removed, each value can be edited independently.
    I still hope there's a way to fix it, though.

    Update 2:
    Doing this (yes, messy code, but it's just quick workaround test):
    Code (csharp):
    1. public override void OnGUI(Rect position, SerializedProperty property, GUIContent label){
    2.     EditorGUI.BeginProperty(position, label, property);
    3.     SerializedProperty value = null;
    4.     if (value == null)
    5.     {
    6.         property.Next(true);
    7.         value = property.Copy();
    8.     }
    9.  
    10.     // now we can actually assign the value
    11.     value.doubleValue = EditorGUI.DoubleField(position, label, value.doubleValue);
    12.     EditorGUI.EndProperty();
    13. }
    Makes it work. Is there a way to avoid value look up with each OnGUI update, though?
     
  7. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,199
    You should file a bug report with the issue!

    That being said, the way propertyDrawer are mostly used in the example code is using string-based lookup. So something like:

    Code (csharp):
    1. public override void OnGUI(Rect position, SerializedProperty property, GUIContent label){
    2.     var value = property.FindPropertyRelative("_value");
    3.     EditorGUI.PropertyField(position, label); //autos to the default drawer for _value, ie. a doubleField
    4. }
    It looks really bad (reflection-based lookup every frame? ugh!), but this is editor code, and in my experience not that slow.

    I believe that you're seeing the bug because PropertyDrawers are (unlike editors) not really per-object. We haven't really been able to nail down when they're re-created and when they're reused, but it's somewhat arbitrary. Caching data might lead to a speedup, but you have to have handle your property suddenly getting a new drawer.
     
  8. NickAtUnity

    NickAtUnity

    Unity Technologies

    Joined:
    Sep 13, 2016
    Posts:
    84
    Baste is pretty much right on all counts. I don't believe this is a "bug", per se, because PropertyDrawers are not guaranteed to be created per-property so caching things in them is not a good idea. The correct pattern is to look up the property in OnGUI as in Baste's example.

    Also, looking it up using FindPropertyRelative also tends to be a better way to ensure you're getting the right serialized property for your value.
     
  9. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,199
    @NickAtUnity, the Editor class has a very nice property where you can create an editor through Editor.CreateEditor, and then re-use it in other editors. This is nice for custom windows or whatnot, you can create some editors for some objects, and then lay them out in a neat context or whatever.

    How much work would it be to allow something similar for PropertyDrawers? If we could create a PropertyDrawer object from script, targeting a specific SerializedProperty, and then cache that specific PropertyDrawer, we could do some serious caching and optimization to draw some editors. As of now, we can't really cache per-property data, so we end up having to do static object-to-data dictionaries to be able to have SerializedProperties that have slow-to-generate data.
     
  10. NickAtUnity

    NickAtUnity

    Unity Technologies

    Joined:
    Sep 13, 2016
    Posts:
    84
    @Baste, do you have an example of when you need those optimizations? I've not encountered performance issues with the current system, but it's entirely possible I'm simply not using as large or complex of data as you have in your project. Can you create some small-ish example project that shows a performance impact where caching would improve the performance?

    I don't work on the team that would work on this feature so I can't speak to its feasibility, but if there's a good case to be made for performance improvements I'm sure the team would like to hear it. :)