transform.position change callback

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.

2 Likes

This is what Transform.hasChanged was built for.

You need to build a system around that - generally something that iterates the objects you care about and check the flag.

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.

That is absolutely worthless for this purpose. You can’t check the stacktrace and find out what has caused the change.

8 Likes

What other solution would you suggest?

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.

2 Likes

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.

4 Likes

Yes. But if there’s no built in solution, then there’s no performance overhead from them. Bingo!

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.

No, there would be a cost.

You need to do some kind of :

if (shouldCallback)
    DoCallback();

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.

So it’s for sure not easy to implement.

1 Like

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.

Also:

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.

At least it must check if anything subscribed. Also this will affect transform h

No, this is wrong assumption. At worst it will need to marshall a call to c++ part to check if there are any non-null subscribers present.

No, at worst it will immediately lead to the heat death of the universe.

…this is a good feature request and should be considered for inclusion.

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) ?

I’ve put together a tool that can greatly help with detecting those transform changes.

You can grab it on the asset store

2 Likes

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).

1 Like

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;
} 
}