I can’t count the number of posts I’ve seen here (or the number of issues I’ve had in my own code) that would be resolved by adding a callback that happens when transform.position is changed. It’d be an insanely easy to add on the callback.
Concerned about too many callbacks impacting performance? Fine, make it Editor-only. 90% of the time it’s just needed for debugging, but holy cow is it needed.
If they’d make it editor only, then people will start complain why not in player even more, than now. Also while there are a lot of cases wich may be solved with this, this is not the only solution possible and this will significanlty slowdown transform in much more cases, where it is completely not needed. So please don’t do it.
Use this instead of tranform so you will always have that event you want.
class VerboseTransform : MonoBehaviour {
public UnityEvent OnPositionChanged;
public Vector3 position {
get => transform.position;
set {
transform.position = value;
OnPositionChanged.Invoke();
}
}
}
Working with transform hierarchies is already super slow in Unity, there’s no need making it even slower.
This solution doesn’t help you find position changes in any code you didn’t write.
And if you find and replace in your code (which still wouldn’t catch stuff like DLLs), then you have the exact same performance problems you’re complaining about for a builtin solution.
What? That doesn’t make any sense. It easily could (and, I assume, would) be written in such a way that if nothing has subscribed to the event, there would be no performance cost.
Where shouldCallback would be a null-check of some sort. That’s expensive at scales like “setting transform’s position”. The editor overhead is bad enough as-is, so enabling this in the editor sounds bad.
If you implement something like this, it has to be hidden behind an #if, which is problematic when talking about built-in things, as those are in precompiled dll’s. You’d literally have to ship two copies of UnityEngine.dll, or incur a bad performance cost.
At worst, it needs to do nothing more than checking against a bool (which only needs to be changed when the “on changed” callback is altered), and you’d need to change transform.position literally millions of times in a frame to see any performance impact from a bool check… and if you’re setting transform.position millions of times in a frame, the performance problem ain’t the bool check.
Setting a bool (which the transform.position setter method obviously must do in order for Transform.hasChanged to work) has a miniscule performance overhead just like checking a bool would, and for drastically less usefulness.
By “at worst”, I mean “this is the best solution I can think of that will definitely 100% work, and the worst case scenario is that no one can think of a better one”, not, “this is the worst possible solution”.
Imagine an implementation that looks something like this:
Vector3 _position = Vector3.zero;
public Vector3 position {
set {
if (doesHavePositionChangeSubscribers) positionChangeCallback(_position, value);
_position = value; //and insert other math etc
}
get {
return _position; //and other math etc
}
}
private bool doesHavePositionChangeSubscribers = false;
private delegate PositionChangeCallback(Vector3 old, Vector3 new);
private event PositionChangeCallback positionChangeCallback = null;
public void SubscribeToPositionChange(PositionChangeCallback pcc) {
positionChangeCallback += pcc;
doesHavePositionChangeSubscribers = true;
}
If implemented this way, it would be nothing more than an added bool comparison. There is no need or reason to make the performance overhead any worse than that.
What if object was destroyed inbetween subscription and invocation, but c# part have not unsubscribed from event (wich is not neccessary in c# and even may be considered bad practice in terms of gc graph fragmentation) ?
Just went a bit forward with the concept and put together a tool that’ll allow you to add events to unity. Could be for transform.position / rotation, or something else like boxCollider.size
(Needed this yet again today, Google brought me here, to make me sad at the fact this is still missing).
The probable real reason Unity never implemented it is that it requires altering some legacy code buried deep inside the Unity Engine/Editor (‘Transform’ is a special class, for performance reasons 20 years ago), that no-one wants to be responsible for rewriting/editing/breaking/fixing. That code was probably badly written in the first place (was a tiny company) but over the decades has become very badly maintained/written, so I’m not surprised no-one has the courage/professional confidence to edit it.
Transform.hasChanged is astonishingly bad code that should never have been published (it’s guaranteed to fail on non-trivial projects and there’s nothing you can do to fix that: the design of it sticks the data in the wrong place, as a global).
For the subset of cases that are RectTransform … I’ve managed to reliably simulate this by implementing the SetLayoutHorizontal() callback, and all the OnRectTransformChange callbacks – and then deducing ‘if it’s a SetLayoutHorizontal but it’s not an OnRectTransformXXXChange, then … it’s the ‘never implemented’ OnRectTransformPositionChange event’. An annoying workaround - but in testing for multiple years has worked reliably - for a core API call Unity never bothered to implement.
(and, of course: it’s not slow at all. That’s not why Unity has failed to add it).
I had a similar issue where I wanted to store transform positional data on a component automatically when moving the transform in the editor. Here’s what I have, it works well for my purposes (I’m not concerned with performance here because it’s editor only, only applies where I attach it, and can be turned off).
using System;
using System.Collections;
using UnityEngine;
public class TransformWatchTest : MonoBehaviour
{
[SerializeField] WatchTransformInEditor example;
[SerializeField] Vector3 value;
void OnValidate() => example.OnValidate(this, Value);
void Value(Vector3 value) => this.value = value;
}
[Serializable]
public class WatchTransformInEditor
{
public bool watch = true;
public float refreshRate = 0.1f;
public Action<Vector3> onSetPosition;
IEnumerator tick;
public void OnValidate(MonoBehaviour mono, Action<Vector3> onSetPosition)
{
if (!watch || !mono.gameObject.activeInHierarchy)
return;
this.onSetPosition = onSetPosition;
if (tick == null)
{
tick = Tick(mono.transform);
mono.StartCoroutine(Tick(mono.transform));
}
}
IEnumerator Tick(Transform transform)
{
var delay = new WaitForSecondsRealtime(refreshRate);
while (watch && transform.gameObject.activeInHierarchy)
{
onSetPosition?.Invoke(transform.position);
yield return delay;
}
tick = null;
}
}