Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 12 Next capture
2021 2022 2023
1 capture
12 Jun 22 - 12 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by awardell · Apr 26, 2017 at 05:55 AM · c#serializationcustom classvalidation

How can I get functionality similar to OnValidate for a custom class?

I have a class similar to as follows:

 [Serializable]
 public class MySerializableClass
 {
     [SerializeField]
     private string someField;

     public void OnValidate()
     {
         Debug.LogError("OnValidate()");
         //perform validation code here
     }
 }

I would like the code in OnValidate to run, similarly to how it runs for MonoBehaviours and ScriptableObjects. However, I have a couple of issues achieving this:

  • This class is not and cannot be a MonoBehaviour nor a ScriptableObject, so OnValidate will not simply run.

  • The code I need to run requires methods on the Unity main thread, such as UnityEditor.AssetDatabase.GetAssetPath(). Because of this, I cannot use ISerializationCallbackReceiver.OnAfterDeserialized(), which runs on a separate thread.

Are there any options available for what I am trying to accomplish? I really want to avoid having to explicitly call OnValidate from any and every class which contains instances of MySerializableClass, as that would defeat the purpose of what I'm trying to do.

Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

4 Replies

· Add your reply
  • Sort: 
avatar image
5
Best Answer

Answer by Bunny83 · Apr 26, 2017 at 09:39 AM

OnValidate is called by Unity on Components whenever a serialized property of that component is changed. That includes "sub-properties" in custom classes. Unity doesn't serialize custom classes on their own. They are simply treated like "structs". So all properties of a custom class simply become properties of the MonoBehaviour class that exposes your custom class.

So when you change a variable in the inspector, no matter if it's an actual field of the MonoBehaviour or if it's nested in a custom class, Unity will call the OnValidate method of the MonoBehaviour.

If you want to apply special handling in the custom class itself, you can simply add your own OnValidate method and call it from the MonoBehaviour that contains that class:

 [System.Serializable]
 public class CustomData
 {
     public int someValue;
     public void OnValidate()
     {
         Debug.Log("ChildData::OnValidate");
     }
 }
 
 
 public class MyComponent : MonoBehaviour
 {
     public int componentField = 42;
     public CustomData data;
     void OnValidate()
     {
         Debug.Log("MyComponent::OnValidate()");
         if (data != null)
             data.OnValidate()
     }
 }

So the OnValidate method inside CustomData now just works exactly the same as the OnValidate of the containing MonoBehaviour.

edit
If you set your asset serialization mode to "force text", scene assets (as well as prefabs) will be serialized as YAML text. Here's how my example looks like as serialized data:

 MonoBehaviour:
   m_ObjectHideFlags: 0
   m_PrefabParentObject: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
   m_GameObject: {fileID: 1140048734}
   m_Enabled: 1
   m_EditorHideFlags: 0
   m_Script: {fileID: 11500000, guid: 001aa7c4a3d39e846af08362b2b2f495, type: 3}
   m_Name: 
   m_EditorClassIdentifier: 
   componentField: 0
   data:
     someValue: 5

As you can see at the end of the internally serialized data there are our serialized fields. "componentField" is a direct member of our MonoBehaviour as well as "data". All the serialized fields of our "CustomData" class are simply sub-fields of the monobehaviour.

So if you simply do the validation in the MonoBehaviour, you don't have to do anything special. OnValidate will be called when you change sub-fields in the inspector. If you want to handle the specific validate code for your sub class inside that class, just "delegate" the OnValidate event like i showed in my code example.

Comment
Add comment · Show 3 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image awardell · Apr 26, 2017 at 04:10 PM 1
Share

I appreciate your detailed comment, however at the end of my question I stated:

"I really want to avoid having to explicitly call OnValidate from any and every class which contains instances of $$anonymous$$ySerializableClass, as that would defeat the purpose of what I'm trying to do."

I understand that OnValidate of the $$anonymous$$onoBehaviour will be called. I've used that plenty in the past. But that is not what I want. I would like to be able to have this class, $$anonymous$$ySerializableClass, to validate itself and not require me to explicitly call the method from the $$anonymous$$onoBehaviour which contains it.

That is why I attempted using ISerializationCallbackReceiver.OnAfterDeserialized(), because that method is automatically called when $$anonymous$$ySerializableClass is deserialized. The problem with that method is that I cannot access Unity code.

avatar image Bunny83 awardell · Apr 26, 2017 at 06:38 PM 1
Share

That's not possible. Classes don't do anything by themselfs. There are systems / framesworks which use certain patterns / interfaces / conventions and may invoke a method when a certain event happens. Unity treats custom classes like structs, pure data-containers. The ISerializationCallbackReceiver is one of those special things they implemented to allow special handling before and after serialization. There is no built-in concept for OnValidate for custom classes. So you have to roll your own solution.

