Reputation: 165
I'm trying to implement a feature that detects a change in the value of another field object reference.
For example, there is an A object, and the B object contains A. The A object is notified by OnValidate when the field of the A object has changed. At this time. Is there any way that "B" can detect the change of "A"?
This feature is only required in the Editor and does not include code in Runtime.
public class A : ScriptableObject
{
[SerializeField]
public string team;
private void OnValidate()
{
Debug.Log($"My team name has been changed to {team}.");
}
}
public class B : ScriptableObject
{
[SerializeField]
public A a;
private void OnValidate()
{
Debug.Log($"A changed.");
}
// How can I detect changes to A and call this functions?
private void OnATeamChanged()
{
Debug.Log($"A's team name has been changed to {a.team}.");
}
private void OnADestroyed()
{
Debug.Log($"A is missing.");
}
}
Upvotes: 2
Views: 4371
Reputation: 90580
I would also propose to use events but be careful how and when you register and remove listeners!
public class A : ScriptableObject
{
// You don't need [SerializeField]
// public properties are serialized anyway
public string team;
public event Action onChanged;
public event Action onReset;
public event Action onDestroyed;
private void OnValidate()
{
Debug.Log($"My team name has been changed to {team}.");
if(onChanged == null) return;
onChanged.Invoke();
}
// This is called on reset
// But if you override this you have to set
// The default values yourself!
private void Reset()
{
team = "";
if (onReset == null) return;
onReset.Invoke();
}
// Called when object is destroyed
private void OnDestroyed()
{
if(onDestroyed == null) return;
onDestroyed.Invoke();
}
}
But now in B
I would not add the listener on Awake
since this adds it after every recompile and multiple times! Instead don't forget to "clean up" all listeners you ever add to avoid getting NullReferenceExceptions:
public class B : ScriptableObject
{
// You don't need [SerializeField] since public properties
// are serialized anyway
public A a;
// Internal for removing listeners later
private A lastA;
private void OnValidate()
{
// Apparently something else was changed
if(a == lastA) return;
Debug.Log("reference a changed");
// Unregister listeners for old A reference
if(lastA)
{
lastA.onChanged -= OnAChanged;
lastA.onReset -= OnAReset;
lastA.onDestroyed -= OnADestroyed;
}
// Register listeners for new A reference
// Note that it is allways possible to remove listeners first
// even if they are not there yet
// that makes sure you listen only once and don't add multiple calls
if(a)
{
a.onChanged -= OnAChanged;
a.onReset -= OnAReset;
a.onDestroyed -= OnADestroyed;
a.onChanged += OnAChanged;
a.onReset += OnAReset;
a.onDestroyed += OnADestroyed;
}
lastA = a;
}
// Make allways sure you also remove all listeners on destroy to not get Null reference exceptions
// Note that it is allways possible to remove listeners even if they are not there yet
private void OnDestroy()
{
if(!a) return;
a.onChanged -= OnAChanged;
a.onReset -= OnAReset;
a.onDestroyed -= OnADestroyed;
}
private void OnAChanged()
{
Debug.Log("A was changed");
}
private void OnAReset()
{
Debug.Log("A was reset");
}
private void OnADestroyed()
{
Debug.Log("a was destroyed");
}
}
Small optional change if you want
If you want additionally to be able to register other listeners in the Inspector the same way you do for Button
s you could simply exchange the
public event Action xy;
with
public UnityEvent xy;
and remove the if(xy == null)
checks.
You would than also replace
a.onChanged -= OnAChanged;
a.onChanged += OnAChanged;
by
a.onChanged.RemoveListener(OnAChanged);
a.onChanged.AddListener(OnAChanged);
Upvotes: 1
Reputation: 10701
You could try to add an event
public class A : ScriptableObject
{
[SerializeField]
public string team;
public Action RaiseChangeName;
private void OnValidate()
{
Debug.Log($"My team name has been changed to {team}.");
if(RaiseChangeName != null) { RaiseChangeName(); }
}
}
public class B : ScriptableObject
{
[SerializeField]
public A a;
void Awake()
{
a.RaiseChangeName += OnATeamChanged;
}
// How can I detect changes to A and call this functions?
private void OnATeamChanged()
{
Debug.Log($"A's team name has been changed to {a.team}.");
}
}
Upvotes: 1