I agree that it would be nice if Unity had more points in their internal event structure where you can "hook in". The introduction of the ISerializationCallbackReceiver some time ago was a huge improvement. However there's always room for improvements.

You can try posting a feature request. However unless "enough" people vote for it, it won't be implemented. Also keep in $$anonymous$$d that feature requests usually take at least a year (usually longer) to actually be implemented in an update. So if you need a solution now, you really need to roll your own solution for now.

You can still use ISerializationCallbackReceiver.OnBeforeSerialize but you would have to delegate the changes into a job queue that is processed by EditorApplication.update for example.

Another solution might be to implement a PropertyDrawer for your class. However inside a property drawer you only have direct access to the serialized data and not the actual instance. There are ways to get the actual instance but it's a quite hacky way.

avatar image awardell Bunny83 · Apr 26, 2017 at 08:45 PM 0
Share

These all seem like good suggestions. I'll try one of those two last things you suggested, and maybe put in a feature request. I have a friend who just now told me he did something very similar, and he did it with a PropertyDrawer but it was indeed very hacky. I'll mark your answer since this seems like the best thread.

avatar image
7

Answer by andrew-lukasik · Sep 08, 2017 at 08:24 AM

Implement ISerializationCallbackReceiver and enjoy simple, clean OnValidate calls:

 [System.Serializable]
 public class ICanHazOnValidate : ISerializationCallbackReceiver
 {
     void OnValidate ()
     {
         /*    (∩^o^)⊃━☆    */
     }
     void ISerializationCallbackReceiver.OnBeforeSerialize () => this.OnValidate();
     void ISerializationCallbackReceiver.OnAfterDeserialize () {}
 }
Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image mikawendt · Oct 10, 2020 at 12:27 PM 1
Share

This should be the top answer

avatar image
2

Answer by Bilelmnasser · Apr 26, 2017 at 07:53 AM

 ////Method 1 using delegate invoking :
 
 public delegate void OnValidateDelegate();
 public class MySerializableClass1
 {
     public OnValidateDelegate ValidationMethod;
     [SerializeField]
     private string someField;
     public string someFieldProperty
     {
         get
         {
             return someField;
         }
         set
         {
             someField = value;
             ValidationMethod.Invoke( );
         }
 
     }
 
 }
 
 public class Exampleusage : MonoBehaviour
 {
     void Start()
     {
         MySerializableClass1 obj = new MySerializableClass1( );
 
 
         obj.ValidationMethod += new OnValidateDelegate( OnValidateExample );
 
     }
     public void OnValidateExample()
     {
         Debug.LogError( "OnValidate()" );
         //perform validation code here
     }
 
 
 
 
 }
 
 ///Method 2 using direct call of code throw set properties:
 
 public class MySerializableClass2
 {
     [SerializeField]
     private string someField;
     public string someFieldProperty
     {
         get
         {
             return someField;
         }
         set
         {
             someField = value;
             OnValidate( );
         }
 
     }
     public void OnValidate()
     {
         Debug.LogError( "OnValidate()" );
         //perform validation code here
     }
 }


Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Bunny83 · Apr 26, 2017 at 09:46 AM 0
Share

This doesn't work as the inspector works on the serialized data. Properties are completely ignored by Unity. This is a way to check for changes at runtime but not inside the editor / inspector at edit time.

There might be some confusion around the word "property". C# / .NET has "properties". However Unity's serialization system calls serialized fields also "properties". Well logically that's correct since they are properties of the class, but not in the C# /.NET sense but in a natural sense. The serialization system abstracts serialized fields as "UnityEditor.SerializedProperty"

avatar image
0

Answer by tanoshimi · Apr 26, 2017 at 06:38 AM

OnValidate is called when a component's exposed properties are changed in the inspector. If your class is not a MonoBehaviour, those properties won't be exposed, so when do you expect this to get called?

Do you simply want to apply validation logic in the variable setter?

Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image awardell · Apr 26, 2017 at 04:02 PM 0
Share

As a Serializable class, if $$anonymous$$ySerializableClass is contained by a $$anonymous$$onoBehaviour, then yes it does show up in the inspector.

 public class $$anonymous$$yContainer : $$anonymous$$onoBehaviour
 {
     public $$anonymous$$ySerializableClass instance;
 }

This code will expose $$anonymous$$ySerializableClass.someField in the inspector to be edited. I would like some way of calling this method when that field is edited. As get/set properties are not exposed in the inspector, perfor$$anonymous$$g validation through a set property is not possible.

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

311 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Multiple Cars not working 1 Answer

Distribute terrain in zones 3 Answers

Instantiating a serializable class and variable 1 Answer

Making a Serializable class that doesn't throw "The class named 'Legacy' is not derived from MonoBehaviour or ScriptableObject!" 0 Answers

How do I make child classes in a list editable from the Inspector? 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges